diff options
author | Werner Koch <[email protected]> | 2019-03-18 18:41:07 +0000 |
---|---|---|
committer | Werner Koch <[email protected]> | 2019-03-18 18:41:07 +0000 |
commit | a52d883fdbe6e0de8cb26f9c6aedf01a7f66cbe7 (patch) | |
tree | e59dfb41b24a12c314dbd1137637366315ac1453 | |
parent | kbx: Add framework for a public key daemon. (diff) | |
parent | speedo: Fix installer build with NSIS-3 (diff) | |
download | gnupg-a52d883fdbe6e0de8cb26f9c6aedf01a7f66cbe7.tar.gz gnupg-a52d883fdbe6e0de8cb26f9c6aedf01a7f66cbe7.zip |
Merge branch 'master' into switch-to-gpgk
--
237 files changed, 21246 insertions, 4553 deletions
diff --git a/Makefile.am b/Makefile.am index 680fe1be1..b59e9e3ec 100644 --- a/Makefile.am +++ b/Makefile.am @@ -189,7 +189,7 @@ release: $(MAKE) -f $(RELEASE_NAME)/build-aux/speedo.mk w32-release ;\ echo "/* Build finished at $$(date -uIseconds) */" ;\ echo "/*" ;\ - echo " * Please run the final step interactivly:" ;\ + echo " * Please run the final step interactively:" ;\ echo " * make sign-release" ;\ echo " */" ;\ ) 2>&1 | tee "$(RELEASE_NAME).buildlog" @@ -1,6 +1,66 @@ Noteworthy changes in version 2.3.0 (unreleased) ------------------------------------------------ + Changes also found in 2.2.11: + + * gpgsm: Fix CRL loading when intermediate certicates are not yet + trusted. + + * gpgsm: Fix an error message about the digest algo. [#4219] + + * gpg: Fix a wrong warning due to new sign usage check introduced + with 2.2.9. [#4014] + + * gpg: Print the "data source" even for an unsuccessful keyserver + query. + + * gpg: Do not store the TOFU trust model in the trustdb. This + allows to enable or disable a TOFO model without triggering a + trustdb rebuild. [#4134] + + * scd: Fix cases of "Bad PIN" after using "forcesig". [#4177] + + * agent: Fix possible hang in the ssh handler. [#4221] + + * dirmngr: Tack the unmodified mail address to a WKD request. See + commit a2bd4a64e5b057f291a60a9499f881dd47745e2f for details. + + * dirmngr: Tweak diagnostic about missing LDAP server file. + + * dirmngr: In verbose mode print the OCSP responder id. + + * dirmngr: Fix parsing of the LDAP port. [#4230] + + * wks: Add option --directory/-C to the server. Always build the + server on Unix systems. + + * wks: Add option --with-colons to the client. Support sites which + use the policy file instead of the submission-address file. + + * Fix EBADF when gpg et al. are called by broken CGI scripts. + + * Fix some minor memory leaks and bugs. + + Release-info: https://dev.gnupg.org/T4233 + See-also: gnupg-announce/2018q4/000432.html + + Changes also found in 2.2.10: + + * gpg: Refresh expired keys originating from the WKD. [#2917] + + * gpg: Use a 256 KiB limit for a WKD imported key. + + * gpg: New option --known-notation. [#4060] + + * scd: Add support for the Trustica Cryptoucan reader. + + * agent: Speed up starting during on-demand launching. [#3490] + + * dirmngr: Validate SRV records in WKD queries. + + Release-info: https://dev.gnupg.org/T4112 + See-also: gnupg-announce/2018q3/000428.html + Changes also found in 2.2.9: * dirmngr: Fix recursive resolver mode and other bugs in the libdns @@ -98,7 +158,7 @@ Noteworthy changes in version 2.3.0 (unreleased) * dirmngr: Fallback to CRL if no default OCSP responder is configured. * dirmngr: Implement CRL fetching via https. Here a redirection to - http is explictly allowed. + http is explicitly allowed. * dirmngr: Make LDAP searching and CRL fetching work under Windows. This stopped working with 2.1. [#3937] @@ -317,15 +377,17 @@ Noteworthy changes in version 2.3.0 (unreleased) Release dates of 2.2.x versions: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Version 2.2.1 (2017-09-19) - Version 2.2.2 (2017-11-07) - Version 2.2.3 (2017-11-20) - Version 2.2.4 (2017-12-20) - Version 2.2.5 (2018-02-22) - Version 2.2.6 (2018-04-09) - Version 2.2.7 (2018-05-02) - Version 2.2.8 (2018-06-08) - Version 2.2.9 (2018-07-12) + Version 2.2.1 (2017-09-19) + Version 2.2.2 (2017-11-07) + Version 2.2.3 (2017-11-20) + Version 2.2.4 (2017-12-20) + Version 2.2.5 (2018-02-22) + Version 2.2.6 (2018-04-09) + Version 2.2.7 (2018-05-02) + Version 2.2.8 (2018-06-08) + Version 2.2.9 (2018-07-12) + Version 2.2.10 (2018-08-30) + Version 2.2.11 (2018-11-06) Noteworthy changes in version 2.2.0 (2017-08-28) @@ -959,7 +1021,7 @@ Noteworthy changes in version 2.1.11 (2016-01-26) * gpg: Emit PROGRESS status lines during key generation. - * gpg: Don't check for ambigious or non-matching key specification in + * gpg: Don't check for ambiguous or non-matching key specification in the config file or given to --encrypt-to. This feature will return in 2.3.x. @@ -986,7 +1048,7 @@ Noteworthy changes in version 2.1.11 (2016-01-26) * dirmmgr: All configured keyservers are now searched. * dirmngr: Install CA certificate for hkps.pool.sks-keyservers.net. - Use this certiticate even if --hkp-cacert is not used. + Use this certificate even if --hkp-cacert is not used. * gpgtar: Add actual encryption code. gpgtar does now fully replace gpg-zip. @@ -1020,7 +1082,7 @@ Noteworthy changes in version 2.1.10 (2015-12-04) * gpg: New option --only-sign-text-ids to exclude photo IDs from key signing. - * gpg: Check for ambigious or non-matching key specification in the + * gpg: Check for ambiguous or non-matching key specification in the config file or given to --encrypt-to. * gpg: Show the used card reader with --card-status. @@ -1310,7 +1372,7 @@ Noteworthy changes in version 2.1.1 (2014-12-16) * gpg: Fixed regression in --refresh-keys. - * gpg: Fixed regresion in %g and %p codes for --sig-notation. + * gpg: Fixed regression in %g and %p codes for --sig-notation. * gpg: Fixed best matching hash algo detection for ECDSA and EdDSA. @@ -1390,7 +1452,7 @@ Noteworthy changes in version 2.1.0 (2014-11-06) * gpg: Default keyring is now created with a .kbx suffix. - * gpg: Add a shortcut to the key capabilies menu (e.g. "=e" sets the + * gpg: Add a shortcut to the key capabilities menu (e.g. "=e" sets the encryption capabilities). * gpg: Fixed obsolete options parsing. @@ -1582,7 +1644,7 @@ Noteworthy changes in version 2.1.0 (2014-11-06) * scdaemon: Does not anymore block after changing a card (regression fix). - * tools: gpg-connect-agent does now proberly display the help output + * tools: gpg-connect-agent does now properly display the help output for "SCD HELP" commands. @@ -1707,7 +1769,7 @@ Noteworthy changes in version 2.0.13 (2009-09-04) * Add hack to the internal CCID driver to allow the use of some Omnikey based card readers with 2048 bit keys. - * GPG now repeatly asks the user to insert the requested OpenPGP + * GPG now repeatedly asks the user to insert the requested OpenPGP card. This can be disabled with --limit-card-insert-tries=1. * Minor bug fixes. @@ -1833,7 +1895,7 @@ Noteworthy changes in version 2.0.9 (2008-03-26) * Extended the PKITS framework. - * Fixed a bug in the ambigious name detection. + * Fixed a bug in the ambiguous name detection. * Fixed possible memory corruption while importing OpenPGP keys (bug introduced with 2.0.8). [CVE-2008-1530] @@ -2383,7 +2445,7 @@ Noteworthy changes in version 1.9.2 (2003-11-17) command but from the menu provided by the new --card-edit command. * PINs are now properly cached and there are only 2 PINs visible. - The 3rd PIN (CHV2) is internally syncronized with the regular PIN. + The 3rd PIN (CHV2) is internally synchronized with the regular PIN. * All kind of other internal stuff. @@ -3087,7 +3149,7 @@ Noteworthy changes in version 1.0.1 (1999-12-16) * Fixed some minor bugs and the problem with conventional encrypted packets which did use the gpg v3 partial length headers. - * Add Indonesian and Portugese translations. + * Add Indonesian and Portuguese translations. * Fixed a bug with symmetric-only encryption using the non-default 3DES. The option --emulate-3des-s2k-bug may be used to decrypt documents @@ -3190,7 +3252,7 @@ Noteworthy changes in version 0.9.8 (1999-06-26) * New option --with-key-data to list the public key parameters. New option -N to insert notations and a --set-policy-url. - A couple of other options to allow reseting of options. + A couple of other options to allow resetting of options. * Better support for HPUX. @@ -3669,7 +3731,7 @@ Noteworthy changes in version 0.2.19 (1998-05-29) Noteworthy changes in version 0.2.18 (1998-05-15) ------------------------------------ - * Splitted cipher/random.c, add new option "--disable-dev-random" + * Split cipher/random.c, add new option "--disable-dev-random" to configure to support the development of a random source for other systems. Prepared sourcefiles rand-unix.c, rand-w32.c and rand-dummy.c (which is used to allow compilation on systems diff --git a/agent/agent.h b/agent/agent.h index 9baf59601..0f804cd8b 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -124,7 +124,11 @@ struct passphrase change. */ int enable_passphrase_history; - /* If set the extended key format is used for new keys. */ + /* If set the extended key format is used for new keys. Note that + * this may vave the value 2 in which case + * --disable-extended-key-format won't have any effect and thus + * effectivley locking it. This is required to support existing + * profiles which lock the use of --enable-extended-key-format. */ int enable_extended_key_format; int running_detached; /* We are running detached from the tty. */ @@ -266,6 +270,14 @@ struct server_control_s }; +/* Status of pinentry. */ +enum + { + PINENTRY_STATUS_CLOSE_BUTTON = 1 << 0, + PINENTRY_STATUS_PIN_REPEATED = 1 << 8, + PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9 + }; + /* Information pertaining to pinentry requests. */ struct pin_entry_info_s { @@ -275,7 +287,8 @@ struct pin_entry_info_s int failed_tries; /* Number of tries so far failed. */ int with_qualitybar; /* Set if the quality bar should be displayed. */ int with_repeat; /* Request repetition of the passphrase. */ - int repeat_okay; /* Repetition worked. */ + int repeat_okay; /* Repetition worked. */ + unsigned int status; /* Status. */ gpg_error_t (*check_cb)(struct pin_entry_info_s *); /* CB used to check the PIN */ void *check_cb_arg; /* optional argument which might be of use in the CB */ @@ -488,6 +501,7 @@ gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey, char **passphrase_addr); /*-- protect.c --*/ +void set_s2k_calibration_time (unsigned int milliseconds); unsigned long get_calibrated_s2k_count (void); unsigned long get_standard_s2k_count (void); unsigned char get_standard_s2k_count_rfc4880 (void); @@ -538,15 +552,15 @@ int divert_pkdecrypt (ctrl_t ctrl, const char *desc_text, char **r_buf, size_t *r_len, int *r_padding); int divert_generic_cmd (ctrl_t ctrl, const char *cmdline, void *assuan_context); -int divert_writekey (ctrl_t ctrl, int force, const char *serialno, - const char *id, const char *keydata, size_t keydatalen); +gpg_error_t divert_writekey (ctrl_t ctrl, int force, const char *serialno, + const char *keyref, + const char *keydata, size_t keydatalen); /*-- call-scd.c --*/ void initialize_module_call_scd (void); void agent_scd_dump_state (void); int agent_scd_check_running (void); -void agent_scd_check_aliveness (void); int agent_reset_scd (ctrl_t ctrl); int agent_card_learn (ctrl_t ctrl, void (*kpinfo_cb)(void*, const char *), @@ -577,12 +591,12 @@ int agent_card_pkdecrypt (ctrl_t ctrl, int agent_card_readcert (ctrl_t ctrl, const char *id, char **r_buf, size_t *r_buflen); int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf); -int agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, - const char *id, const char *keydata, - size_t keydatalen, - int (*getpin_cb)(void *, const char *, - const char *, char*, size_t), - void *getpin_cb_arg); +gpg_error_t agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, + const char *keyref, + const char *keydata, size_t keydatalen, + int (*getpin_cb)(void *, const char *, + const char *, char*, size_t), + void *getpin_cb_arg); gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result); gpg_error_t agent_card_cardlist (ctrl_t ctrl, strlist_t *result); int agent_card_scd (ctrl_t ctrl, const char *cmdline, diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index af4eb06f2..34dde3744 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -85,6 +85,7 @@ struct entry_parm_s int lines; size_t size; unsigned char *buffer; + int status; }; @@ -98,11 +99,15 @@ void initialize_module_call_pinentry (void) { static int initialized; + int err; if (!initialized) { - if (npth_mutex_init (&entry_lock, NULL)) - initialized = 1; + err = npth_mutex_init (&entry_lock, NULL); + if (err) + log_fatal ("error initializing mutex: %s\n", strerror (err)); + + initialized = 1; } } @@ -497,14 +502,16 @@ start_pinentry (ctrl_t ctrl) { /* Provide a few default strings for use by the pinentries. This - may help a pinentry to avoid implementing localization code. */ + * may help a pinentry to avoid implementing localization code. + * Note that gpg-agent has been set to utf-8 so that the strings + * are in the expected encoding. */ static const struct { const char *key, *value; int what; } tbl[] = { - /* TRANSLATORS: These are labels for buttons etc used in - Pinentries. An underscore indicates that the next letter - should be used as an accelerator. Double the underscore for - a literal one. The actual to be translated text starts after - the second vertical bar. Note that gpg-agent has been set to - utf-8 so that the strings are in the expected encoding. */ + /* TRANSLATORS: These are labels for buttons etc as used in + * Pinentries. In your translation copy the text before the + * second vertical bar verbatim; translate only the following + * text. An underscore indicates that the next letter should be + * used as an accelerator. Double the underscore to have + * pinentry display a literal underscore. */ { "ok", N_("|pinentry-label|_OK") }, { "cancel", N_("|pinentry-label|_Cancel") }, { "yes", N_("|pinentry-label|_Yes") }, @@ -888,13 +895,6 @@ setup_qualitybar (ctrl_t ctrl) return 0; } -enum - { - PINENTRY_STATUS_CLOSE_BUTTON = 1 << 0, - PINENTRY_STATUS_PIN_REPEATED = 1 << 8, - PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9 - }; - /* Check the button_info line for a close action. Also check for the PIN_REPEATED flag. */ static gpg_error_t @@ -942,6 +942,112 @@ build_cmd_setdesc (char *line, size_t linelen, const char *desc) +/* Watch the socket's EOF condition, while checking finish of + foreground thread. When EOF condition is detected, terminate + the pinentry process behind the assuan pipe. + */ +static void * +watch_sock (void *arg) +{ + gnupg_fd_t *p = (gnupg_fd_t *)arg; + pid_t pid = assuan_get_pid (entry_ctx); + + while (1) + { + int err; + gnupg_fd_t sock = *p; + fd_set fdset; + struct timeval timeout = { 0, 500000 }; + + if (sock == GNUPG_INVALID_FD) + return NULL; + + FD_ZERO (&fdset); + FD_SET (FD2INT (sock), &fdset); + err = npth_select (FD2INT (sock)+1, &fdset, NULL, NULL, &timeout); + + if (err < 0) + { + if (errno == EINTR) + continue; + else + return NULL; + } + + /* Possibly, it's EOF. */ + if (err > 0) + break; + } + + if (pid == (pid_t)(-1)) + ; /* No pid available can't send a kill. */ +#ifdef HAVE_W32_SYSTEM + /* Older versions of assuan set PID to 0 on Windows to indicate an + invalid value. */ + else if (pid != (pid_t) INVALID_HANDLE_VALUE && pid != 0) + TerminateProcess ((HANDLE)pid, 1); +#else + else if (pid > 0) + kill (pid, SIGINT); +#endif + + return NULL; +} + + +/* Ask pinentry to get a pin by "GETPIN" command, spawning a thread + detecting the socket's EOF. + */ +static gpg_error_t +do_getpin (ctrl_t ctrl, struct entry_parm_s *parm) +{ + npth_attr_t tattr; + gpg_error_t rc; + int err; + npth_t thread; + int saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); + gnupg_fd_t sock_watched = ctrl->thread_startup.fd; + + err = npth_attr_init (&tattr); + if (err) + { + log_error ("do_getpin: error npth_attr_init: %s\n", strerror (err)); + return gpg_error_from_errno (err); + } + npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); + + err = npth_create (&thread, &tattr, watch_sock, (void *)&sock_watched); + npth_attr_destroy (&tattr); + if (err) + { + log_error ("do_getpin: error spawning thread: %s\n", strerror (err)); + return gpg_error_from_errno (err); + } + + assuan_begin_confidential (entry_ctx); + rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, parm, + inq_quality, entry_ctx, + pinentry_status_cb, &parm->status); + assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); + /* Most pinentries out in the wild return the old Assuan error code + for canceled which gets translated to an assuan Cancel error and + not to the code for a user cancel. Fix this here. */ + if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); + /* Change error code in case the window close button was clicked + to cancel the operation. */ + if ((parm->status & PINENTRY_STATUS_CLOSE_BUTTON) + && gpg_err_code (rc) == GPG_ERR_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); + + sock_watched = GNUPG_INVALID_FD; + err = npth_join (thread, NULL); + if (err) + log_error ("do_getpin: error joining thread: %s\n", strerror (err)); + + return rc; +} + /* Call the Entry and ask for the PIN. We do check for a valid PIN number here and repeat it as long as we have invalid formed numbers. KEYINFO and CACHE_MODE are used to tell pinentry something @@ -958,8 +1064,6 @@ agent_askpin (ctrl_t ctrl, struct entry_parm_s parm; const char *errtext = NULL; int is_pin = 0; - int saveflag; - unsigned int pinentry_status; if (opt.batch) return 0; /* fixme: we should return BAD PIN */ @@ -1070,6 +1174,7 @@ agent_askpin (ctrl_t ctrl, pininfo->with_repeat = 0; /* Pinentry does not support it. */ } pininfo->repeat_okay = 0; + pininfo->status = 0; for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++) { @@ -1101,27 +1206,8 @@ agent_askpin (ctrl_t ctrl, return unlock_pinentry (ctrl, rc); } - saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); - assuan_begin_confidential (entry_ctx); - pinentry_status = 0; - rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, - inq_quality, entry_ctx, - pinentry_status_cb, &pinentry_status); - assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); - /* Most pinentries out in the wild return the old Assuan error code - for canceled which gets translated to an assuan Cancel error and - not to the code for a user cancel. Fix this here. */ - if (rc && gpg_err_source (rc) - && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) - rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); - - - /* Change error code in case the window close button was clicked - to cancel the operation. */ - if ((pinentry_status & PINENTRY_STATUS_CLOSE_BUTTON) - && gpg_err_code (rc) == GPG_ERR_CANCELED) - rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); - + rc = do_getpin (ctrl, &parm); + pininfo->status = parm.status; if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA) errtext = is_pin? L_("PIN too long") : L_("Passphrase too long"); @@ -1145,12 +1231,19 @@ agent_askpin (ctrl_t ctrl, /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); - if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE - && pininfo->cb_errtext) - errtext = pininfo->cb_errtext; - else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE - || gpg_err_code (rc) == GPG_ERR_BAD_PIN) - errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); + /* When pinentry cache causes an error, return now. */ + if (rc + && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) + return unlock_pinentry (ctrl, rc); + + if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) + { + if (pininfo->cb_errtext) + errtext = pininfo->cb_errtext; + else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE + || gpg_err_code (rc) == GPG_ERR_BAD_PIN) + errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); + } else if (rc) return unlock_pinentry (ctrl, rc); } @@ -1158,12 +1251,12 @@ agent_askpin (ctrl_t ctrl, if (!errtext) { if (pininfo->with_repeat - && (pinentry_status & PINENTRY_STATUS_PIN_REPEATED)) + && (pininfo->status & PINENTRY_STATUS_PIN_REPEATED)) pininfo->repeat_okay = 1; return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */ } - if ((pinentry_status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) + if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) /* The password was read from the cache. Don't count this against the retry count. */ pininfo->failed_tries --; @@ -1183,12 +1276,9 @@ agent_get_passphrase (ctrl_t ctrl, const char *errtext, int with_qualitybar, const char *keyinfo, cache_mode_t cache_mode) { - int rc; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; - int saveflag; - unsigned int pinentry_status; *retpass = NULL; if (opt.batch) @@ -1272,24 +1362,7 @@ agent_get_passphrase (ctrl_t ctrl, if (!parm.buffer) return unlock_pinentry (ctrl, out_of_core ()); - saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); - assuan_begin_confidential (entry_ctx); - pinentry_status = 0; - rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, - inq_quality, entry_ctx, - pinentry_status_cb, &pinentry_status); - assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); - /* Most pinentries out in the wild return the old Assuan error code - for canceled which gets translated to an assuan Cancel error and - not to the code for a user cancel. Fix this here. */ - if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) - rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); - /* Change error code in case the window close button was clicked - to cancel the operation. */ - if ((pinentry_status & PINENTRY_STATUS_CLOSE_BUTTON) - && gpg_err_code (rc) == GPG_ERR_CANCELED) - rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); - + rc = do_getpin (ctrl, &parm); if (rc) xfree (parm.buffer); else @@ -1537,14 +1610,6 @@ agent_popup_message_stop (ctrl_t ctrl) TerminateProcess (process, 1); } #else - else if (pid && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) - { /* The daemon already died. No need to send a kill. However - because we already waited for the process, we need to tell - assuan that it should not wait again (done by - unlock_pinentry). */ - if (rc == pid) - assuan_set_flag (entry_ctx, ASSUAN_NO_WAITPID, 1); - } else if (pid > 0) kill (pid, SIGINT); #endif diff --git a/agent/call-scd.c b/agent/call-scd.c index 51d9abd70..b2266225e 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -54,17 +54,10 @@ struct scd_local_s SCD_LOCAL_LIST (see below). */ struct scd_local_s *next_local; - /* We need to get back to the ctrl object actually referencing this - structure. This is really an awkward way of enumerating the local - contexts. A much cleaner way would be to keep a global list of - ctrl objects to enumerate them. */ - ctrl_t ctrl_backlink; - - assuan_context_t ctx; /* NULL or session context for the SCdaemon - used with this connection. */ - int locked; /* This flag is used to assert proper use of - start_scd and unlock_scd. */ - + assuan_context_t ctx; /* NULL or session context for the SCdaemon + used with this connection. */ + unsigned int in_use: 1; /* CTX is in use. */ + unsigned int invalid:1; /* CTX is invalid, should be released. */ }; @@ -138,7 +131,7 @@ initialize_module_call_scd (void) { err = npth_mutex_init (&start_scd_lock, NULL); if (err) - log_fatal ("error initializing mutex: %s\n", strerror (err)); + log_fatal ("error initializing mutex: %s\n", strerror (err)); initialized = 1; } } @@ -168,14 +161,33 @@ agent_scd_dump_state (void) static int unlock_scd (ctrl_t ctrl, int rc) { - if (ctrl->scd_local->locked != 1) + int err; + + if (ctrl->scd_local->in_use == 0) { - log_error ("unlock_scd: invalid lock count (%d)\n", - ctrl->scd_local->locked); + log_error ("unlock_scd: CTX is not in use\n"); if (!rc) rc = gpg_error (GPG_ERR_INTERNAL); } - ctrl->scd_local->locked = 0; + err = npth_mutex_lock (&start_scd_lock); + if (err) + { + log_error ("failed to acquire the start_scd lock: %s\n", strerror (err)); + return gpg_error (GPG_ERR_INTERNAL); + } + ctrl->scd_local->in_use = 0; + if (ctrl->scd_local->invalid) + { + assuan_release (ctrl->scd_local->ctx); + ctrl->scd_local->ctx = NULL; + ctrl->scd_local->invalid = 0; + } + err = npth_mutex_unlock (&start_scd_lock); + if (err) + { + log_error ("failed to release the start_scd lock: %s\n", strerror (err)); + return gpg_error (GPG_ERR_INTERNAL); + } return rc; } @@ -191,6 +203,86 @@ atfork_cb (void *opaque, int where) } +static void * +wait_child_thread (void *arg) +{ + int err; + struct scd_local_s *sl; + +#ifdef HAVE_W32_SYSTEM + HANDLE pid = (HANDLE)arg; + + npth_unprotect (); + WaitForSingleObject ((HANDLE)pid, INFINITE); + npth_protect (); + log_info ("scdaemon finished\n"); +#else + int wstatus; + pid_t pid = (pid_t)(uintptr_t)arg; + + again: + npth_unprotect (); + err = waitpid (pid, &wstatus, 0); + npth_protect (); + + if (err < 0) + { + if (errno == EINTR) + goto again; + log_error ("waitpid failed: %s\n", strerror (errno)); + return NULL; + } + else + { + if (WIFEXITED (wstatus)) + log_info ("scdaemon finished (status %d)\n", WEXITSTATUS (wstatus)); + else if (WIFSIGNALED (wstatus)) + log_info ("scdaemon killed by signal %d\n", WTERMSIG (wstatus)); + else + { + if (WIFSTOPPED (wstatus)) + log_info ("scdaemon stopped by signal %d\n", WSTOPSIG (wstatus)); + goto again; + } + } +#endif + + err = npth_mutex_lock (&start_scd_lock); + if (err) + { + log_error ("failed to acquire the start_scd lock: %s\n", + strerror (err)); + } + else + { + assuan_set_flag (primary_scd_ctx, ASSUAN_NO_WAITPID, 1); + + for (sl = scd_local_list; sl; sl = sl->next_local) + { + sl->invalid = 1; + if (!sl->in_use && sl->ctx) + { + assuan_release (sl->ctx); + sl->ctx = NULL; + } + } + + primary_scd_ctx = NULL; + primary_scd_ctx_reusable = 0; + + xfree (socket_name); + socket_name = NULL; + + err = npth_mutex_unlock (&start_scd_lock); + if (err) + log_error ("failed to release the start_scd lock after waitpid: %s\n", + strerror (err)); + } + + return NULL; +} + + /* Fork off the SCdaemon if this has not already been done. Lock the daemon and make sure that a proper context has been setup in CTRL. This function might also lock the daemon, which means that the @@ -211,37 +303,19 @@ start_scd (ctrl_t ctrl) if (opt.disable_scdaemon) return gpg_error (GPG_ERR_NOT_SUPPORTED); - /* If this is the first call for this session, setup the local data - structure. */ - if (!ctrl->scd_local) + if (ctrl->scd_local && ctrl->scd_local->ctx) { - ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local); - if (!ctrl->scd_local) - return gpg_error_from_syserror (); - ctrl->scd_local->ctrl_backlink = ctrl; - ctrl->scd_local->next_local = scd_local_list; - scd_local_list = ctrl->scd_local; + ctrl->scd_local->in_use = 1; + return 0; /* Okay, the context is fine. */ } - - /* Assert that the lock count is as expected. */ - if (ctrl->scd_local->locked) + if (ctrl->scd_local && ctrl->scd_local->in_use) { - log_error ("start_scd: invalid lock count (%d)\n", - ctrl->scd_local->locked); + log_error ("start_scd: CTX is in use\n"); return gpg_error (GPG_ERR_INTERNAL); } - ctrl->scd_local->locked++; - - if (ctrl->scd_local->ctx) - return 0; /* Okay, the context is fine. We used to test for an - alive context here and do an disconnect. Now that we - have a ticker function to check for it, it is easier - not to check here but to let the connection run on an - error instead. */ - - /* We need to protect the following code. */ + /* We need to serialize the access to scd_local_list and primary_scd_ctx. */ rc = npth_mutex_lock (&start_scd_lock); if (rc) { @@ -250,6 +324,25 @@ start_scd (ctrl_t ctrl) return gpg_error (GPG_ERR_INTERNAL); } + /* If this is the first call for this session, setup the local data + structure. */ + if (!ctrl->scd_local) + { + ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local); + if (!ctrl->scd_local) + { + err = gpg_error_from_syserror (); + rc = npth_mutex_unlock (&start_scd_lock); + if (rc) + log_error ("failed to release the start_scd lock: %s\n", strerror (rc)); + return err; + } + ctrl->scd_local->next_local = scd_local_list; + scd_local_list = ctrl->scd_local; + } + + ctrl->scd_local->in_use = 1; + /* Check whether the pipe server has already been started and in this case either reuse a lingering pipe connection or establish a new socket based one. */ @@ -351,7 +444,7 @@ start_scd (ctrl_t ctrl) detached flag so that under Windows SCDAEMON does not show up a new window. */ rc = assuan_pipe_connect (ctx, opt.scdaemon_program, argv, - no_close_list, atfork_cb, NULL, + no_close_list, atfork_cb, NULL, ASSUAN_PIPE_CONNECT_DETACHED); if (rc) { @@ -415,21 +508,41 @@ start_scd (ctrl_t ctrl) primary_scd_ctx = ctx; primary_scd_ctx_reusable = 0; + { + npth_t thread; + npth_attr_t tattr; + pid_t pid; + + pid = assuan_get_pid (primary_scd_ctx); + err = npth_attr_init (&tattr); + if (!err) + { + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + err = npth_create (&thread, &tattr, wait_child_thread, + (void *)(uintptr_t)pid); + if (err) + log_error ("error spawning wait_child_thread: %s\n", strerror (err)); + npth_attr_destroy (&tattr); + } + } + leave: + rc = npth_mutex_unlock (&start_scd_lock); + if (rc) + log_error ("failed to release the start_scd lock: %s\n", strerror (rc)); + xfree (abs_homedir); if (err) { unlock_scd (ctrl, err); if (ctx) - assuan_release (ctx); + assuan_release (ctx); } else { + ctrl->scd_local->invalid = 0; ctrl->scd_local->ctx = ctx; } - rc = npth_mutex_unlock (&start_scd_lock); - if (rc) - log_error ("failed to release the start_scd lock: %s\n", strerror (rc)); return err; } @@ -444,144 +557,69 @@ agent_scd_check_running (void) } -/* Check whether the Scdaemon is still alive and clean it up if not. */ -void -agent_scd_check_aliveness (void) +/* Reset the SCD if it has been used. Actually it is not a reset but + a cleanup of resources used by the current connection. */ +int +agent_reset_scd (ctrl_t ctrl) { - pid_t pid; -#ifdef HAVE_W32_SYSTEM - DWORD rc; -#else - int rc; -#endif - struct timespec abstime; - int err; - - if (!primary_scd_ctx) - return; /* No scdaemon running. */ + int err = npth_mutex_lock (&start_scd_lock); - /* This is not a critical function so we use a short timeout while - acquiring the lock. */ - npth_clock_gettime (&abstime); - abstime.tv_sec += 1; - err = npth_mutex_timedlock (&start_scd_lock, &abstime); if (err) { - if (err == ETIMEDOUT) - { - if (opt.verbose > 1) - log_info ("failed to acquire the start_scd lock while" - " doing an aliveness check: %s\n", strerror (err)); - } - else - log_error ("failed to acquire the start_scd lock while" - " doing an aliveness check: %s\n", strerror (err)); - return; + log_error ("failed to acquire the start_scd lock: %s\n", + strerror (err)); } - - if (primary_scd_ctx) + else { - pid = assuan_get_pid (primary_scd_ctx); -#ifdef HAVE_W32_SYSTEM - /* If we have a PID we disconnect if either GetExitProcessCode - fails or if ir returns the exit code of the scdaemon. 259 is - the error code for STILL_ALIVE. */ - if (pid != (pid_t)(void*)(-1) && pid - && (!GetExitCodeProcess ((HANDLE)pid, &rc) || rc != 259)) -#else - if (pid != (pid_t)(-1) && pid - && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) -#endif + if (ctrl->scd_local) { - /* Okay, scdaemon died. Disconnect the primary connection - now but take care that it won't do another wait. Also - cleanup all other connections and release their - resources. The next use will start a new daemon then. - Due to the use of the START_SCD_LOCAL we are sure that - none of these context are actually in use. */ - struct scd_local_s *sl; - - assuan_set_flag (primary_scd_ctx, ASSUAN_NO_WAITPID, 1); - assuan_release (primary_scd_ctx); - - for (sl=scd_local_list; sl; sl = sl->next_local) + if (ctrl->scd_local->ctx) { - if (sl->ctx) + /* We send a reset and keep that connection for reuse. */ + if (ctrl->scd_local->ctx == primary_scd_ctx) { - if (sl->ctx != primary_scd_ctx) - assuan_release (sl->ctx); - sl->ctx = NULL; + /* Send a RESTART to the SCD. This is required for the + primary connection as a kind of virtual EOF; we don't + have another way to tell it that the next command + should be viewed as if a new connection has been + made. For the non-primary connections this is not + needed as we simply close the socket. We don't check + for an error here because the RESTART may fail for + example if the scdaemon has already been terminated. + Anyway, we need to set the reusable flag to make sure + that the aliveness check can clean it up. */ + assuan_transact (primary_scd_ctx, "RESTART", + NULL, NULL, NULL, NULL, NULL, NULL); + primary_scd_ctx_reusable = 1; } + else + assuan_release (ctrl->scd_local->ctx); + ctrl->scd_local->ctx = NULL; } - primary_scd_ctx = NULL; - primary_scd_ctx_reusable = 0; - - xfree (socket_name); - socket_name = NULL; - } - } - - err = npth_mutex_unlock (&start_scd_lock); - if (err) - log_error ("failed to release the start_scd lock while" - " doing the aliveness check: %s\n", strerror (err)); -} - - - -/* Reset the SCD if it has been used. Actually it is not a reset but - a cleanup of resources used by the current connection. */ -int -agent_reset_scd (ctrl_t ctrl) -{ - if (ctrl->scd_local) - { - if (ctrl->scd_local->ctx) - { - /* We can't disconnect the primary context because libassuan - does a waitpid on it and thus the system would hang. - Instead we send a reset and keep that connection for - reuse. */ - if (ctrl->scd_local->ctx == primary_scd_ctx) + /* Remove the local context from our list and release it. */ + if (!scd_local_list) + BUG (); + else if (scd_local_list == ctrl->scd_local) + scd_local_list = ctrl->scd_local->next_local; + else { - /* Send a RESTART to the SCD. This is required for the - primary connection as a kind of virtual EOF; we don't - have another way to tell it that the next command - should be viewed as if a new connection has been - made. For the non-primary connections this is not - needed as we simply close the socket. We don't check - for an error here because the RESTART may fail for - example if the scdaemon has already been terminated. - Anyway, we need to set the reusable flag to make sure - that the aliveness check can clean it up. */ - assuan_transact (primary_scd_ctx, "RESTART", - NULL, NULL, NULL, NULL, NULL, NULL); - primary_scd_ctx_reusable = 1; + struct scd_local_s *sl; + + for (sl=scd_local_list; sl->next_local; sl = sl->next_local) + if (sl->next_local == ctrl->scd_local) + break; + if (!sl->next_local) + BUG (); + sl->next_local = ctrl->scd_local->next_local; } - else - assuan_release (ctrl->scd_local->ctx); - ctrl->scd_local->ctx = NULL; + xfree (ctrl->scd_local); + ctrl->scd_local = NULL; } - /* Remove the local context from our list and release it. */ - if (!scd_local_list) - BUG (); - else if (scd_local_list == ctrl->scd_local) - scd_local_list = ctrl->scd_local->next_local; - else - { - struct scd_local_s *sl; - - for (sl=scd_local_list; sl->next_local; sl = sl->next_local) - if (sl->next_local == ctrl->scd_local) - break; - if (!sl->next_local) - BUG (); - sl->next_local = ctrl->scd_local->next_local; - } - xfree (ctrl->scd_local); - ctrl->scd_local = NULL; + err = npth_mutex_unlock (&start_scd_lock); + if (err) + log_error ("failed to release the start_scd lock: %s\n", strerror (err)); } return 0; @@ -1055,23 +1093,27 @@ inq_writekey_parms (void *opaque, const char *line) } -int +/* Call scd to write a key to a card under the id KEYREF. */ +gpg_error_t agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, - const char *id, const char *keydata, size_t keydatalen, + const char *keyref, + const char *keydata, size_t keydatalen, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg) { - int rc; + gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct inq_needpin_parm_s parms; - (void)serialno; - rc = start_scd (ctrl); - if (rc) - return rc; + (void)serialno; /* NULL or a number to check for the correct card. + * But is is not implemented. */ - snprintf (line, DIM(line), "WRITEKEY %s%s", force ? "--force " : "", id); + err = start_scd (ctrl); + if (err) + return err; + + snprintf (line, DIM(line), "WRITEKEY %s%s", force ? "--force " : "", keyref); parms.ctx = ctrl->scd_local->ctx; parms.getpin_cb = getpin_cb; parms.getpin_cb_arg = getpin_cb_arg; @@ -1080,9 +1122,9 @@ agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, parms.keydata = keydata; parms.keydatalen = keydatalen; - rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, - inq_writekey_parms, &parms, NULL, NULL); - return unlock_scd (ctrl, rc); + err = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, + inq_writekey_parms, &parms, NULL, NULL); + return unlock_scd (ctrl, err); } diff --git a/agent/command-ssh.c b/agent/command-ssh.c index df63ed713..ebd28ab5a 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -195,9 +195,14 @@ struct ssh_key_type_spec algorithm. */ ssh_signature_encoder_t signature_encoder; - /* The name of the ECC curve or NULL. */ + /* The name of the ECC curve or NULL for non-ECC algos. This is the + * canonical name for the curve as specified by RFC-5656. */ const char *curve_name; + /* An alias for curve_name or NULL. Actually this is Libcgrypt's + * primary name of the curve. */ + const char *alt_curve_name; + /* The hash algorithm to be used with this key. 0 for using the default. */ int hash_algo; @@ -297,68 +302,71 @@ static const ssh_key_type_spec_t ssh_key_types[] = { "ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_eddsa, - "Ed25519", 0, SPEC_FLAG_IS_EdDSA + "Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA }, { "ssh-rsa", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu", ssh_key_modifier_rsa, ssh_signature_encoder_rsa, - NULL, 0, SPEC_FLAG_USE_PKCS1V2 + NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2 }, { "ssh-dss", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx", NULL, ssh_signature_encoder_dsa, - NULL, 0, 0 + NULL, NULL, 0, 0 }, { "ecdsa-sha2-nistp256", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, - "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA + "nistp256", "NIST P-256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA }, { "ecdsa-sha2-nistp384", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, - "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA + "nistp384", "NIST P-384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA }, { "ecdsa-sha2-nistp521", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, - "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA + "nistp521", "NIST P-521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA }, { "[email protected]", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_eddsa, - "Ed25519", 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT + "Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT }, { "[email protected]", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu", ssh_key_modifier_rsa, ssh_signature_encoder_rsa, - NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT + NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT }, { "[email protected]", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx", NULL, ssh_signature_encoder_dsa, - NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT + NULL, NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT }, { "[email protected]", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, - "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT + "nistp256", "NIST P-256", GCRY_MD_SHA256, + SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT }, { "[email protected]", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, - "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT + "nistp384", "NIST P-384", GCRY_MD_SHA384, + SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT }, { "[email protected]", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, - "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT + "nistp521", "NIST P-521", GCRY_MD_SHA512, + SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT } }; @@ -389,16 +397,24 @@ realloc_secure (void *a, size_t n) /* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns - NULL if not found. */ + * NULL if not found. If found the ssh indetifier is returned and a + * pointer to the canonical curve name as specified for ssh is stored + * at R_CANON_NAME. */ static const char * -ssh_identifier_from_curve_name (const char *curve_name) +ssh_identifier_from_curve_name (const char *curve_name, + const char **r_canon_name) { int i; for (i = 0; i < DIM (ssh_key_types); i++) if (ssh_key_types[i].curve_name - && !strcmp (ssh_key_types[i].curve_name, curve_name)) - return ssh_key_types[i].ssh_identifier; + && (!strcmp (ssh_key_types[i].curve_name, curve_name) + || (ssh_key_types[i].alt_curve_name + && !strcmp (ssh_key_types[i].alt_curve_name, curve_name)))) + { + *r_canon_name = ssh_key_types[i].curve_name; + return ssh_key_types[i].ssh_identifier; + } return NULL; } @@ -1849,7 +1865,6 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, gpg_error_t err = 0; gcry_sexp_t value_list = NULL; gcry_sexp_t value_pair = NULL; - char *curve_name = NULL; estream_t stream = NULL; void *blob = NULL; size_t blob_size; @@ -1867,7 +1882,7 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, goto out; } - /* Get the type of the key extpression. */ + /* Get the type of the key expression. */ data = gcry_sexp_nth_data (sexp, 0, &datalen); if (!data) { @@ -1898,49 +1913,17 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, /* Write the ssh algorithm identifier. */ if ((key_spec.flags & SPEC_FLAG_IS_ECDSA)) { - /* Parse the "curve" parameter. We currently expect the curve - name for ECC and not the parameters of the curve. This can - easily be changed but then we need to find the curve name - from the parameters using gcry_pk_get_curve. */ - const char *mapped; - const char *sshname; + /* Map the curve name to the ssh name. */ + const char *name, *sshname, *canon_name; - gcry_sexp_release (value_pair); - value_pair = gcry_sexp_find_token (value_list, "curve", 5); - if (!value_pair) - { - err = gpg_error (GPG_ERR_INV_CURVE); - goto out; - } - curve_name = gcry_sexp_nth_string (value_pair, 1); - if (!curve_name) - { - err = gpg_error (GPG_ERR_INV_CURVE); /* (Or out of core.) */ - goto out; - } - - /* Fixme: The mapping should be done by using gcry_pk_get_curve - et al to iterate over all name aliases. */ - if (!strcmp (curve_name, "NIST P-256")) - mapped = "nistp256"; - else if (!strcmp (curve_name, "NIST P-384")) - mapped = "nistp384"; - else if (!strcmp (curve_name, "NIST P-521")) - mapped = "nistp521"; - else - mapped = NULL; - if (mapped) + name = gcry_pk_get_curve (sexp, 0, NULL); + if (!name) { - xfree (curve_name); - curve_name = xtrystrdup (mapped); - if (!curve_name) - { - err = gpg_error_from_syserror (); - goto out; - } + err = gpg_error (GPG_ERR_INV_CURVE); + goto out; } - sshname = ssh_identifier_from_curve_name (curve_name); + sshname = ssh_identifier_from_curve_name (name, &canon_name); if (!sshname) { err = gpg_error (GPG_ERR_UNKNOWN_CURVE); @@ -1949,7 +1932,7 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, err = stream_write_cstring (stream, sshname); if (err) goto out; - err = stream_write_cstring (stream, curve_name); + err = stream_write_cstring (stream, canon_name); if (err) goto out; } @@ -2022,7 +2005,6 @@ ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, out: gcry_sexp_release (value_list); gcry_sexp_release (value_pair); - xfree (curve_name); es_fclose (stream); es_free (blob); @@ -2081,7 +2063,7 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, ssh_key_type_spec_t spec; gcry_mpi_t *mpi_list = NULL; const char *elems; - char *curve_name = NULL; + const char *curve_name = NULL; err = stream_read_cstring (stream, &key_type); @@ -2204,34 +2186,19 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, * certificate. */ unsigned char *buffer; - const char *mapped; err = stream_read_string (cert? cert : stream, 0, &buffer, NULL); if (err) goto out; - curve_name = buffer; - /* Fixme: Check that curve_name matches the keytype. */ - /* Because Libgcrypt < 1.6 has no support for the "nistpNNN" - curve names, we need to translate them here to Libgcrypt's - native names. */ - if (!strcmp (curve_name, "nistp256")) - mapped = "NIST P-256"; - else if (!strcmp (curve_name, "nistp384")) - mapped = "NIST P-384"; - else if (!strcmp (curve_name, "nistp521")) - mapped = "NIST P-521"; - else - mapped = NULL; - if (mapped) + /* Get the canonical name. Should be the same as the read + * string but we use this mapping to validate that name. */ + if (!ssh_identifier_from_curve_name (buffer, &curve_name)) { - xfree (curve_name); - curve_name = xtrystrdup (mapped); - if (!curve_name) - { - err = gpg_error_from_syserror (); - goto out; - } + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + xfree (buffer); + goto out; } + xfree (buffer); err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list); if (err) @@ -2299,7 +2266,6 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, out: es_fclose (cert); mpint_list_free (mpi_list); - xfree (curve_name); xfree (key_type); xfree (comment); @@ -2647,6 +2613,8 @@ ssh_handler_request_identities (ctrl_t ctrl, continue; err = ssh_send_key_public (key_blobs, key_public, cardsn); + if (err && opt.verbose) + gcry_log_debugsxp ("pubkey", key_public); gcry_sexp_release (key_public); key_public = NULL; xfree (cardsn); @@ -2722,6 +2690,8 @@ ssh_handler_request_identities (ctrl_t ctrl, } else { + log_error ("ssh request identities failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); } @@ -2751,7 +2721,7 @@ data_hash (unsigned char *data, size_t data_n, allow the use of signature algorithms that implement the hashing internally (e.g. Ed25519). On success the created signature is stored in ssh format at R_SIG and it's size at R_SIGLEN; the caller - must use es_free to releaase this memory. */ + must use es_free to release this memory. */ static gpg_error_t data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec, const void *hash, size_t hashlen, @@ -3249,9 +3219,10 @@ ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response) while (1) { err = stream_read_byte (request, &b); - if (gpg_err_code (err) == GPG_ERR_EOF) - { - err = 0; + if (err) + { + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; break; } @@ -3625,7 +3596,7 @@ static void get_client_info (int fd, struct peer_info_s *out) { pid_t client_pid = (pid_t)(-1); - uid_t client_uid = (uid_t)-1; + int client_uid = -1; #ifdef SO_PEERCRED { @@ -3640,10 +3611,10 @@ get_client_info (int fd, struct peer_info_s *out) { #if defined (HAVE_STRUCT_SOCKPEERCRED_PID) || defined (HAVE_STRUCT_UCRED_PID) client_pid = cr.pid; - client_uid = cr.uid; + client_uid = (int)cr.uid; #elif defined (HAVE_STRUCT_UCRED_CR_PID) client_pid = cr.cr_pid; - client_pid = cr.cr_uid; + client_uid = (int)cr.cr_uid; #else #error "Unknown SO_PEERCRED struct" #endif @@ -3660,7 +3631,7 @@ get_client_info (int fd, struct peer_info_s *out) len = sizeof (struct xucred); if (!getsockopt (fd, SOL_LOCAL, LOCAL_PEERCRED, &cr, &len)) - client_uid = cr.cr_uid; + client_uid = (int)cr.cr_uid; } #endif } @@ -3670,8 +3641,10 @@ get_client_info (int fd, struct peer_info_s *out) socklen_t unpl = sizeof unp; if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1) - client_pid = unp.unp_pid; - client_uid = unp.unp_euid; + { + client_pid = unp.unp_pid; + client_uid = (int)unp.unp_euid; + } } #elif defined (HAVE_GETPEERUCRED) { @@ -3680,7 +3653,7 @@ get_client_info (int fd, struct peer_info_s *out) if (getpeerucred (fd, &ucred) != -1) { client_pid = ucred_getpid (ucred); - client_uid = ucred_geteuid (ucred); + client_uid = (int)ucred_geteuid (ucred); ucred_free (ucred); } } @@ -3689,7 +3662,7 @@ get_client_info (int fd, struct peer_info_s *out) #endif out->pid = (client_pid == (pid_t)(-1)? 0 : (unsigned long)client_pid); - out->uid = (int)client_uid; + out->uid = client_uid; } diff --git a/agent/command.c b/agent/command.c index 925d1f780..5e2b6df2b 100644 --- a/agent/command.c +++ b/agent/command.c @@ -887,7 +887,7 @@ cmd_genkey (assuan_context_t ctx, char *line) ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int no_protection; - unsigned char *value; + unsigned char *value = NULL; size_t valuelen; unsigned char *newpasswd = NULL; membuf_t outbuf; @@ -1595,19 +1595,24 @@ static const char hlp_clear_passphrase[] = "may be used to invalidate the cache entry for a passphrase. The\n" "function returns with OK even when there is no cached passphrase.\n" "The --mode=normal option is used to clear an entry for a cacheid\n" - "added by the agent.\n"; + "added by the agent. The --mode=ssh option is used for a cacheid\n" + "added for ssh.\n"; static gpg_error_t cmd_clear_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *cacheid = NULL; char *p; - int opt_normal; + cache_mode_t cache_mode = CACHE_MODE_USER; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); - opt_normal = has_option (line, "--mode=normal"); + if (has_option (line, "--mode=normal")) + cache_mode = CACHE_MODE_NORMAL; + else if (has_option (line, "--mode=ssh")) + cache_mode = CACHE_MODE_SSH; + line = skip_options (line); /* parse the stuff */ @@ -1620,12 +1625,9 @@ cmd_clear_passphrase (assuan_context_t ctx, char *line) if (!*cacheid || strlen (cacheid) > 50) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID"); - agent_put_cache (ctrl, cacheid, - opt_normal ? CACHE_MODE_NORMAL : CACHE_MODE_USER, - NULL, 0); + agent_put_cache (ctrl, cacheid, cache_mode, NULL, 0); - agent_clear_passphrase (ctrl, cacheid, - opt_normal ? CACHE_MODE_NORMAL : CACHE_MODE_USER); + agent_clear_passphrase (ctrl, cacheid, cache_mode); return 0; } @@ -2482,19 +2484,23 @@ cmd_delete_key (assuan_context_t ctx, char *line) #endif static const char hlp_keytocard[] = - "KEYTOCARD [--force] <hexstring_with_keygrip> <serialno> <id> <timestamp>\n" - "\n"; + "KEYTOCARD [--force] <hexgrip> <serialno> <keyref> [<timestamp>]\n" + "\n" + "TIMESTAMP is required for OpenPGP and defaults to the Epoch. The\n" + "SERIALNO is used for checking; use \"-\" to disable the check."; static gpg_error_t cmd_keytocard (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int force; gpg_error_t err = 0; + char *argv[5]; + int argc; unsigned char grip[20]; + const char *serialno, *timestamp_str, *keyref; gcry_sexp_t s_skey = NULL; unsigned char *keydata; size_t keydatalen; - const char *serialno, *timestamp_str, *id; unsigned char *shadow_info = NULL; time_t timestamp; @@ -2504,7 +2510,14 @@ cmd_keytocard (assuan_context_t ctx, char *line) force = has_option (line, "--force"); line = skip_options (line); - err = parse_keygrip (ctx, line, grip); + argc = split_fields (line, argv, DIM (argv)); + if (argc < 3) + { + err = gpg_error (GPG_ERR_MISSING_VALUE); + goto leave; + } + + err = parse_keygrip (ctx, argv[0], grip); if (err) goto leave; @@ -2514,39 +2527,19 @@ cmd_keytocard (assuan_context_t ctx, char *line) goto leave; } - /* Fixme: Replace the parsing code by split_fields(). */ - line += 40; - while (*line && (*line == ' ' || *line == '\t')) - line++; - serialno = line; - while (*line && (*line != ' ' && *line != '\t')) - line++; - if (!*line) - { - err = gpg_error (GPG_ERR_MISSING_VALUE); - goto leave; - } - *line = '\0'; - line++; - while (*line && (*line == ' ' || *line == '\t')) - line++; - id = line; - while (*line && (*line != ' ' && *line != '\t')) - line++; - if (!*line) - { - err = gpg_error (GPG_ERR_MISSING_VALUE); - goto leave; - } - *line = '\0'; - line++; - while (*line && (*line == ' ' || *line == '\t')) - line++; - timestamp_str = line; - while (*line && (*line != ' ' && *line != '\t')) - line++; - if (*line) - *line = '\0'; + /* Note that checking of the s/n is currently not implemented but we + * want to provide a clean interface if we ever implement it. */ + serialno = argv[1]; + if (!strcmp (serialno, "-")) + serialno = NULL; + + keyref = argv[2]; + + /* FIXME: Default to the creation time as stored in the private + * key. The parameter is here so that gpg can make sure that the + * timestamp as used for key creation (and thus the openPGP + * fingerprint) is used. */ + timestamp_str = argc > 3? argv[3] : "19700101T000000"; if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1)) { @@ -2558,38 +2551,37 @@ cmd_keytocard (assuan_context_t ctx, char *line) &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, NULL); if (err) - { - xfree (shadow_info); - goto leave; - } + goto leave; if (shadow_info) { - /* Key is on a smartcard already. */ - xfree (shadow_info); - gcry_sexp_release (s_skey); + /* Key is already on a smartcard - we can't extract it. */ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); goto leave; } - keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); + /* Note: We can't use make_canon_sexp because we need to allocate a + * few extra bytes for our hack below. */ + keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); keydata = xtrymalloc_secure (keydatalen + 30); if (keydata == NULL) { err = gpg_error_from_syserror (); - gcry_sexp_release (s_skey); goto leave; } - gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen); gcry_sexp_release (s_skey); + s_skey = NULL; keydatalen--; /* Decrement for last '\0'. */ - /* Add timestamp "created-at" in the private key */ + /* Hack to insert the timestamp "created-at" into the private key. */ snprintf (keydata+keydatalen-1, 30, KEYTOCARD_TIMESTAMP_FORMAT, timestamp); keydatalen += 10 + 19 - 1; - err = divert_writekey (ctrl, force, serialno, id, keydata, keydatalen); + + err = divert_writekey (ctrl, force, serialno, keyref, keydata, keydatalen); xfree (keydata); leave: + gcry_sexp_release (s_skey); + xfree (shadow_info); return leave_cmd (ctx, err); } @@ -2751,7 +2743,7 @@ cmd_put_secret (assuan_context_t ctx, char *line) * into a string. Instead of resorting to base64 encoding we use a * special percent escaping which only quoted the Nul and the * percent character. */ - string = percent_data_escape (value? value : valstr, valuelen); + string = percent_data_escape (0, NULL, value? value : valstr, valuelen); if (!string) { err = gpg_error_from_syserror (); @@ -3588,8 +3580,13 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd) } else { +#ifdef HAVE_W32_SYSTEM + pid = assuan_get_pid (ctx); + ctrl->client_uid = -1; +#else pid = client_creds->pid; ctrl->client_uid = client_creds->uid; +#endif } ctrl->client_pid = (pid == ASSUAN_INVALID_PID)? 0 : (unsigned long)pid; ctrl->server_local->connect_from_self = (pid == getpid ()); diff --git a/agent/divert-scd.c b/agent/divert-scd.c index b85b490c1..e89c74a19 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -195,7 +195,7 @@ has_percent0A_suffix (const char *string) string with the passphrase, the buffer may optionally be padded with arbitrary characters. - If DESC_TEXT is not NULL it can be used as further informtion shown + If DESC_TEXT is not NULL it can be used as further information shown atop of the INFO message. INFO gets displayed as part of a generic string. However if the @@ -278,25 +278,47 @@ getpin_cb (void *opaque, const char *desc_text, const char *info, { if (info) { - char *desc, *desc2; + char *desc; + const char *desc2; - if ( asprintf (&desc, - L_("%s%%0A%%0AUse the reader's pinpad for input."), - info) < 0 ) - rc = gpg_error_from_syserror (); + if (!strcmp (info, "--ack")) + { + desc2 = L_("Push ACK button on card/token."); + + if (desc_text) + { + desc = strconcat (desc_text, + has_percent0A_suffix (desc_text) + ? "%0A" : "%0A%0A", + desc2, NULL); + desc2 = NULL; + } + else + desc = NULL; + } else { - /* Prepend DESC_TEXT to INFO. */ + desc2 = NULL; + if (desc_text) - desc2 = strconcat (desc_text, - has_percent0A_suffix (desc_text) - ? "%0A" : "%0A%0A", - desc, NULL); + desc = strconcat (desc_text, + has_percent0A_suffix (desc_text) + ? "%0A" : "%0A%0A", + info, "%0A%0A", + L_("Use the reader's pinpad for input."), + NULL); else - desc2 = NULL; + desc = strconcat (info, "%0A%0A", + L_("Use the reader's pinpad for input."), + NULL); + } + + if (!desc2 && !desc) + rc = gpg_error_from_syserror (); + else + { rc = agent_popup_message_start (ctrl, desc2? desc2:desc, NULL); - xfree (desc2); xfree (desc); } } @@ -476,6 +498,7 @@ divert_pkdecrypt (ctrl_t ctrl, const char *desc_text, char *kid; const unsigned char *s; size_t n; + int depth; const unsigned char *ciphertext; size_t ciphertextlen; char *plaintext; @@ -484,7 +507,6 @@ divert_pkdecrypt (ctrl_t ctrl, const char *desc_text, (void)desc_text; *r_padding = -1; - s = cipher; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); @@ -500,6 +522,21 @@ divert_pkdecrypt (ctrl_t ctrl, const char *desc_text, n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); + + /* First check whether we have a flags parameter and skip it. */ + if (smatch (&s, n, "flags")) + { + depth = 1; + if (sskip (&s, &depth) || depth) + return gpg_error (GPG_ERR_INV_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + } + if (smatch (&s, n, "rsa")) { if (*s != '(') @@ -560,12 +597,13 @@ divert_pkdecrypt (ctrl_t ctrl, const char *desc_text, return rc; } -int + +gpg_error_t divert_writekey (ctrl_t ctrl, int force, const char *serialno, - const char *id, const char *keydata, size_t keydatalen) + const char *keyref, const char *keydata, size_t keydatalen) { - return agent_card_writekey (ctrl, force, serialno, id, keydata, keydatalen, - getpin_cb, ctrl); + return agent_card_writekey (ctrl, force, serialno, keyref, + keydata, keydatalen, getpin_cb, ctrl); } int diff --git a/agent/findkey.c b/agent/findkey.c index 78c3b1a47..89a18fa9e 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -632,7 +632,17 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, pi->check_cb_arg = &arg; rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, hexgrip, cache_mode); - if (!rc) + if (rc) + { + if ((pi->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) + { + log_error ("Clearing pinentry cache which caused error %s\n", + gpg_strerror (rc)); + + agent_clear_passphrase (ctrl, hexgrip, cache_mode); + } + } + else { assert (arg.unprotected_key); if (arg.change_required) diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 1fdc94d0f..d9e2bbf25 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -112,6 +112,7 @@ enum cmd_and_opt_values oCheckPassphrasePattern, oMaxPassphraseDays, oEnablePassphraseHistory, + oDisableExtendedKeyFormat, oEnableExtendedKeyFormat, oUseStandardSocket, oNoUseStandardSocket, @@ -135,10 +136,13 @@ enum cmd_and_opt_values oDisableScdaemon, oDisableCheckOwnSocket, oS2KCount, + oS2KCalibration, oAutoExpandSecmem, oListenBacklog, - oWriteEnvFile + oWriteEnvFile, + + oNoop }; @@ -250,9 +254,11 @@ static ARGPARSE_OPTS opts[] = { /* */ "@" #endif ), + ARGPARSE_s_n (oDisableExtendedKeyFormat, "disable-extended-key-format", "@"), ARGPARSE_s_n (oEnableExtendedKeyFormat, "enable-extended-key-format", "@"), ARGPARSE_s_u (oS2KCount, "s2k-count", "@"), + ARGPARSE_s_u (oS2KCalibration, "s2k-calibration", "@"), ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"), @@ -263,6 +269,9 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_s_n (oUseStandardSocket, "use-standard-socket", "@"), ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"), + /* Dummy options. */ + + ARGPARSE_end () /* End of list */ }; @@ -823,7 +832,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) opt.check_passphrase_pattern = NULL; opt.max_passphrase_days = MAX_PASSPHRASE_DAYS; opt.enable_passphrase_history = 0; - opt.enable_extended_key_format = 0; + opt.enable_extended_key_format = 1; opt.ignore_cache_for_signing = 0; opt.allow_mark_trusted = 1; opt.allow_external_cache = 1; @@ -834,6 +843,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) /* Note: When changing the next line, change also gpgconf_list. */ opt.ssh_fingerprint_digest = GCRY_MD_MD5; opt.s2k_count = 0; + set_s2k_calibration_time (0); /* Set to default. */ return 1; } @@ -851,7 +861,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) case oLogFile: if (!reread) - return 0; /* not handeld */ + return 0; /* not handled */ if (!current_logfile || !pargs->r.ret_str || strcmp (current_logfile, pargs->r.ret_str)) { @@ -898,7 +908,11 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) break; case oEnableExtendedKeyFormat: - opt.enable_extended_key_format = 1; + opt.enable_extended_key_format = 2; + break; + case oDisableExtendedKeyFormat: + if (opt.enable_extended_key_format != 2) + opt.enable_extended_key_format = 0; break; case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break; @@ -929,6 +943,12 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) opt.s2k_count = pargs->r.ret_ulong; break; + case oS2KCalibration: + set_s2k_calibration_time (pargs->r.ret_ulong); + break; + + case oNoop: break; + default: return 0; /* not handled */ } @@ -1444,8 +1464,6 @@ main (int argc, char **argv ) GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("pinentry-timeout:%lu:0:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME); - es_printf ("enable-extended-key-format:%lu:\n", - GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("grab:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); @@ -1768,7 +1786,7 @@ main (int argc, char **argv ) /* Unless we are running with a program given on the command * line we can assume that the inotify things works and thus - * we can avoid tye regular stat calls. */ + * we can avoid the regular stat calls. */ if (!argc) reliable_homedir_inotify = 1; } @@ -2108,7 +2126,7 @@ get_agent_scd_notify_event (void) GetCurrentProcess(), &h2, EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) { - log_error ("setting syncronize for scd notify event failed: %s\n", + log_error ("setting synchronize for scd notify event failed: %s\n", w32_strerror (-1) ); CloseHandle (h); } @@ -2370,9 +2388,6 @@ handle_tick (void) if (!last_minute) last_minute = time (NULL); - /* Check whether the scdaemon has died and cleanup in this case. */ - agent_scd_check_aliveness (); - /* If we are running as a child of another process, check whether the parent is still alive and shutdown if not. */ #ifndef HAVE_W32_SYSTEM diff --git a/agent/keyformat.txt b/agent/keyformat.txt index 2e48b346e..c7426db9d 100644 --- a/agent/keyformat.txt +++ b/agent/keyformat.txt @@ -234,7 +234,7 @@ The currently defined protection modes are: (csum n) (protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT))) - Note that the public key paramaters in SKEY are duplicated and + Note that the public key parameters in SKEY are duplicated and should be identical to their copies in the standard parameter elements. Here is an example of an entire protected private key using this format: @@ -359,8 +359,8 @@ KEY_1 to KEY_N are unique identifiers for the shared secret, for example an URI. In case this information should be kept confidential as well, they may not appear in the unprotected part; however they are mandatory in the encrypted_octet_string. The list of keywords is -optional. The oder of the "key" lists and the order of the "value" -lists mut match, that is the first "key"-list is associated with the +optional. The order of the "key" lists and the order of the "value" +lists must match, that is the first "key"-list is associated with the first "value" list in the encrypted_octet_string. The protection mode etc. is identical to the protection mode as diff --git a/agent/learncard.c b/agent/learncard.c index abe1dd0bf..f3219ed8f 100644 --- a/agent/learncard.c +++ b/agent/learncard.c @@ -40,7 +40,7 @@ struct keypair_info_s char hexgrip[1]; /* The keygrip (i.e. a hash over the public key parameters) formatted as a hex string. Allocated somewhat large to also act as - memeory for the above ID field. */ + memory for the above ID field. */ }; typedef struct keypair_info_s *KEYPAIR_INFO; diff --git a/agent/pksign.c b/agent/pksign.c index f54af0817..828e63f58 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -367,20 +367,29 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, if (is_RSA) { + unsigned char *p = buf; + check_signature = 1; - if (*buf & 0x80) + + /* + * Smartcard returns fixed-size data, which is good for + * PKCS1. If variable-size unsigned MPI is needed, remove + * zeros. + */ + if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1 + || ctrl->digest.raw_value) { - len++; - buf = xtryrealloc (buf, len); - if (!buf) - goto leave; + int i; - memmove (buf + 1, buf, len - 1); - *buf = 0; + for (i = 0; i < len - 1; i++) + if (p[i]) + break; + p += i; + len -= i; } err = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%b)))", - (int)len, buf); + (int)len, p); } else if (is_EdDSA) { @@ -389,53 +398,34 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, } else if (is_ECDSA) { - unsigned char *r_buf_allocated = NULL; - unsigned char *s_buf_allocated = NULL; unsigned char *r_buf, *s_buf; int r_buflen, s_buflen; + int i; r_buflen = s_buflen = len/2; - if (*buf & 0x80) - { - r_buflen++; - r_buf_allocated = xtrymalloc (r_buflen); - if (!r_buf_allocated) - { - err = gpg_error_from_syserror (); - goto leave; - } - - r_buf = r_buf_allocated; - memcpy (r_buf + 1, buf, len/2); - *r_buf = 0; - } - else - r_buf = buf; - - if (*(buf + len/2) & 0x80) - { - s_buflen++; - s_buf_allocated = xtrymalloc (s_buflen); - if (!s_buf_allocated) - { - err = gpg_error_from_syserror (); - xfree (r_buf_allocated); - goto leave; - } - - s_buf = s_buf_allocated; - memcpy (s_buf + 1, buf + len/2, len/2); - *s_buf = 0; - } - else - s_buf = buf + len/2; + /* + * Smartcard returns fixed-size data. For ECDSA signature, + * variable-size unsigned MPI is assumed, thus, remove + * zeros. + */ + r_buf = buf; + for (i = 0; i < r_buflen - 1; i++) + if (r_buf[i]) + break; + r_buf += i; + r_buflen -= i; + + s_buf = buf + len/2; + for (i = 0; i < s_buflen - 1; i++) + if (s_buf[i]) + break; + s_buf += i; + s_buflen -= i; err = gcry_sexp_build (&s_sig, NULL, "(sig-val(ecdsa(r%b)(s%b)))", r_buflen, r_buf, s_buflen, s_buf); - xfree (r_buf_allocated); - xfree (s_buf_allocated); } else err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); diff --git a/agent/protect.c b/agent/protect.c index 16ae715e1..61fb8f45d 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -41,6 +41,7 @@ #include "cvt-openpgp.h" #include "../common/sexp-parse.h" +#include "../common/openpgpdefs.h" /* For s2k functions. */ /* The protection mode for encryption. The supported modes for @@ -49,9 +50,6 @@ #define PROT_CIPHER_STRING "aes" #define PROT_CIPHER_KEYLEN (128/8) -/* Decode an rfc4880 encoded S2K count. */ -#define S2K_DECODE_COUNT(_val) ((16ul + ((_val) & 15)) << (((_val) >> 4) + 6)) - /* A table containing the information needed to create a protected private key. */ @@ -71,6 +69,13 @@ static const struct { }; +/* The number of milliseconds we use in the S2K function and the + * calibrated count value. A count value of zero indicates that the + * calibration has not yet been done or needs to be done again. */ +static unsigned int s2k_calibration_time = AGENT_S2K_CALIBRATION; +static unsigned long s2k_calibrated_count; + + /* A helper object for time measurement. */ struct calibrate_time_s { @@ -175,11 +180,11 @@ calibrate_s2k_count (void) ms = calibrate_s2k_count_one (count); if (opt.verbose > 1) log_info ("S2K calibration: %lu -> %lums\n", count, ms); - if (ms > AGENT_S2K_CALIBRATION) + if (ms > s2k_calibration_time) break; } - count = (unsigned long)(((double)count / ms) * AGENT_S2K_CALIBRATION); + count = (unsigned long)(((double)count / ms) * s2k_calibration_time); count /= 1024; count *= 1024; if (count < 65536) @@ -195,18 +200,30 @@ calibrate_s2k_count (void) } +/* Set the calibration time. This may be called early at startup or + * at any time. Thus it should one set variables. */ +void +set_s2k_calibration_time (unsigned int milliseconds) +{ + if (!milliseconds) + milliseconds = AGENT_S2K_CALIBRATION; + else if (milliseconds > 60 * 1000) + milliseconds = 60 * 1000; /* Cap at 60 seconds. */ + s2k_calibration_time = milliseconds; + s2k_calibrated_count = 0; /* Force re-calibration. */ +} + + /* Return the calibrated S2K count. This is only public for the use * of the Assuan getinfo s2k_count_cal command. */ unsigned long get_calibrated_s2k_count (void) { - static unsigned long count; - - if (!count) - count = calibrate_s2k_count (); + if (!s2k_calibrated_count) + s2k_calibrated_count = calibrate_s2k_count (); /* Enforce a lower limit. */ - return count < 65536 ? 65536 : count; + return s2k_calibrated_count < 65536 ? 65536 : s2k_calibrated_count; } @@ -606,7 +623,7 @@ agent_protect (const unsigned char *plainkey, const char *passphrase, int have_curve = 0; if (use_ocb == -1) - use_ocb = opt.enable_extended_key_format; + use_ocb = !!opt.enable_extended_key_format; /* Create an S-expression with the protected-at timestamp. */ memcpy (timestamp_exp, "(12:protected-at15:", 19); @@ -1109,7 +1126,7 @@ agent_unprotect (ctrl_t ctrl, if (!protect_info[infidx].algo) return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); - /* See wether we have a protected-at timestamp. */ + /* See whether we have a protected-at timestamp. */ protect_list = s; /* Save for later. */ if (protected_at) { diff --git a/autogen.rc b/autogen.rc index aa565911a..c80dd4801 100644 --- a/autogen.rc +++ b/autogen.rc @@ -17,25 +17,15 @@ esac case "$myhost" in w32) configure_opts=" - --with-gpg-error-prefix=@SYSROOT@ - --with-ksba-prefix=@SYSROOT@ - --with-libgcrypt-prefix=@SYSROOT@ - --with-libassuan-prefix=@SYSROOT@ --with-zlib=@SYSROOT@ --with-regex=@SYSROOT@ - --with-npth-prefix=@SYSROOT@ --disable-g13 " ;; amd64) configure_opts=" - --with-gpg-error-prefix=@SYSROOT@ - --with-ksba-prefix=@SYSROOT@ - --with-libgcrypt-prefix=@SYSROOT@ - --with-libassuan-prefix=@SYSROOT@ --with-zlib=/usr/x86_64-linux-gnu/usr - --with-pth-prefix=/usr/x86_64-linux-gnu/usr " ;; esac diff --git a/build-aux/speedo.mk b/build-aux/speedo.mk index d9b4a7515..569940099 100644 --- a/build-aux/speedo.mk +++ b/build-aux/speedo.mk @@ -157,8 +157,9 @@ INST_NAME=gnupg-w32 # Use this to override the installaion directory for native builds. INSTALL_PREFIX=none -# The Authenticode key used to sign the Windows installer +# The Authenticode key and cert chain used to sign the Windows installer AUTHENTICODE_KEY=${HOME}/.gnupg/g10code-authenticode-key.p12 +AUTHENTICODE_CERTS=${HOME}/.gnupg/g10code-authenticode-certs.pem # Directory names. @@ -520,12 +521,12 @@ endif # The LDFLAGS is needed for -lintl for glib. ifeq ($(WITH_GUI),1) speedo_pkg_gpgme_configure = \ - --enable-static --enable-w32-glib --disable-w32-qt \ + --enable-static --enable-w32-glib \ --with-gpg-error-prefix=$(idir) \ LDFLAGS=-L$(idir)/lib else speedo_pkg_gpgme_configure = \ - --disable-static --disable-w32-glib --disable-w32-qt \ + --disable-static --disable-w32-glib \ --with-gpg-error-prefix=$(idir) \ LDFLAGS=-L$(idir)/lib endif @@ -970,7 +971,7 @@ else endif @touch $(stampdir)/stamp-$(1)-01-configure -# Note that unpack has no 64 bit version becuase it is just the source. +# Note that unpack has no 64 bit version because it is just the source. # Fixme: We should use templates to create the standard and w64 # version of these rules. $(stampdir)/stamp-w64-$(1)-01-configure: $(stampdir)/stamp-$(1)-00-unpack @@ -1142,7 +1143,7 @@ all-speedo: $(stampdir)/stamp-final report-speedo: $(addprefix report-,$(speedo_build_list)) -# Just to check if we catched all stamps. +# Just to check if we caught all stamps. clean-stamps: $(RM) -fR $(stampdir) @@ -1211,7 +1212,9 @@ extra_installer_options += -DWITH_GUI=1 endif installer: all w32_insthelpers $(w32src)/inst-options.ini $(bdir)/README.txt - $(MAKENSIS) -V2 \ + (nsis3_args=$$(makensis -version | grep -q "^v3" && \ + echo "-INPUTCHARSET CP1252"); \ + $(MAKENSIS) -V2 $$nsis3_args \ -DINST_DIR=$(idir) \ -DINST6_DIR=$(idir6) \ -DBUILD_DIR=$(bdir) \ @@ -1222,7 +1225,7 @@ installer: all w32_insthelpers $(w32src)/inst-options.ini $(bdir)/README.txt -DNAME=$(INST_NAME) \ -DVERSION=$(INST_VERSION) \ -DPROD_VERSION=$(INST_PROD_VERSION) \ - $(extra_installer_options) $(w32src)/inst.nsi + $(extra_installer_options) $(w32src)/inst.nsi) @echo "Ready: $(idir)/$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).exe" @@ -1266,8 +1269,11 @@ sign-installer: echo "speedo: * Signing installer" ;\ echo "speedo: * Key: $(AUTHENTICODE_KEY)";\ echo "speedo: */" ;\ - osslsigncode sign -pkcs12 $(AUTHENTICODE_KEY) -askpass \ - -h sha256 -in "PLAY/inst/$$exefile" -out "../../$$exefile" ;\ + osslsigncode sign -certs $(AUTHENTICODE_CERTS)\ + -pkcs12 $(AUTHENTICODE_KEY) -askpass \ + -ts "http://timestamp.globalsign.com/scripts/timstamp.dll" \ + -h sha256 -n GnuPG -i https://gnupg.org \ + -in "PLAY/inst/$$exefile" -out "../../$$exefile" ;\ exefile="../../$$exefile" ;\ $(call MKSWDB_commands,$${exefile},$${reldate}); \ echo "speedo: /*" ;\ @@ -1283,7 +1289,7 @@ endif # -# Check availibility of standard tools +# Check availability of standard tools # check-tools: diff --git a/build-aux/texinfo.tex b/build-aux/texinfo.tex index 5a17f9793..9e1184868 100644 --- a/build-aux/texinfo.tex +++ b/build-aux/texinfo.tex @@ -3,7 +3,7 @@ % Load plain if necessary, i.e., if running under initex. \expandafter\ifx\csname fmtname\endcsname\relax\input plain\fi % -\def\texinfoversion{2007-05-03.09} +\def\texinfoversion{2018-10-25.16} % % Copyright (C) 1985, 1986, 1988, 1990, 1991, 1992, 1993, 1994, 1995, % 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, @@ -4598,7 +4598,7 @@ end \chardef\maxseclevel = 3 % % A numbered section within an unnumbered changes to unnumbered too. -% To achive this, remember the "biggest" unnum. sec. we are currently in: +% To achieve this, remember the "biggest" unnum. sec. we are currently in: \chardef\unmlevel = \maxseclevel % % Trace whether the current chapter is an appendix or not: diff --git a/common/Makefile.am b/common/Makefile.am index d288fa36b..b6a6605f1 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -83,7 +83,7 @@ common_sources = \ localename.c \ session-env.c session-env.h \ userids.c userids.h \ - openpgp-oid.c \ + openpgp-oid.c openpgp-s2k.c \ ssh-utils.c ssh-utils.h \ agent-opt.c \ helpfile.c \ diff --git a/common/argparse.c b/common/argparse.c index 331998bb2..db0b7e079 100644 --- a/common/argparse.c +++ b/common/argparse.c @@ -408,7 +408,7 @@ static void store_alias( ARGPARSE_ARGS *arg, char *name, char *value ) { /* TODO: replace this dummy function with a rea one - * and fix the probelms IRIX has with (ALIAS_DEV)arg.. + * and fix the problems IRIX has with (ALIAS_DEV)arg.. * used as lvalue */ (void)arg; @@ -439,7 +439,7 @@ ignore_invalid_option_p (ARGPARSE_ARGS *arg, const char *keyword) /* Add the keywords up to the next LF to the list of to be ignored options. After returning FP will either be at EOF or the next - character read wll be the first of a new line. The function + character read will be the first of a new line. The function returns 0 on success or true on malloc failure. */ static int ignore_invalid_option_add (ARGPARSE_ARGS *arg, FILE *fp) @@ -1280,7 +1280,7 @@ long_opt_strlen( ARGPARSE_OPTS *o ) * this option * - a description,ine which starts with a '@' and is followed by * any other characters is printed as is; this may be used for examples - * ans such. + * and such. * - A description which starts with a '|' outputs the string between this * bar and the next one as arguments of the long option. */ diff --git a/common/audit.h b/common/audit.h index 4ef2645da..05f39533d 100644 --- a/common/audit.h +++ b/common/audit.h @@ -185,7 +185,7 @@ typedef enum if no real recipient has been given. */ AUDIT_SESSION_KEY, /* string */ - /* Mark the creation or availibility of the session key. The + /* Mark the creation or availability of the session key. The parameter is the algorithm ID. */ AUDIT_ENCRYPTED_TO, /* cert, err */ diff --git a/common/convert.c b/common/convert.c index 6d03adc3d..40fb4eecf 100644 --- a/common/convert.c +++ b/common/convert.c @@ -177,7 +177,7 @@ bin2hexcolon (const void *buffer, size_t length, char *stringbuf) string or a white space character. The function makes sure that the resulting string in BUFFER is terminated by a Nul byte. Note that the returned string may include embedded Nul bytes; the extra - Nul byte at the end is used to make sure tha the result can always + Nul byte at the end is used to make sure that the result can always be used as a C-string. BUFSIZE is the available length of BUFFER; if the converted result diff --git a/common/dotlock.c b/common/dotlock.c index 5227bb64e..1bc31d8a6 100644 --- a/common/dotlock.c +++ b/common/dotlock.c @@ -140,7 +140,7 @@ you pass (0) instead of (-1) the function does not wait in case the file is already locked but returns -1 and sets ERRNO to EACCES. Any other positive value for the second parameter is considered a - timeout valuie in milliseconds. + timeout value in milliseconds. To release the lock you call: diff --git a/common/exectool.c b/common/exectool.c index 3458de4a7..82b398542 100644 --- a/common/exectool.c +++ b/common/exectool.c @@ -53,6 +53,7 @@ typedef struct exec_tool_status_cb_t status_cb; void *status_cb_value; int cont; + int quiet; size_t used; size_t buffer_size; char *buffer; @@ -110,6 +111,8 @@ read_and_log_stderr (read_and_log_buffer_t *state, es_poll_t *fderr) state->status_cb (state->status_cb_value, state->buffer + 9, rest); } + else if (state->quiet) + ; else if (!state->cont && !strncmp (state->buffer, pname, len) && strlen (state->buffer) > strlen (pname) @@ -331,10 +334,16 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], int count; read_and_log_buffer_t fderrstate; struct copy_buffer *cpbuf_in = NULL, *cpbuf_out = NULL, *cpbuf_extra = NULL; + int quiet = 0; + int dummy_exitcode; memset (fds, 0, sizeof fds); memset (&fderrstate, 0, sizeof fderrstate); + /* If the first argument to the program is "--quiet" avoid all extra + * diagnostics. */ + quiet = (argv && argv[0] && !strcmp (argv[0], "--quiet")); + cpbuf_in = xtrymalloc (sizeof *cpbuf_in); if (cpbuf_in == NULL) { @@ -360,6 +369,7 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], copy_buffer_init (cpbuf_extra); fderrstate.pgmname = pgmname; + fderrstate.quiet = quiet; fderrstate.status_cb = status_cb; fderrstate.status_cb_value = status_cb_value; fderrstate.buffer_size = 256; @@ -375,7 +385,7 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], err = gnupg_create_outbound_pipe (extrapipe, &extrafp, 1); if (err) { - log_error ("error running outbound pipe for extra fp: %s\n", + log_error ("error creating outbound pipe for extra fp: %s\n", gpg_strerror (err)); goto leave; } @@ -411,7 +421,8 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], argv[argsaveidx] = argsave; if (err) { - log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err)); + if (!quiet) + log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } @@ -535,7 +546,7 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], es_fclose (outfp); outfp = NULL; es_fclose (errfp); errfp = NULL; - err = gnupg_wait_process (pgmname, pid, 1, NULL); + err = gnupg_wait_process (pgmname, pid, 1, quiet? &dummy_exitcode : NULL); pid = (pid_t)(-1); leave: @@ -547,7 +558,7 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], es_fclose (outfp); es_fclose (errfp); if (pid != (pid_t)(-1)) - gnupg_wait_process (pgmname, pid, 1, NULL); + gnupg_wait_process (pgmname, pid, 1, quiet? &dummy_exitcode : NULL); gnupg_release_process (pid); copy_buffer_shred (cpbuf_in); diff --git a/common/iobuf.c b/common/iobuf.c index 02c9b491c..05944255f 100644 --- a/common/iobuf.c +++ b/common/iobuf.c @@ -68,8 +68,8 @@ /*-- End configurable part. --*/ -/* The size of the iobuffers. This can be chnages using the - * iobuf_set_buffer_size fucntion. */ +/* The size of the iobuffers. This can be changed using the + * iobuf_set_buffer_size function. */ static unsigned int iobuf_buffer_size = DEFAULT_IOBUF_BUFFER_SIZE; @@ -878,9 +878,9 @@ block_filter (void *opaque, int control, iobuf_t chain, byte * buffer, } else if (c == 255) { - a->size = (size_t)iobuf_get (chain) << 24; - a->size |= iobuf_get (chain) << 16; - a->size |= iobuf_get (chain) << 8; + a->size = iobuf_get_noeof (chain) << 24; + a->size |= iobuf_get_noeof (chain) << 16; + a->size |= iobuf_get_noeof (chain) << 8; if ((c = iobuf_get (chain)) == -1) { log_error ("block_filter: invalid 4 byte length\n"); @@ -2262,6 +2262,7 @@ iobuf_copy (iobuf_t dest, iobuf_t source) size_t nread; size_t nwrote = 0; + size_t max_read = 0; int err; assert (source->use == IOBUF_INPUT || source->use == IOBUF_INPUT_TEMP); @@ -2278,6 +2279,9 @@ iobuf_copy (iobuf_t dest, iobuf_t source) /* EOF. */ break; + if (nread > max_read) + max_read = nread; + err = iobuf_write (dest, temp, nread); if (err) break; @@ -2285,7 +2289,8 @@ iobuf_copy (iobuf_t dest, iobuf_t source) } /* Burn the buffer. */ - wipememory (temp, sizeof (temp)); + if (max_read) + wipememory (temp, max_read); xfree (temp); return nwrote; @@ -2610,12 +2615,50 @@ iobuf_read_line (iobuf_t a, byte ** addr_of_buffer, } p = buffer; - while ((c = iobuf_get (a)) != -1) + while (1) { - *p++ = c; - nbytes++; - if (c == '\n') - break; + if (!a->nofast && a->d.start < a->d.len && nbytes < length - 1) + /* Fast path for finding '\n' by using standard C library's optimized + memchr. */ + { + unsigned size = a->d.len - a->d.start; + byte *newline_pos; + + if (size > length - 1 - nbytes) + size = length - 1 - nbytes; + + newline_pos = memchr (a->d.buf + a->d.start, '\n', size); + if (newline_pos) + { + /* Found newline, copy buffer and return. */ + size = (newline_pos - (a->d.buf + a->d.start)) + 1; + memcpy (p, a->d.buf + a->d.start, size); + p += size; + nbytes += size; + a->d.start += size; + a->nbytes += size; + break; + } + else + { + /* No newline, copy buffer and continue. */ + memcpy (p, a->d.buf + a->d.start, size); + p += size; + nbytes += size; + a->d.start += size; + a->nbytes += size; + } + } + else + { + c = iobuf_readbyte (a); + if (c == -1) + break; + *p++ = c; + nbytes++; + if (c == '\n') + break; + } if (nbytes == length - 1) /* We don't have enough space to add a \n and a \0. Increase diff --git a/common/mbox-util.c b/common/mbox-util.c index 76255ba38..a9086a3f5 100644 --- a/common/mbox-util.c +++ b/common/mbox-util.c @@ -173,11 +173,12 @@ is_valid_mailbox (const char *name) /* Return the mailbox (local-part@domain) form a standard user id. - All plain ASCII characters in the result are converted to - lowercase. Caller must free the result. Returns NULL if no valid - mailbox was found (or we are out of memory). */ + * All plain ASCII characters in the result are converted to + * lowercase. If SUBADDRESS is 1, '+' denoted sub-addresses are not + * included in the result. Caller must free the result. Returns NULL + * if no valid mailbox was found (or we are out of memory). */ char * -mailbox_from_userid (const char *userid) +mailbox_from_userid (const char *userid, int subaddress) { const char *s, *s_end; size_t len; @@ -226,6 +227,29 @@ mailbox_from_userid (const char *userid) else errno = EINVAL; + if (result && subaddress == 1) + { + char *atsign, *plus; + + if ((atsign = strchr (result, '@'))) + { + /* We consider a subaddress only if there is a single '+' + * in the local part and the '+' is not the first or last + * character. */ + *atsign = 0; + if ((plus = strchr (result, '+')) + && !strchr (plus+1, '+') + && result != plus + && plus[1] ) + { + *atsign = '@'; + memmove (plus, atsign, strlen (atsign)+1); + } + else + *atsign = '@'; + } + } + return result? ascii_strlwr (result): NULL; } diff --git a/common/mbox-util.h b/common/mbox-util.h index 7355ceef5..10ff2c4a0 100644 --- a/common/mbox-util.h +++ b/common/mbox-util.h @@ -22,7 +22,7 @@ int has_invalid_email_chars (const void *buffer, size_t length); int is_valid_mailbox (const char *name); int is_valid_mailbox_mem (const void *buffer, size_t length); -char *mailbox_from_userid (const char *userid); +char *mailbox_from_userid (const char *userid, int subaddress); int is_valid_user_id (const char *uid); int is_valid_domain_name (const char *string); diff --git a/common/miscellaneous.c b/common/miscellaneous.c index 0b374e6c8..260552828 100644 --- a/common/miscellaneous.c +++ b/common/miscellaneous.c @@ -328,6 +328,82 @@ make_printable_string (const void *p, size_t n, int delim ) } +/* Decode the C formatted string SRC and return the result in a newly + * allocated buffer. In error returns NULL and sets ERRNO. */ +char * +decode_c_string (const char *src) +{ + char *buffer, *dst; + int val; + + /* The converted string will never be larger than the original + string. */ + buffer = dst = xtrymalloc (strlen (src) + 1); + if (!buffer) + return NULL; + + while (*src) + { + if (*src != '\\') + { + *dst++ = *src++; + continue; + } + +#define DECODE_ONE(_m,_r) case _m: src += 2; *dst++ = _r; break; + + switch (src[1]) + { + DECODE_ONE ('n', '\n'); + DECODE_ONE ('r', '\r'); + DECODE_ONE ('f', '\f'); + DECODE_ONE ('v', '\v'); + DECODE_ONE ('b', '\b'); + DECODE_ONE ('t', '\t'); + DECODE_ONE ('\\', '\\'); + DECODE_ONE ('\'', '\''); + DECODE_ONE ('\"', '\"'); + + case 'x': + val = hextobyte (src+2); + if (val == -1) /* Bad coding, keep as is. */ + { + *dst++ = *src++; + *dst++ = *src++; + if (*src) + *dst++ = *src++; + if (*src) + *dst++ = *src++; + } + else if (!val) + { + /* A binary zero is not representable in a C string thus + * we keep the C-escaping. Note that this will also + * never be larger than the source string. */ + *dst++ = '\\'; + *dst++ = '0'; + src += 4; + } + else + { + *(unsigned char *)dst++ = val; + src += 4; + } + break; + + default: /* Bad coding; keep as is.. */ + *dst++ = *src++; + *dst++ = *src++; + break; + } +#undef DECODE_ONE + } + *dst++ = 0; + + return buffer; +} + + /* Check whether (BUF,LEN) is valid header for an OpenPGP compressed * packet. LEN should be at least 6. */ static int diff --git a/common/mischelp.c b/common/mischelp.c index 75ba60714..81dd501f8 100644 --- a/common/mischelp.c +++ b/common/mischelp.c @@ -49,6 +49,22 @@ #include "mischelp.h" +void +wipememory (void *ptr, size_t len) +{ +#if defined(HAVE_W32_SYSTEM) && defined(SecureZeroMemory) + SecureZeroMemory (ptr, len); +#elif defined(HAVE_EXPLICIT_BZERO) + explicit_bzero (ptr, len); +#else + /* Prevent compiler from optimizing away the call to memset by accessing + memset through volatile pointer. */ + static void *(*volatile memset_ptr)(void *, int, size_t) = (void *)memset; + memset_ptr (ptr, 0, len); +#endif +} + + /* Check whether the files NAME1 and NAME2 are identical. This is for example achieved by comparing the inode numbers of the files. */ int diff --git a/common/mischelp.h b/common/mischelp.h index 18ec96edf..bdee5a443 100644 --- a/common/mischelp.h +++ b/common/mischelp.h @@ -47,15 +47,9 @@ time_t timegm (struct tm *tm); #define DIM(v) (sizeof(v)/sizeof((v)[0])) #define DIMof(type,member) DIM(((type *)0)->member) -/* To avoid that a compiler optimizes certain memset calls away, these - macros may be used instead. */ -#define wipememory2(_ptr,_set,_len) do { \ - volatile char *_vptr=(volatile char *)(_ptr); \ - size_t _vlen=(_len); \ - while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \ - } while(0) -#define wipememory(_ptr,_len) wipememory2(_ptr,0,_len) - +/* To avoid that a compiler optimizes certain memset calls away, + wipememory function may be used instead. */ +void wipememory(void *ptr, size_t len); /* Include hacks which are mainly required for Slowaris. */ #ifdef GNUPG_COMMON_NEED_AFLOCAL diff --git a/common/mkerrors b/common/mkerrors index 138d3c1d1..2a6960ab6 100755 --- a/common/mkerrors +++ b/common/mkerrors @@ -30,7 +30,7 @@ cat <<EOF * gnupg_strerror: * @err: Error code * - * This function returns a textual representaion of the given + * This function returns a textual representation of the given * errorcode. If this is an unknown value, a string with the value * is returned (Beware: it is hold in a static buffer). * diff --git a/common/mkerrtok b/common/mkerrtok index e6310722a..49c10e595 100755 --- a/common/mkerrtok +++ b/common/mkerrtok @@ -26,7 +26,7 @@ cat <<EOF * gnupg_error_token: * @err: Error code * - * This function returns a textual representaion of the given + * This function returns a textual representation of the given * errorcode. If this is an unknown value, a static string is returned. * This function differs from gnupg_strerror that it yields the string * representation of the macro which is never subject to i18n. diff --git a/common/openpgp-oid.c b/common/openpgp-oid.c index d800e7d57..419471870 100644 --- a/common/openpgp-oid.c +++ b/common/openpgp-oid.c @@ -184,48 +184,36 @@ openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi) } -/* Return a malloced string represenation of the OID in the opaque MPI - A. In case of an error NULL is returned and ERRNO is set. */ +/* Return a malloced string representation of the OID in the buffer + * (BUF,LEN). In case of an error NULL is returned and ERRNO is set. + * As per OpenPGP spec the first byte of the buffer is the length of + * the rest; the function performs a consistency check. */ char * -openpgp_oid_to_str (gcry_mpi_t a) +openpgp_oidbuf_to_str (const unsigned char *buf, size_t len) { - const unsigned char *buf; - size_t length; - unsigned int lengthi; char *string, *p; int n = 0; unsigned long val, valmask; valmask = (unsigned long)0xfe << (8 * (sizeof (valmask) - 1)); - - if (!a - || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE) - || !(buf = gcry_mpi_get_opaque (a, &lengthi))) - { - gpg_err_set_errno (EINVAL); - return NULL; - } - - buf = gcry_mpi_get_opaque (a, &lengthi); - length = (lengthi+7)/8; - /* The first bytes gives the length; check consistency. */ - if (!length || buf[0] != length -1) + + if (!len || buf[0] != len -1) { gpg_err_set_errno (EINVAL); return NULL; } /* Skip length byte. */ - length--; + len--; buf++; /* To calculate the length of the string we can safely assume an upper limit of 3 decimal characters per byte. Two extra bytes - account for the special first octect */ - string = p = xtrymalloc (length*(1+3)+2+1); + account for the special first octet */ + string = p = xtrymalloc (len*(1+3)+2+1); if (!string) return NULL; - if (!length) + if (!len) { *p = 0; return string; @@ -237,7 +225,7 @@ openpgp_oid_to_str (gcry_mpi_t a) p += sprintf (p, "1.%d", buf[n]-40); else { val = buf[n] & 0x7f; - while ( (buf[n]&0x80) && ++n < length ) + while ( (buf[n]&0x80) && ++n < len ) { if ( (val & valmask) ) goto badoid; /* Overflow. */ @@ -250,10 +238,10 @@ openpgp_oid_to_str (gcry_mpi_t a) sprintf (p, "2.%lu", val); p += strlen (p); } - for (n++; n < length; n++) + for (n++; n < len; n++) { val = buf[n] & 0x7f; - while ( (buf[n]&0x80) && ++n < length ) + while ( (buf[n]&0x80) && ++n < len ) { if ( (val & valmask) ) goto badoid; /* Overflow. */ @@ -278,6 +266,35 @@ openpgp_oid_to_str (gcry_mpi_t a) } +/* Return a malloced string representation of the OID in the opaque + * MPI A. In case of an error NULL is returned and ERRNO is set. */ +char * +openpgp_oid_to_str (gcry_mpi_t a) +{ + const unsigned char *buf; + unsigned int lengthi; + + if (!a + || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE) + || !(buf = gcry_mpi_get_opaque (a, &lengthi))) + { + gpg_err_set_errno (EINVAL); + return NULL; + } + + buf = gcry_mpi_get_opaque (a, &lengthi); + return openpgp_oidbuf_to_str (buf, (lengthi+7)/8); +} + + +/* Return true if (BUF,LEN) represents the OID for Ed25519. */ +int +openpgp_oidbuf_is_ed25519 (const void *buf, size_t len) +{ + return (buf && len == DIM (oid_ed25519) + && !memcmp (buf, oid_ed25519, DIM (oid_ed25519))); +} + /* Return true if A represents the OID for Ed25519. */ int @@ -285,32 +302,36 @@ openpgp_oid_is_ed25519 (gcry_mpi_t a) { const unsigned char *buf; unsigned int nbits; - size_t n; if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) return 0; buf = gcry_mpi_get_opaque (a, &nbits); - n = (nbits+7)/8; - return (n == DIM (oid_ed25519) - && !memcmp (buf, oid_ed25519, DIM (oid_ed25519))); + return openpgp_oidbuf_is_ed25519 (buf, (nbits+7)/8); } +/* Return true if (BUF,LEN) represents the OID for Curve25519. */ +int +openpgp_oidbuf_is_cv25519 (const void *buf, size_t len) +{ + return (buf && len == DIM (oid_cv25519) + && !memcmp (buf, oid_cv25519, DIM (oid_cv25519))); +} + + +/* Return true if the MPI A represents the OID for Curve25519. */ int openpgp_oid_is_cv25519 (gcry_mpi_t a) { const unsigned char *buf; unsigned int nbits; - size_t n; if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) return 0; buf = gcry_mpi_get_opaque (a, &nbits); - n = (nbits+7)/8; - return (n == DIM (oid_cv25519) - && !memcmp (buf, oid_cv25519, DIM (oid_cv25519))); + return openpgp_oidbuf_is_cv25519 (buf, (nbits+7)/8); } diff --git a/common/openpgp-s2k.c b/common/openpgp-s2k.c new file mode 100644 index 000000000..2b0ba604b --- /dev/null +++ b/common/openpgp-s2k.c @@ -0,0 +1,67 @@ +/* openpgp-s2ks.c - OpenPGP S2K helper functions + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, + * 2005, 2006 Free Software Foundation, Inc. + * Copyright (C) 2010, 2019 g10 Code GmbH + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * or both in parallel, as here. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdlib.h> +#include <errno.h> +#include <ctype.h> +#include <assert.h> + +#include "util.h" +#include "openpgpdefs.h" + + +/* Pack an s2k iteration count into the form specified in RFC-48800. + * If we're in between valid values, round up. */ +unsigned char +encode_s2k_iterations (int iterations) +{ + unsigned char c=0; + unsigned char result; + unsigned int count; + + if (iterations <= 1024) + return 0; /* Command line arg compatibility. */ + + if (iterations >= 65011712) + return 255; + + /* Need count to be in the range 16-31 */ + for (count=iterations>>6; count>=32; count>>=1) + c++; + + result = (c<<4)|(count-16); + + if (S2K_DECODE_COUNT(result) < iterations) + result++; + + return result; +} diff --git a/common/openpgpdefs.h b/common/openpgpdefs.h index 8699a178d..2f7ff456e 100644 --- a/common/openpgpdefs.h +++ b/common/openpgpdefs.h @@ -196,5 +196,19 @@ typedef enum } compress_algo_t; +/* Limits to be used for static arrays. */ +#define OPENPGP_MAX_NPKEY 5 /* Maximum number of public key parameters. */ +#define OPENPGP_MAX_NSKEY 7 /* Maximum number of secret key parameters. */ +#define OPENPGP_MAX_NSIG 2 /* Maximum number of signature parameters. */ +#define OPENPGP_MAX_NENC 2 /* Maximum number of encryption parameters. */ + + +/* Decode an rfc4880 encoded S2K count. */ +#define S2K_DECODE_COUNT(_val) ((16ul + ((_val) & 15)) << (((_val) >> 4) + 6)) + + +/*--openpgp-s2k.c --*/ +unsigned char encode_s2k_iterations (int iterations); + #endif /*GNUPG_COMMON_OPENPGPDEFS_H*/ diff --git a/common/percent.c b/common/percent.c index eeb026fbe..ecc6a1959 100644 --- a/common/percent.c +++ b/common/percent.c @@ -37,16 +37,16 @@ /* Create a newly alloced string from STRING with all spaces and - control characters converted to plus signs or %xx sequences. The - function returns the new string or NULL in case of a malloc - failure. - - Note that we also escape the quote character to work around a bug - in the mingw32 runtime which does not correcty handle command line - quoting. We correctly double the quote mark when calling a program - (i.e. gpg-protect-tool), but the pre-main code does not notice the - double quote as an escaped quote. We do this also on POSIX systems - for consistency. */ + * control characters converted to plus signs or %xx sequences. The + * function returns the new string or NULL in case of a malloc + * failure. + * + * Note that this fucntion also escapes the quote character to work + * around a bug in the mingw32 runtime which does not correctly handle + * command line quoting. We correctly double the quote mark when + * calling a program (i.e. gpg-protect-tool), but the pre-main code + * does not notice the double quote as an escaped quote. We do this + * also on POSIX systems for consistency. */ char * percent_plus_escape (const char *string) { @@ -87,19 +87,36 @@ percent_plus_escape (const char *string) } -/* Create a newly alloced string from (DATA,DATALEN) with embedded - * Nuls quoted as %00. The standard percent unescaping can be - * used to reverse this encoding. */ +/* Create a newly malloced string from (DATA,DATALEN) with embedded + * nuls quoted as %00. The standard percent unescaping can be used to + * reverse this encoding. With PLUS_ESCAPE set plus-escaping (spaces + * are replaced by a '+') and escaping of characters with values less + * than 0x20 is used. If PREFIX is not NULL it will be prepended to + * the output in standard escape format; that is PLUS_ESCAPING is + * ignored for PREFIX. */ char * -percent_data_escape (const void *data, size_t datalen) +percent_data_escape (int plus_escape, const char *prefix, + const void *data, size_t datalen) { char *buffer, *p; - const char *s; - size_t n, length; + const unsigned char *s; + size_t n; + size_t length = 1; + + if (prefix) + { + for (s = prefix; *s; s++) + { + if (*s == '%' || *s < 0x20) + length += 3; + else + length++; + } + } - for (length=1, s=data, n=datalen; n; s++, n--) + for (s=data, n=datalen; n; s++, n--) { - if (!*s || *s == '%') + if (!*s || *s == '%' || (plus_escape && (*s < ' ' || *s == '+'))) length += 3; else length++; @@ -109,6 +126,20 @@ percent_data_escape (const void *data, size_t datalen) if (!buffer) return NULL; + if (prefix) + { + for (s = prefix; *s; s++) + { + if (*s == '%' || *s < 0x20) + { + snprintf (p, 4, "%%%02X", *s); + p += 3; + } + else + *p++ = *s; + } + } + for (s=data, n=datalen; n; s++, n--) { if (!*s) @@ -121,13 +152,21 @@ percent_data_escape (const void *data, size_t datalen) memcpy (p, "%25", 3); p += 3; } + else if (plus_escape && *s == ' ') + { + *p++ = '+'; + } + else if (plus_escape && (*s < ' ' || *s == '+')) + { + snprintf (p, 4, "%%%02X", *s); + p += 3; + } else *p++ = *s; } *p = 0; return buffer; - } diff --git a/common/server-help.c b/common/server-help.c index 53a888a86..e5a69e02d 100644 --- a/common/server-help.c +++ b/common/server-help.c @@ -30,8 +30,22 @@ #include <config.h> #include <string.h> -#include "server-help.h" #include "util.h" +#include "server-help.h" + + +static GPGRT_INLINE gpg_error_t +my_error (int e) +{ + return gpg_err_make (default_errsource, (e)); +} + +static GPGRT_INLINE gpg_error_t +my_error_from_syserror (void) +{ + return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); +} + /* Skip over options in LINE. @@ -114,6 +128,40 @@ has_option_name (const char *line, const char *name) } +/* Parse an option with the format "--NAME=VALUE" which must occur in + * LINE before a double-dash. LINE is written to but not modified by + * this function. If the option is found and has a value the value is + * stored as a malloced string at R_VALUE. If the option was not + * found or an error occurred NULL is stored there. Note that + * currently the value must be a string without any space; we may + * eventually update this function to allow for a quoted value. */ +gpg_error_t +get_option_value (char *line, const char *name, char **r_value) +{ + char *p, *pend; + int c; + + *r_value = NULL; + + p = (char*)has_option_name (line, name); + if (!p || p >= skip_options (line)) + return 0; + + if (*p != '=' || !p[1] || spacep (p+1)) + return my_error (GPG_ERR_INV_ARG); + p++; + for (pend = p; *pend && !spacep (pend); pend++) + ; + c = *pend; + *pend = 0; + *r_value = xtrystrdup (p); + *pend = c; + if (!p) + return my_error_from_syserror (); + return 0; +} + + /* Return a pointer to the argument of the option with NAME. If such an option is not given, NULL is returned. */ char * diff --git a/common/server-help.h b/common/server-help.h index 9e3d7ada1..9d2f4cfba 100644 --- a/common/server-help.h +++ b/common/server-help.h @@ -55,6 +55,14 @@ int has_leading_option (const char *line, const char *name); or a space. */ const char *has_option_name (const char *line, const char *name); +/* Same as has_option_name but ignores all options after a "--" and + * does not return a const char ptr. */ +char *has_leading_option_name (char *line, const char *name); + +/* Parse an option with the format "--NAME=VALUE" and return the value + * as a malloced string. */ +gpg_error_t get_option_value (char *line, const char *name, char **r_value); + /* Return a pointer to the argument of the option with NAME. If such an option is not given, NULL is returned. */ char *option_value (const char *line, const char *name); diff --git a/common/sexp-parse.h b/common/sexp-parse.h index 4f77f1430..0403d65f5 100644 --- a/common/sexp-parse.h +++ b/common/sexp-parse.h @@ -105,7 +105,7 @@ smatch (unsigned char const **buf, size_t buflen, const char *token) } /* Format VALUE for use as the length indicatior of an S-expression. - The caller needs to provide a buffer HELP_BUFFER wth a length of + The caller needs to provide a buffer HELP_BUFFER with a length of HELP_BUFLEN. The return value is a pointer into HELP_BUFFER with the formatted length string. The colon and a trailing nul are appended. HELP_BUFLEN must be at least 3 - a more useful value is diff --git a/common/sexputil.c b/common/sexputil.c index f30790aa1..d3020e169 100644 --- a/common/sexputil.c +++ b/common/sexputil.c @@ -303,7 +303,7 @@ make_simple_sexp_from_hexstr (const char *line, size_t *nscanned) for (; n > 1; n -=2, s += 2) *p++ = xtoi_2 (s); *p++ = ')'; - *p = 0; /* (Not really neaded.) */ + *p = 0; /* (Not really needed.) */ return buf; } @@ -577,3 +577,61 @@ get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen) gcry_sexp_release (sexp); return algo; } + + +/* Given the public key S_PKEY, return a new buffer with a descriptive + * string for its algorithm. This function may return NULL on memory + * error. */ +char * +pubkey_algo_string (gcry_sexp_t s_pkey) +{ + const char *prefix; + gcry_sexp_t l1; + char *algoname; + int algo; + char *result; + + l1 = gcry_sexp_find_token (s_pkey, "public-key", 0); + if (!l1) + return xtrystrdup ("E_no_key"); + { + gcry_sexp_t l_tmp = gcry_sexp_cadr (l1); + gcry_sexp_release (l1); + l1 = l_tmp; + } + algoname = gcry_sexp_nth_string (l1, 0); + gcry_sexp_release (l1); + if (!algoname) + return xtrystrdup ("E_no_algo"); + + algo = gcry_pk_map_name (algoname); + switch (algo) + { + case GCRY_PK_RSA: prefix = "rsa"; break; + case GCRY_PK_ELG: prefix = "elg"; break; + case GCRY_PK_DSA: prefix = "dsa"; break; + case GCRY_PK_ECC: prefix = ""; break; + default: prefix = NULL; break; + } + + if (prefix && *prefix) + result = xtryasprintf ("%s%u", prefix, gcry_pk_get_nbits (s_pkey)); + else if (prefix) + { + const char *curve = gcry_pk_get_curve (s_pkey, 0, NULL); + const char *name = openpgp_oid_to_curve + (openpgp_curve_to_oid (curve, NULL), 0); + + if (name) + result = xtrystrdup (name); + else if (curve) + result = xtryasprintf ("X_%s", curve); + else + result = xtrystrdup ("E_unknown"); + } + else + result = xtryasprintf ("X_algo_%d", algo); + + xfree (algoname); + return result; +} diff --git a/common/simple-pwquery.c b/common/simple-pwquery.c index e7f4af341..7688c846d 100644 --- a/common/simple-pwquery.c +++ b/common/simple-pwquery.c @@ -75,7 +75,7 @@ /* Name of the socket to be used. This is a kludge to keep on using - the existsing code despite that we only support a standard socket. */ + the existing code despite that we only support a standard socket. */ static char *default_gpg_agent_info; @@ -246,6 +246,7 @@ agent_open (assuan_context_t *ctx) #ifdef SPWQ_USE_LOGGING log_error (_("no gpg-agent running in this session\n")); #endif + *ctx = NULL; return SPWQ_NO_AGENT; } diff --git a/common/ssh-utils.c b/common/ssh-utils.c index 38d6e8aa2..013b28e5b 100644 --- a/common/ssh-utils.c +++ b/common/ssh-utils.c @@ -247,7 +247,7 @@ get_fingerprint (gcry_sexp_t key, int algo, goto leave; } - strncpy (*r_fpr, algo_name, strlen (algo_name)); + memcpy (*r_fpr, algo_name, strlen (algo_name)); fpr = (char *) *r_fpr + strlen (algo_name); *fpr++ = ':'; diff --git a/common/status.c b/common/status.c index 50afce496..269ffea5d 100644 --- a/common/status.c +++ b/common/status.c @@ -34,6 +34,10 @@ #include "status.h" #include "status-codes.h" +/* The stream to output the status information. Output is disabled if + * this is NULL. */ +static estream_t statusfp; + /* Return the status string for code NO. */ const char * @@ -47,6 +51,60 @@ get_status_string ( int no ) } +/* Set a global status FD. */ +void +gnupg_set_status_fd (int fd) +{ + static int last_fd = -1; + + if (fd != -1 && last_fd == fd) + return; + + if (statusfp && statusfp != es_stdout && statusfp != es_stderr) + es_fclose (statusfp); + statusfp = NULL; + if (fd == -1) + return; + + if (fd == 1) + statusfp = es_stdout; + else if (fd == 2) + statusfp = es_stderr; + else + statusfp = es_fdopen (fd, "w"); + if (!statusfp) + { + log_fatal ("can't open fd %d for status output: %s\n", + fd, gpg_strerror (gpg_error_from_syserror ())); + } + last_fd = fd; +} + + +/* Write a status line with code NO followed by the output of the + * printf style FORMAT. The caller needs to make sure that LFs and + * CRs are not printed. */ +void +gnupg_status_printf (int no, const char *format, ...) +{ + va_list arg_ptr; + + if (!statusfp) + return; /* Not enabled. */ + + es_fputs ("[GNUPG:] ", statusfp); + es_fputs (get_status_string (no), statusfp); + if (format) + { + es_putc (' ', statusfp); + va_start (arg_ptr, format); + es_vfprintf (statusfp, format, arg_ptr); + va_end (arg_ptr); + } + es_putc ('\n', statusfp); +} + + const char * get_inv_recpsgnr_code (gpg_error_t err) { diff --git a/common/status.h b/common/status.h index dc62f3629..aeab54202 100644 --- a/common/status.h +++ b/common/status.h @@ -163,6 +163,10 @@ enum const char *get_status_string (int code); +void gnupg_set_status_fd (int fd); +void gnupg_status_printf (int no, const char *format, + ...) GPGRT_ATTR_PRINTF(2,3); + const char *get_inv_recpsgnr_code (gpg_error_t err); diff --git a/common/stringhelp.c b/common/stringhelp.c index 0abac8ae5..dd1711684 100644 --- a/common/stringhelp.c +++ b/common/stringhelp.c @@ -810,6 +810,19 @@ ascii_strlwr (char *s) return s; } +/* Upcase all ASCII characters in S. */ +char * +ascii_strupr (char *s) +{ + char *p = s; + + for (p=s; *p; p++ ) + if (isascii (*p) && *p >= 'a' && *p <= 'z') + *p &= ~0x20; + + return s; +} + int ascii_strcasecmp( const char *a, const char *b ) { @@ -1400,7 +1413,7 @@ parse_version_number (const char *s, int *number) /* This function breaks up the complete string-representation of the - version number S, which is of the following struture: <major + version number S, which is of the following structure: <major number>.<minor number>[.<micro number>]<patch level>. The major, minor, and micro number components will be stored in *MAJOR, *MINOR and *MICRO. If MICRO is not given 0 is used instead. diff --git a/common/stringhelp.h b/common/stringhelp.h index 5b07af95e..7df6c7656 100644 --- a/common/stringhelp.h +++ b/common/stringhelp.h @@ -76,6 +76,7 @@ int ascii_islower (int c); int ascii_toupper (int c); int ascii_tolower (int c); char *ascii_strlwr (char *s); +char *ascii_strupr (char *s); int ascii_strcasecmp( const char *a, const char *b ); int ascii_strncasecmp (const char *a, const char *b, size_t n); int ascii_memcasecmp( const void *a, const void *b, size_t n ); diff --git a/common/sysutils.c b/common/sysutils.c index 55a7ee9ec..0a3dc2eaf 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -551,14 +551,13 @@ gnupg_tmpfile (void) void gnupg_reopen_std (const char *pgmname) { -#if defined(HAVE_STAT) && !defined(HAVE_W32_SYSTEM) - struct stat statbuf; +#ifdef F_GETFD int did_stdin = 0; int did_stdout = 0; int did_stderr = 0; FILE *complain; - if (fstat (STDIN_FILENO, &statbuf) == -1 && errno ==EBADF) + if (fcntl (STDIN_FILENO, F_GETFD) == -1 && errno ==EBADF) { if (open ("/dev/null",O_RDONLY) == STDIN_FILENO) did_stdin = 1; @@ -566,7 +565,7 @@ gnupg_reopen_std (const char *pgmname) did_stdin = 2; } - if (fstat (STDOUT_FILENO, &statbuf) == -1 && errno == EBADF) + if (fcntl (STDOUT_FILENO, F_GETFD) == -1 && errno == EBADF) { if (open ("/dev/null",O_WRONLY) == STDOUT_FILENO) did_stdout = 1; @@ -574,7 +573,7 @@ gnupg_reopen_std (const char *pgmname) did_stdout = 2; } - if (fstat (STDERR_FILENO, &statbuf)==-1 && errno==EBADF) + if (fcntl (STDERR_FILENO, F_GETFD)==-1 && errno==EBADF) { if (open ("/dev/null", O_WRONLY) == STDERR_FILENO) did_stderr = 1; @@ -607,7 +606,7 @@ gnupg_reopen_std (const char *pgmname) if (did_stdin == 2 || did_stdout == 2 || did_stderr == 2) exit (3); -#else /* !(HAVE_STAT && !HAVE_W32_SYSTEM) */ +#else /* !F_GETFD */ (void)pgmname; #endif } diff --git a/common/t-exechelp.c b/common/t-exechelp.c index cf967fcc7..3bf082bbb 100644 --- a/common/t-exechelp.c +++ b/common/t-exechelp.c @@ -131,7 +131,7 @@ test_close_all_fds (void) free (array); /* Now let's check the realloc we use. We do this and the next - tests only if we are allowed to open enought descriptors. */ + tests only if we are allowed to open enough descriptors. */ if (get_max_fds () > 32) { int except[] = { 20, 23, 24, -1 }; diff --git a/common/t-mbox-util.c b/common/t-mbox-util.c index fb1ac12e0..ae717f96f 100644 --- a/common/t-mbox-util.c +++ b/common/t-mbox-util.c @@ -25,6 +25,9 @@ #include "util.h" #include "mbox-util.h" +#define PGM "t-mbox-util" + + #define pass() do { ; } while(0) #define fail(a) do { fprintf (stderr, "%s:%d: test %d failed\n",\ __FILE__,__LINE__, (a)); \ @@ -32,6 +35,10 @@ } while(0) +static int verbose; +static int debug; + + static void run_mbox_test (void) { @@ -76,7 +83,86 @@ run_mbox_test (void) for (idx=0; testtbl[idx].userid; idx++) { - char *mbox = mailbox_from_userid (testtbl[idx].userid); + char *mbox = mailbox_from_userid (testtbl[idx].userid, 0); + + if (!testtbl[idx].mbox) + { + if (mbox) + fail (idx); + } + else if (!mbox) + fail (idx); + else if (strcmp (mbox, testtbl[idx].mbox)) + fail (idx); + + xfree (mbox); + } +} + + +static void +run_mbox_no_sub_test (void) +{ + static struct + { + const char *userid; + const char *mbox; + } testtbl[] = + { + { "[email protected]", "[email protected]" }, + { "Werner Koch <[email protected]>", "[email protected]" }, + { "<[email protected]>", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected] ", NULL }, + { " [email protected]", NULL }, + { "Werner Koch (test) <[email protected]>", "[email protected]" }, + { "Werner Koch <[email protected]> (test)", "[email protected]" }, + { "Werner Koch <[email protected] (test)", NULL }, + { "Werner Koch <[email protected] >", NULL }, + { "Werner Koch <[email protected]", NULL }, + { "", NULL }, + { "@", NULL }, + { "bar <>", NULL }, + { "<[email protected]>", "[email protected]" }, + { "<[email protected]>", "[email protected]" }, + { "<[email protected]>", "[email protected]" }, + { "<[email protected]>", "[email protected]" }, + { "<[email protected]>", "[email protected]" }, + { "<[email protected].>", NULL }, + { "<[email protected]>", NULL }, + { "<foo@.>", NULL }, + { "<@example.org>", NULL }, + { "<foo@@example.org>", NULL }, + { "<@[email protected]>", NULL }, + { "<[email protected]> ()", "[email protected]" }, + { "<fo()[email protected]> ()", "fo()[email protected]" }, + { "<fo()[email protected]> ()", "fo()[email protected]" }, + { "fo()[email protected]", NULL}, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + { "[email protected]", "[email protected]" }, + + { NULL, NULL } + }; + int idx; + + for (idx=0; testtbl[idx].userid; idx++) + { + char *mbox = mailbox_from_userid (testtbl[idx].userid, 1); if (!testtbl[idx].mbox) { @@ -143,14 +229,105 @@ run_dns_test (void) } +static void +run_filter (int no_sub) +{ + char buf[4096]; + int c; + char *p, *mbox; + unsigned int count1 = 0; + unsigned int count2 = 0; + + while (fgets (buf, sizeof buf, stdin)) + { + p = strchr (buf, '\n'); + if (p) + *p = 0; + else + { + /* Skip to the end of the line. */ + while ((c = getc (stdin)) != EOF && c != '\n') + ; + } + count1++; + trim_spaces (buf); + mbox = mailbox_from_userid (buf, no_sub); + if (mbox) + { + printf ("%s\n", mbox); + xfree (mbox); + count2++; + } + } + if (verbose) + fprintf (stderr, PGM ": lines=%u mboxes=%u\n", count1, count2); +} + + int main (int argc, char **argv) { - (void)argc; - (void)argv; + int last_argc = -1; + int opt_filter = 0; + int opt_no_sub = 0; - run_mbox_test (); - run_dns_test (); + if (argc) + { argc--; argv++; } + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--")) + { + argc--; argv++; + break; + } + else if (!strcmp (*argv, "--help")) + { + fputs ("usage: " PGM " [FILE]\n" + "Options:\n" + " --verbose Print timings etc.\n" + " --debug Flyswatter\n" + " --filter Filter mboxes from input lines\n" + " --no-sub Ignore '+'-sub-addresses\n" + , stdout); + exit (0); + } + else if (!strcmp (*argv, "--verbose")) + { + verbose++; + argc--; argv++; + } + else if (!strcmp (*argv, "--debug")) + { + verbose += 2; + debug++; + argc--; argv++; + } + else if (!strcmp (*argv, "--filter")) + { + opt_filter = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--no-sub")) + { + opt_no_sub = 1; + argc--; argv++; + } + else if (!strncmp (*argv, "--", 2)) + { + fprintf (stderr, PGM ": unknown option '%s'\n", *argv); + exit (1); + } + } + + if (opt_filter) + run_filter (opt_no_sub); + else + { + run_mbox_test (); + run_mbox_no_sub_test (); + run_dns_test (); + } return 0; } diff --git a/common/t-openpgp-oid.c b/common/t-openpgp-oid.c index cb5709d98..fd9de5dde 100644 --- a/common/t-openpgp-oid.c +++ b/common/t-openpgp-oid.c @@ -142,7 +142,15 @@ test_openpgp_oid_to_str (void) fail (idx, 0); xfree (string); gcry_mpi_release (a); - } + + /* Again using the buffer variant. */ + string = openpgp_oidbuf_to_str (samples[idx].der, samples[idx].der[0]+1); + if (!string) + fail (idx, gpg_error_from_syserror ()); + if (strcmp (string, samples[idx].string)) + fail (idx, 0); + xfree (string); +} } diff --git a/common/t-percent.c b/common/t-percent.c index 94ece9249..774fa80ee 100644 --- a/common/t-percent.c +++ b/common/t-percent.c @@ -103,25 +103,182 @@ static void test_percent_data_escape (void) { static struct { + const char *prefix; const char *data; size_t datalen; const char *expect; } tbl[] = { { + NULL, "", 0, "" }, { + NULL, "a", 1, "a", }, { + NULL, "%22", 3, "%2522" }, { + NULL, "%%", 3, "%25%25%00" }, { + NULL, "\n \0BC\t", 6, "\n %00BC\t" + }, { + "", + "", 0, + "" + }, { + "", + "a", 1, + "a", + }, { + "", + "%22", 3, + "%2522" + }, { + "", + "%%", 3, + "%25%25%00" + }, { + "", + "\n \0BC\t", 6, + "\n %00BC\t" + }, { + "a", + "", 0, + "a" + }, { + "a", + "a", 1, + "aa", + }, { + "a", + "%22", 3, + "a%2522" + }, { + "a", + "%%", 3, + "a%25%25%00" + }, { + "a", + "\n \0BC\t", 6, + "a\n %00BC\t" + }, { + " ", + "%%", 3, + " %25%25%00" + }, { + "+", + "%%", 3, + "+%25%25%00" + }, { + "%", + "%%", 3, + "%25%25%25%00" + }, { + "a b", + "%%", 3, + "a b%25%25%00" + }, { + "a%2Bb", + "%%", 3, + "a%252Bb%25%25%00" + }, { + "\n", + "%%", 3, + "%0A%25%25%00" + }, { + NULL, + NULL, 0, + NULL } + }; + char *buf; + int i; + size_t len, prefixlen; + + for (i=0; tbl[i].data; i++) + { + buf = percent_data_escape (0, tbl[i].prefix, tbl[i].data, tbl[i].datalen); + if (!buf) + { + fprintf (stderr, "out of core: %s\n", strerror (errno)); + exit (2); + } + if (strcmp (buf, tbl[i].expect)) + { + fail (i); + } + len = percent_plus_unescape_inplace (buf, 0); + prefixlen = tbl[i].prefix? strlen (tbl[i].prefix) : 0; + if (len != tbl[i].datalen + prefixlen) + fail (i); + else if (tbl[i].prefix && memcmp (buf, tbl[i].prefix, prefixlen) + && !(prefixlen == 1 && *tbl[i].prefix == '+' && *buf == ' ')) + { + /* Note extra condition above handles the one test case + * which reverts a plus to a space due to the use of the + * plus-unescape fucntion also for the prefix part. */ + fail (i); + } + else if (memcmp (buf+prefixlen, tbl[i].data, tbl[i].datalen)) + { + fail (i); + } + xfree (buf); + } +} + + + +static void +test_percent_data_escape_plus (void) +{ + static struct { + const char *data; + size_t datalen; + const char *expect; + } tbl[] = { + { + "", 0, + "" + }, { + "a", 1, + "a", + }, { + "%22", 3, + "%2522" + }, { + "%%", 3, + "%25%25%00" + }, { + "\n \0BC\t", 6, + "%0A+%00BC%09" + }, { + " ", 1, + "+" + }, { + " ", 2, + "++" + }, { + "+ +", 3, + "%2B+%2B" + }, { + "\" \"", 3, /* Note: This function does not escape quotes. */ + "\"+\"" + }, { + "%22", 3, + "%2522" + }, { + "%% ", 3, + "%25%25+" + }, { + "\n ABC\t", 6, + "%0A+ABC%09" }, { NULL, 0, NULL } }; char *buf; @@ -130,14 +287,16 @@ test_percent_data_escape (void) for (i=0; tbl[i].data; i++) { - buf = percent_data_escape (tbl[i].data, tbl[i].datalen); + buf = percent_data_escape (1, NULL, tbl[i].data, tbl[i].datalen); if (!buf) { fprintf (stderr, "out of core: %s\n", strerror (errno)); exit (2); } if (strcmp (buf, tbl[i].expect)) - fail (i); + { + fail (i); + } len = percent_plus_unescape_inplace (buf, 0); if (len != tbl[i].datalen) fail (i); @@ -148,16 +307,15 @@ test_percent_data_escape (void) } - int main (int argc, char **argv) { (void)argc; (void)argv; - /* FIXME: We escape_unescape is not tested - only - percent_plus_unescape. */ + /* FIXME: escape_unescape is not tested - only percent_plus_unescape. */ test_percent_plus_escape (); test_percent_data_escape (); + test_percent_data_escape_plus (); return 0; } diff --git a/common/ttyio.c b/common/ttyio.c index c7c9d85ab..4c095bc03 100644 --- a/common/ttyio.c +++ b/common/ttyio.c @@ -404,166 +404,192 @@ tty_print_utf8_string( const byte *p, size_t n ) static char * do_get( const char *prompt, int hidden ) { - char *buf; + char *buf; #ifndef __riscos__ - byte cbuf[1]; + byte cbuf[1]; #endif - int c, n, i; + int c, n, i; - if( batchmode ) { - log_error("Sorry, we are in batchmode - can't get input\n"); - exit(2); + if (batchmode) + { + log_error ("Sorry, we are in batchmode - can't get input\n"); + exit (2); } - if (no_terminal) { - log_error("Sorry, no terminal at all requested - can't get input\n"); - exit(2); + if (no_terminal) + { + log_error ("Sorry, no terminal at all requested - can't get input\n"); + exit (2); } - if( !initialized ) - init_ttyfp(); + if (!initialized) + init_ttyfp (); - last_prompt_len = 0; - tty_printf( "%s", prompt ); - buf = xmalloc((n=50)); - i = 0; + last_prompt_len = 0; + tty_printf ("%s", prompt); + buf = xmalloc ((n=50)); + i = 0; #ifdef USE_W32_CONSOLE - if( hidden ) - SetConsoleMode(con.in, HID_INPMODE ); - - for(;;) { - DWORD nread; + if (hidden) + SetConsoleMode(con.in, HID_INPMODE ); - if( !ReadConsoleA( con.in, cbuf, 1, &nread, NULL ) ) - log_fatal("ReadConsole failed: rc=%d", (int)GetLastError() ); - if( !nread ) - continue; - if( *cbuf == '\n' ) - break; - - if( !hidden ) - last_prompt_len++; - c = *cbuf; - if( c == '\t' ) - c = ' '; - else if( c > 0xa0 ) - ; /* we don't allow 0xa0, as this is a protected blank which may - * confuse the user */ - else if( iscntrl(c) ) - continue; - if( !(i < n-1) ) { - n += 50; - buf = xrealloc (buf, n); - } - buf[i++] = c; + for (;;) + { + DWORD nread; + + if (!ReadConsoleA( con.in, cbuf, 1, &nread, NULL)) + log_fatal ("ReadConsole failed: rc=%d", (int)GetLastError ()); + if (!nread) + continue; + if (*cbuf == '\n') + break; + + if (!hidden) + last_prompt_len++; + c = *cbuf; + if (c == '\t') + c = ' '; + else if ( (c >= 0 && c <= 0x1f) || c == 0x7f) + continue; + if (!(i < n-1)) + { + n += 50; + buf = xrealloc (buf, n); + } + buf[i++] = c; } - if( hidden ) - SetConsoleMode(con.in, DEF_INPMODE ); + if (hidden) + SetConsoleMode(con.in, DEF_INPMODE ); #elif defined(__riscos__) || defined(HAVE_W32CE_SYSTEM) - do { + do + { #ifdef HAVE_W32CE_SYSTEM /* Using getchar is not a correct solution but for now it doesn't matter because we have no real console at all. We should rework this as soon as we have switched this entire module to estream. */ - c = getchar(); + c = getchar(); #else - c = riscos_getchar(); + c = riscos_getchar(); #endif - if (c == 0xa || c == 0xd) { /* Return || Enter */ - c = (int) '\n'; - } else if (c == 0x8 || c == 0x7f) { /* Backspace || Delete */ - if (i>0) { - i--; - if (!hidden) { - last_prompt_len--; - fputc(8, ttyfp); - fputc(32, ttyfp); - fputc(8, ttyfp); - fflush(ttyfp); + if (c == 0xa || c == 0xd) /* Return || Enter */ + { + c = (int) '\n'; + } + else if (c == 0x8 || c == 0x7f) /* Backspace || Delete */ + { + if (i>0) + { + i--; + if (!hidden) + { + last_prompt_len--; + fputc(8, ttyfp); + fputc(32, ttyfp); + fputc(8, ttyfp); + fflush(ttyfp); } - } else { - fputc(7, ttyfp); - fflush(ttyfp); } - continue; - } else if (c == (int) '\t') { /* Tab */ - c = ' '; - } else if (c > 0xa0) { - ; /* we don't allow 0xa0, as this is a protected blank which may - * confuse the user */ - } else if (iscntrl(c)) { - continue; + else + { + fputc(7, ttyfp); + fflush(ttyfp); + } + continue; + } + else if (c == (int) '\t') /* Tab */ + { + c = ' '; + } + else if (c > 0xa0) + { + ; /* we don't allow 0xa0, as this is a protected blank which may + * confuse the user */ + } + else if (iscntrl(c)) + { + continue; } - if(!(i < n-1)) { - n += 50; - buf = xrealloc (buf, n); + if (!(i < n-1)) + { + n += 50; + buf = xrealloc (buf, n); } - buf[i++] = c; - if (!hidden) { - last_prompt_len++; - fputc(c, ttyfp); - fflush(ttyfp); + buf[i++] = c; + if (!hidden) + { + last_prompt_len++; + fputc(c, ttyfp); + fflush(ttyfp); } - } while (c != '\n'); - i = (i>0) ? i-1 : 0; + } + while (c != '\n'); + i = (i>0) ? i-1 : 0; + #else /* Other systems. */ - if( hidden ) { + + if (hidden) + { #ifdef HAVE_TCGETATTR - struct termios term; - - if( tcgetattr(fileno(ttyfp), &termsave) ) - log_fatal("tcgetattr() failed: %s\n", strerror(errno) ); - restore_termios = 1; - term = termsave; - term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); - if( tcsetattr( fileno(ttyfp), TCSAFLUSH, &term ) ) - log_fatal("tcsetattr() failed: %s\n", strerror(errno) ); + struct termios term; + + if (tcgetattr(fileno(ttyfp), &termsave)) + log_fatal("tcgetattr() failed: %s\n", strerror(errno)); + restore_termios = 1; + term = termsave; + term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + if (tcsetattr( fileno(ttyfp), TCSAFLUSH, &term ) ) + log_fatal("tcsetattr() failed: %s\n", strerror(errno)); #endif } - /* fixme: How can we avoid that the \n is echoed w/o disabling - * canonical mode - w/o this kill_prompt can't work */ - while( read(fileno(ttyfp), cbuf, 1) == 1 && *cbuf != '\n' ) { - if( !hidden ) - last_prompt_len++; - c = *cbuf; - if( c == CONTROL_D ) - log_info("control d found\n"); - if( c == '\t' ) - c = ' '; - else if( c > 0xa0 ) - ; /* we don't allow 0xa0, as this is a protected blank which may - * confuse the user */ - else if( iscntrl(c) ) - continue; - if( !(i < n-1) ) { - n += 50; - buf = xrealloc (buf, n ); - } - buf[i++] = c; + /* fixme: How can we avoid that the \n is echoed w/o disabling + * canonical mode - w/o this kill_prompt can't work */ + while (read(fileno(ttyfp), cbuf, 1) == 1 && *cbuf != '\n') + { + if (!hidden) + last_prompt_len++; + c = *cbuf; + if (c == CONTROL_D) + log_info ("Control-D detected\n"); + + if (c == '\t') /* Map tab to a space. */ + c = ' '; + else if ( (c >= 0 && c <= 0x1f) || c == 0x7f) + continue; /* Skip all other ASCII control characters. */ + if (!(i < n-1)) + { + n += 50; + buf = xrealloc (buf, n); + } + buf[i++] = c; } - if( *cbuf != '\n' ) { - buf[0] = CONTROL_D; - i = 1; + if (*cbuf != '\n') + { + buf[0] = CONTROL_D; + i = 1; } - if( hidden ) { + if (hidden) + { #ifdef HAVE_TCGETATTR - if( tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave) ) - log_error("tcsetattr() failed: %s\n", strerror(errno) ); - restore_termios = 0; + if (tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave)) + log_error ("tcsetattr() failed: %s\n", strerror(errno)); + restore_termios = 0; #endif } #endif /* end unix version */ - buf[i] = 0; - return buf; + + buf[i] = 0; + return buf; } + +/* Note: This function never returns NULL. */ char * tty_get( const char *prompt ) { diff --git a/common/userids.c b/common/userids.c index 01f2cd84b..181b48866 100644 --- a/common/userids.c +++ b/common/userids.c @@ -226,14 +226,15 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) goto out; } } - if (i != 32 && i != 40) + if (i != 32 && i != 40 && i != 64) { rc = gpg_error (GPG_ERR_INV_USER_ID); /* Invalid length of fpr. */ goto out; } for (i=0,si=s; si < se; i++, si +=2) desc->u.fpr[i] = hextobyte(si); - for (; i < 20; i++) + desc->fprlen = i; + for (; i < 32; i++) desc->u.fpr[i]= 0; mode = KEYDB_SEARCH_MODE_FPR; } @@ -326,14 +327,17 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) } desc->u.fpr[i] = c; } - mode = KEYDB_SEARCH_MODE_FPR16; + desc->fprlen = 16; + for (; i < 32; i++) + desc->u.fpr[i]= 0; + mode = KEYDB_SEARCH_MODE_FPR; } else if ((hexlength == 40 && (s[hexlength] == 0 || (s[hexlength] == '!' && s[hexlength + 1] == 0))) || (!hexprefix && hexlength == 41 && *s == '0')) { - /* SHA1/RMD160 fingerprint. */ + /* SHA1 fingerprint. */ int i; if (hexlength == 41) s++; @@ -347,7 +351,32 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) } desc->u.fpr[i] = c; } - mode = KEYDB_SEARCH_MODE_FPR20; + desc->fprlen = 20; + for (; i < 32; i++) + desc->u.fpr[i]= 0; + mode = KEYDB_SEARCH_MODE_FPR; + } + else if ((hexlength == 64 + && (s[hexlength] == 0 + || (s[hexlength] == '!' && s[hexlength + 1] == 0))) + || (!hexprefix && hexlength == 65 && *s == '0')) + { + /* SHA256 fingerprint. */ + int i; + if (hexlength == 65) + s++; + for (i=0; i < 32; i++, s+=2) + { + int c = hextobyte(s); + if (c == -1) + { + rc = gpg_error (GPG_ERR_INV_USER_ID); + goto out; + } + desc->u.fpr[i] = c; + } + desc->fprlen = 32; + mode = KEYDB_SEARCH_MODE_FPR; } else if (!hexprefix) { @@ -367,15 +396,21 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) desc->u.fpr[i] = c; } if (i == 20) - mode = KEYDB_SEARCH_MODE_FPR20; + { + desc->fprlen = 20; + mode = KEYDB_SEARCH_MODE_FPR; + } + for (; i < 32; i++) + desc->u.fpr[i]= 0; } if (!mode) { /* Still not found. Now check for a space separated - OpenPGP v4 fingerprint like: - 8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367 - or - 8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367 + * OpenPGP v4 fingerprint like: + * 8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367 + * or + * 8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367 + * FIXME: Support OpenPGP v5 fingerprint */ hexlength = strspn (s, " 0123456789abcdefABCDEF"); if (s[hexlength] && s[hexlength] != ' ') @@ -409,7 +444,12 @@ classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) s += 2; } if (i == 20) - mode = KEYDB_SEARCH_MODE_FPR20; + { + desc->fprlen = 20; + mode = KEYDB_SEARCH_MODE_FPR; + } + for (; i < 32; i++) + desc->u.fpr[i]= 0; } } if (!mode) /* Default to substring search. */ diff --git a/common/util.h b/common/util.h index 682415d92..8895137ec 100644 --- a/common/util.h +++ b/common/util.h @@ -39,7 +39,10 @@ * libgpg-error version. Define them here. * Example: (#if GPG_ERROR_VERSION_NUMBER < 0x011500 // 1.21) */ - +#if GPG_ERROR_VERSION_NUMBER < 0x012400 /* 1.36 */ +#define GPG_ERR_NO_AUTH 314 +#define GPG_ERR_BAD_AUTH 315 +#endif /*GPG_ERROR_VERSION_NUMBER*/ /* Hash function used with libksba. */ #define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write) @@ -189,6 +192,7 @@ gpg_error_t get_rsa_pk_from_canon_sexp (const unsigned char *keydata, int get_pk_algo_from_key (gcry_sexp_t key); int get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen); +char *pubkey_algo_string (gcry_sexp_t s_pkey); /*-- convert.c --*/ int hex2bin (const char *string, void *buffer, size_t length); @@ -201,7 +205,8 @@ char *hex2str_alloc (const char *hexstring, size_t *r_count); /*-- percent.c --*/ char *percent_plus_escape (const char *string); -char *percent_data_escape (const void *data, size_t datalen); +char *percent_data_escape (int plus, const char *prefix, + const void *data, size_t datalen); char *percent_plus_unescape (const char *string, int nulrepl); char *percent_unescape (const char *string, int nulrepl); @@ -210,8 +215,11 @@ size_t percent_unescape_inplace (char *string, int nulrepl); /*-- openpgp-oid.c --*/ gpg_error_t openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi); +char *openpgp_oidbuf_to_str (const unsigned char *buf, size_t len); char *openpgp_oid_to_str (gcry_mpi_t a); +int openpgp_oidbuf_is_ed25519 (const void *buf, size_t len); int openpgp_oid_is_ed25519 (gcry_mpi_t a); +int openpgp_oidbuf_is_cv25519 (const void *buf, size_t len); int openpgp_oid_is_cv25519 (gcry_mpi_t a); const char *openpgp_curve_to_oid (const char *name, unsigned int *r_nbits); const char *openpgp_oid_to_curve (const char *oid, int canon); @@ -258,6 +266,13 @@ void gnupg_module_name_flush_some (void); void gnupg_set_builddir (const char *newdir); +/* A list of constants to identify protocols. This is used by tools + * which need to distinguish between the different protocols + * implemented by GnuPG. May be used as bit flags. */ +#define GNUPG_PROTOCOL_OPENPGP 1 /* The one and only (gpg). */ +#define GNUPG_PROTOCOL_CMS 2 /* The core of S/MIME (gpgsm) */ +#define GNUPG_PROTOCOL_SSH_AGENT 4 /* Out ssh-agent implementation */ + /*-- gpgrlhelp.c --*/ void gnupg_rl_initialize (void); @@ -299,6 +314,7 @@ void print_hexstring (FILE *fp, const void *buffer, size_t length, int reserved); char *try_make_printable_string (const void *p, size_t n, int delim); char *make_printable_string (const void *p, size_t n, int delim); +char *decode_c_string (const char *src); int is_file_compressed (const char *s, int *ret_rc); diff --git a/configure.ac b/configure.ac index 9f78259e8..df5ca1238 100644 --- a/configure.ac +++ b/configure.ac @@ -129,7 +129,10 @@ GNUPG_BUILD_PROGRAM(symcryptrun, no) # We use gpgtar to unpack test data, hence we always build it. If the # user opts out, we simply don't install it. GNUPG_BUILD_PROGRAM(gpgtar, yes) -GNUPG_BUILD_PROGRAM(wks-tools, no) +# We also install the gpg-wks-server tool by default but disable it +# later for platforms where it can't be build. +GNUPG_BUILD_PROGRAM(wks-tools, yes) + AC_SUBST(PACKAGE) AC_SUBST(PACKAGE_GT) @@ -507,6 +510,7 @@ AH_BOTTOM([ #define GNUPG_PRIVATE_KEYS_DIR "private-keys-v1.d" #define GNUPG_PUBLIC_KEYS_DIR "public-keys.d" #define GNUPG_OPENPGP_REVOC_DIR "openpgp-revocs.d" +#define GNUPG_CACHE_DIR "cache.d" #define GNUPG_DEF_COPYRIGHT_LINE \ "Copyright (C) 2018 Free Software Foundation, Inc." @@ -623,7 +627,6 @@ AC_ARG_VAR(YAT2M, [tool to convert texi to man pages]) AM_CONDITIONAL(HAVE_YAT2M, test -n "$ac_cv_path_YAT2M") AC_ISC_POSIX AC_SYS_LARGEFILE -GNUPG_CHECK_USTAR # We need to compile and run a program on the build machine. A @@ -682,6 +685,7 @@ case "${host}" in try_gettext="no" use_simple_gettext=yes mmap_needed=no + build_wks_tools=no ;; i?86-emx-os2 | i?86-*-os2*emx ) # OS/2 with the EMX environment @@ -689,6 +693,7 @@ case "${host}" in AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" + build_wks_tools=no ;; i?86-*-msdosdjgpp*) @@ -697,6 +702,7 @@ case "${host}" in AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" + build_wks_tools=no ;; *-*-hpux*) @@ -727,6 +733,7 @@ case "${host}" in # Android is fully utf-8 and we do not want to use iconv to # keeps things simple require_iconv=no + build_wks_tools=no ;; *-apple-darwin*) AC_DEFINE(_DARWIN_C_SOURCE, 900000L, @@ -1361,6 +1368,7 @@ AC_CHECK_SIZEOF(unsigned short) AC_CHECK_SIZEOF(unsigned int) AC_CHECK_SIZEOF(unsigned long) AC_CHECK_SIZEOF(unsigned long long) +AC_CHECK_SIZEOF(size_t) AC_HEADER_TIME AC_CHECK_SIZEOF(time_t,,[[ #include <stdio.h> @@ -1394,16 +1402,16 @@ AC_FUNC_FSEEKO AC_FUNC_VPRINTF AC_FUNC_FORK AC_CHECK_FUNCS([atexit canonicalize_file_name clock_gettime ctermid \ - fcntl flockfile fsync ftello ftruncate funlockfile \ - getaddrinfo getenv getpagesize getpwnam getpwuid \ - getrlimit getrusage gettimeofday gmtime_r \ - inet_ntop inet_pton isascii lstat \ - memicmp memmove memrchr mmap nl_langinfo pipe \ - raise rand setenv setlocale setrlimit sigaction \ - sigprocmask stat stpcpy strcasecmp strerror strftime \ - stricmp strlwr strncasecmp strpbrk strsep \ - strtol strtoul strtoull tcgetattr timegm times \ - ttyname unsetenv wait4 waitpid ]) + explicit_bzero fcntl flockfile fsync ftello \ + ftruncate funlockfile getaddrinfo getenv getpagesize \ + getpwnam getpwuid getrlimit getrusage gettimeofday \ + gmtime_r inet_ntop inet_pton isascii lstat memicmp \ + memmove memrchr mmap nl_langinfo pipe raise rand \ + setenv setlocale setrlimit sigaction sigprocmask \ + stat stpcpy strcasecmp strerror strftime stricmp \ + strlwr strncasecmp strpbrk strsep strtol strtoul \ + strtoull tcgetattr timegm times ttyname unsetenv \ + wait4 waitpid ]) # On some systems (e.g. Solaris) nanosleep requires linking to librl. # Given that we use nanosleep only as an optimization over a select @@ -1482,7 +1490,7 @@ if test "$use_regex" = yes ; then use_regex=no else if test x"$cross_compiling" = xyes; then - AC_MSG_WARN([cross compiling; assuming regexp libray is not broken]) + AC_MSG_WARN([cross compiling; assuming regexp library is not broken]) else AC_CACHE_CHECK([whether your system's regexp library is broken], [gnupg_cv_regex_broken], @@ -2046,7 +2054,6 @@ agent/Makefile scd/Makefile g13/Makefile dirmngr/Makefile -tools/gpg-zip tools/Makefile doc/Makefile tests/Makefile diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am index 22b8c1a3a..098e711e9 100644 --- a/dirmngr/Makefile.am +++ b/dirmngr/Makefile.am @@ -120,7 +120,7 @@ t_common_ldadd = $(libcommon) $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ $(NTBTLS_LIBS) $(LIBGNUTLS_LIBS) \ $(DNSLIBS) $(LIBINTL) $(LIBICONV) -module_tests = +module_tests = t-http-basic if USE_LDAP module_tests += t-ldap-parse-uri @@ -151,6 +151,15 @@ t_http_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \ t_http_LDADD = $(t_common_ldadd) \ $(NTBTLS_LIBS) $(KSBA_LIBS) $(LIBGNUTLS_LIBS) $(DNSLIBS) +t_http_basic_SOURCES = $(t_common_src) t-http-basic.c http.c \ + dns-stuff.c http-common.c +t_http_basic_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \ + $(LIBGCRYPT_CFLAGS) $(NTBTLS_CFLAGS) $(LIBGNUTLS_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(KSBA_CFLAGS) +t_http_basic_LDADD = $(t_common_ldadd) \ + $(NTBTLS_LIBS) $(KSBA_LIBS) $(LIBGNUTLS_LIBS) $(DNSLIBS) + + t_ldap_parse_uri_SOURCES = \ t-ldap-parse-uri.c ldap-parse-uri.c ldap-parse-uri.h \ http.c http-common.c dns-stuff.c \ diff --git a/dirmngr/cdb.h b/dirmngr/cdb.h index 0c0d2702a..5d46f6956 100644 --- a/dirmngr/cdb.h +++ b/dirmngr/cdb.h @@ -85,7 +85,7 @@ int cdb_make_put(struct cdb_make *cdbmp, const void *key, cdbi_t klen, const void *val, cdbi_t vlen, int flag); -#define CDB_PUT_ADD 0 /* add unconditionnaly, like cdb_make_add() */ +#define CDB_PUT_ADD 0 /* add unconditionally, like cdb_make_add() */ #define CDB_PUT_REPLACE 1 /* replace: do not place to index OLD record */ #define CDB_PUT_INSERT 2 /* add only if not already exists */ #define CDB_PUT_WARN 3 /* add unconditionally but ret. 1 if exists */ diff --git a/dirmngr/cdblib.c b/dirmngr/cdblib.c index 827399f7e..c40126396 100644 --- a/dirmngr/cdblib.c +++ b/dirmngr/cdblib.c @@ -19,7 +19,7 @@ length, meaning that corresponding hash table is empty. Right after toc section, data section follows without any - alingment. It consists of series of records, each is a key length, + alignment. It consists of series of records, each is a key length, value (data) length, key and value. Again, key and value length are 4-byte unsigned integers. Each next record follows previous without any special alignment. @@ -52,7 +52,7 @@ beginning of a table). When hash value in question is found in hash table, look to key of corresponding record, comparing it with key in question. If them of the same length and equals to each - other, then record is found, overwise, repeat with next hash table + other, then record is found, otherwise, repeat with next hash table slot. Note that there may be several records with the same key. */ @@ -245,7 +245,7 @@ cdb_find(struct cdb *cdbp, const void *key, cdbi_t klen) pos = cdb_unpack(htp); /* htab position */ if (n > (cdbp->cdb_fsize >> 3) /* overflow of httodo ? */ || pos > cdbp->cdb_fsize /* htab start within file ? */ - || httodo > cdbp->cdb_fsize - pos) /* entrie htab within file ? */ + || httodo > cdbp->cdb_fsize - pos) /* htab entry within file ? */ { gpg_err_set_errno (EPROTO); return -1; diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c index fbe3beea1..c9e5ca68f 100644 --- a/dirmngr/crlcache.c +++ b/dirmngr/crlcache.c @@ -1250,13 +1250,15 @@ crl_cache_deinit (void) } -/* Delete the cache from disk. Return 0 on success.*/ +/* Delete the cache from disk and memory. Return 0 on success.*/ int crl_cache_flush (void) { int rc; + crl_cache_deinit (); rc = cleanup_cache_dir (0)? -1 : 0; + crl_cache_init (); return rc; } @@ -1782,7 +1784,7 @@ crl_parse_insert (ctrl_t ctrl, ksba_crl_t crl, ksba_sexp_t keyid; /* We need to look for the issuer only after having read - all items. The issuer itselfs comes before the items + all items. The issuer itself comes before the items but the optional authorityKeyIdentifier comes after the items. */ err = ksba_crl_get_issuer (crl, &crlissuer); @@ -1907,7 +1909,7 @@ get_crl_number (ksba_crl_t crl) /* Return the authorityKeyIdentifier or NULL if it is not available. - The issuer name may consists of several parts - they are delimted by + The issuer name may consists of several parts - they are delimited by 0x01. */ static char * get_auth_key_id (ksba_crl_t crl, char **serialno) diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 6fdfe36c2..3a99a2e2e 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -338,7 +338,7 @@ static int active_connections; * thread to run background network tasks. */ static int network_activity_seen; -/* A list of filenames registred with --hkp-cacert. */ +/* A list of filenames registered with --hkp-cacert. */ static strlist_t hkp_cacert_filenames; @@ -411,7 +411,7 @@ my_strusage( int level ) /* Callback from libksba to hash a provided buffer. Our current implementation does only allow SHA-1 for hashing. This may be - extended by mapping the name, testing for algorithm availibility + extended by mapping the name, testing for algorithm availability and adjust the length checks accordingly. */ static gpg_error_t my_ksba_hash_buffer (void *arg, const char *oid, @@ -520,7 +520,7 @@ set_tor_mode (void) { if (dirmngr_use_tor ()) { - /* Enable Tor mode and when called again force a new curcuit + /* Enable Tor mode and when called again force a new circuit * (e.g. on SIGHUP). */ enable_dns_tormode (1); if (assuan_sock_set_flag (ASSUAN_INVALID_FD, "tor-mode", 1)) @@ -752,7 +752,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) } -/* This fucntion is called after option parsing to adjust some values +/* This function is called after option parsing to adjust some values * and call option setup functions. */ static void post_option_parsing (void) @@ -763,7 +763,6 @@ post_option_parsing (void) opt.connect_quick_timeout = opt.connect_timeout; set_debug (); - set_tor_mode (); } @@ -802,6 +801,7 @@ static void thread_init (void) { npth_init (); + assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); /* Now with NPth running we can set the logging callback. Our @@ -877,7 +877,6 @@ main (int argc, char **argv) assuan_set_malloc_hooks (&malloc_hooks); assuan_set_assuan_log_prefix (log_get_prefix (NULL)); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); - assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); assuan_sock_init (); setup_libassuan_logging (&opt.debug, dirmngr_assuan_log_monitor); @@ -1090,7 +1089,12 @@ main (int argc, char **argv) log_printf ("\n"); } + /* Note that we do not run set_tor_mode in --gpgconf-list mode + * because it will attempt to connect to the tor client and that can + * be time consuming. */ post_option_parsing (); + if (cmd != aGPGConfTest && cmd != aGPGConfList) + set_tor_mode (); /* Get LDAP server list from file. */ #if USE_LDAP @@ -1143,6 +1147,7 @@ main (int argc, char **argv) thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); + ks_hkp_init (); http_register_netactivity_cb (netactivity_action); start_command_handler (ASSUAN_INVALID_FD, 0); shutdown_reaper (); @@ -1178,6 +1183,7 @@ main (int argc, char **argv) thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); + ks_hkp_init (); http_register_netactivity_cb (netactivity_action); handle_connections (3); shutdown_reaper (); @@ -1399,6 +1405,7 @@ main (int argc, char **argv) thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); + ks_hkp_init (); http_register_netactivity_cb (netactivity_action); handle_connections (fd); shutdown_reaper (); @@ -1421,6 +1428,7 @@ main (int argc, char **argv) thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); + ks_hkp_init (); if (!argc) rc = crl_cache_load (&ctrlbuf, NULL); else @@ -1444,6 +1452,7 @@ main (int argc, char **argv) thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); + ks_hkp_init (); rc = crl_fetch (&ctrlbuf, argv[0], &reader); if (rc) log_error (_("fetching CRL from '%s' failed: %s\n"), @@ -1859,6 +1868,7 @@ dirmngr_sighup_action (void) log_info (_("SIGHUP received - " "re-reading configuration and flushing caches\n")); reread_configuration (); + set_tor_mode (); cert_cache_deinit (0); crl_cache_deinit (); cert_cache_init (hkp_cacert_filenames); diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index edaf46394..9c26c09e6 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -218,7 +218,7 @@ int dirmngr_use_tor (void); /*-- Various housekeeping functions. --*/ void ks_hkp_housekeeping (time_t curtime); void ks_hkp_reload (void); - +void ks_hkp_init (void); /*-- server.c --*/ ldap_server_t get_ldapservers_from_ctrl (ctrl_t ctrl); diff --git a/dirmngr/dirmngr_ldap.c b/dirmngr/dirmngr_ldap.c index 8452c3ba0..dd7e4bda5 100644 --- a/dirmngr/dirmngr_ldap.c +++ b/dirmngr/dirmngr_ldap.c @@ -417,9 +417,9 @@ set_timeout (my_opt_t myopt) sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; - /* Create a manual resetable timer. */ + /* Create a manual resettable timer. */ timer = CreateWaitableTimer (NULL, TRUE, NULL); - /* Intially set the timer. */ + /* Initially set the timer. */ SetWaitableTimer (timer, &due_time, 0, NULL, NULL, 0); if (CreateThread (&sec_attr, 0, alarm_thread, timer, 0, &tid)) diff --git a/dirmngr/dns-stuff.c b/dirmngr/dns-stuff.c index 09b17c0fb..e48aca730 100644 --- a/dirmngr/dns-stuff.c +++ b/dirmngr/dns-stuff.c @@ -151,7 +151,7 @@ static char tor_socks_password[20]; #ifdef USE_LIBDNS -/* Libdns gobal data. */ +/* Libdns global data. */ struct libdns_s { struct dns_resolv_conf *resolv_conf; @@ -701,6 +701,11 @@ libdns_res_open (ctrl_t ctrl, struct dns_resolver **r_res) gpg_error_t err; struct dns_resolver *res; int derr; + struct dns_options opts = { 0 }; + + opts.socks_host = &libdns.socks_host; + opts.socks_user = tor_socks_user; + opts.socks_password = tor_socks_password; *r_res = NULL; @@ -726,10 +731,7 @@ libdns_res_open (ctrl_t ctrl, struct dns_resolver **r_res) set_dns_timeout (0); res = dns_res_open (libdns.resolv_conf, libdns.hosts, libdns.hints, NULL, - dns_opts (.socks_host = &libdns.socks_host, - .socks_user = tor_socks_user, - .socks_password = tor_socks_password ), - &derr); + &opts, &derr); if (!res) return libdns_error_to_gpg_error (derr); @@ -1056,16 +1058,17 @@ resolve_name_standard (ctrl_t ctrl, const char *name, unsigned short port, /* This a wrapper around getaddrinfo with slightly different semantics. - NAME is the name to resolve. - PORT is the requested port or 0. - WANT_FAMILY is either 0 (AF_UNSPEC), AF_INET6, or AF_INET4. - WANT_SOCKETTYPE is either SOCK_STREAM or SOCK_DGRAM. - - On success the result is stored in a linked list with the head - stored at the address R_AI; the caller must call gpg_addrinfo_free - on this. If R_CANONNAME is not NULL the official name of the host - is stored there as a malloced string; if that name is not available - NULL is stored. */ + * NAME is the name to resolve. + * PORT is the requested port or 0. + * WANT_FAMILY is either 0 (AF_UNSPEC), AF_INET6, or AF_INET4. + * WANT_SOCKETTYPE is either 0 for any socket type + * or SOCK_STREAM or SOCK_DGRAM. + * + * On success the result is stored in a linked list with the head + * stored at the address R_AI; the caller must call free_dns_addrinfo + * on this. If R_CANONNAME is not NULL the official name of the host + * is stored there as a malloced string; if that name is not available + * NULL is stored. */ gpg_error_t resolve_dns_name (ctrl_t ctrl, const char *name, unsigned short port, int want_family, int want_socktype, @@ -1167,7 +1170,7 @@ resolve_addr_libdns (ctrl_t ctrl, struct dns_rr_i rri; memset (&rri, 0, sizeof rri); - dns_rr_i_init (&rri, ans); + dns_rr_i_init (&rri); rri.section = DNS_S_ALL & ~DNS_S_QD; rri.name = host; rri.type = DNS_T_PTR; @@ -1458,7 +1461,7 @@ get_dns_cert_libdns (ctrl_t ctrl, const char *name, int want_certtype, goto leave; memset (&rri, 0, sizeof rri); - dns_rr_i_init (&rri, ans); + dns_rr_i_init (&rri); rri.section = DNS_S_ALL & ~DNS_S_QD; rri.name = host; rri.type = qtype; @@ -1888,7 +1891,7 @@ getsrv_libdns (ctrl_t ctrl, goto leave; memset (&rri, 0, sizeof rri); - dns_rr_i_init (&rri, ans); + dns_rr_i_init (&rri); rri.section = DNS_S_ALL & ~DNS_S_QD; rri.name = host; rri.type = DNS_T_SRV; diff --git a/dirmngr/dns.c b/dirmngr/dns.c index 77f83f437..fa5e5283d 100644 --- a/dirmngr/dns.c +++ b/dirmngr/dns.c @@ -77,6 +77,7 @@ typedef int socket_fd_t; #include <netdb.h> /* struct addrinfo */ #endif +#include "gpgrt.h" /* For GGPRT_GCC_VERSION */ #include "dns.h" @@ -943,10 +944,11 @@ static int dns_sa_cmp(void *a, void *b) { #if _WIN32 static int dns_inet_pton(int af, const void *src, void *dst) { union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u; + int size_of_u = (int)sizeof u; u.sin.sin_family = af; - if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &(int){ sizeof u })) + if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &size_of_u)) return -1; switch (af) { @@ -1124,6 +1126,7 @@ static inline _Bool dns_isgraph(unsigned char c) { static int dns_poll(int fd, short events, int timeout) { fd_set rset, wset; + struct timeval tv = { timeout, 0 }; if (!events) return 0; @@ -1140,7 +1143,7 @@ static int dns_poll(int fd, short events, int timeout) { if (events & DNS_POLLOUT) FD_SET(fd, &wset); - select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &(struct timeval){ timeout, 0 } : NULL); + select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &tv : NULL); return 0; } /* dns_poll() */ @@ -1214,9 +1217,10 @@ static size_t dns_send_nopipe(int fd, const void *src, size_t len, int flags, dn if (!sigismember(&pending, SIGPIPE)) { int saved = error; + const struct timespec ts = { 0, 0 }; if (!count && error == EPIPE) { - while (-1 == sigtimedwait(&piped, NULL, &(struct timespec){ 0, 0 }) && errno == EINTR) + while (-1 == sigtimedwait(&piped, NULL, &ts) && errno == EINTR) ;; } @@ -1832,7 +1836,7 @@ static void dns_p_free(struct dns_packet *P) { } /* dns_p_free() */ -/* convience routine to free any existing packet before storing new packet */ +/* convenience routine to free any existing packet before storing new packet */ static struct dns_packet *dns_p_setptr(struct dns_packet **dst, struct dns_packet *src) { dns_p_free(*dst); @@ -2213,7 +2217,8 @@ static void dns_p_dump3(struct dns_packet *P, struct dns_rr_i *I, FILE *fp) { void dns_p_dump(struct dns_packet *P, FILE *fp) { - dns_p_dump3(P, dns_rr_i_new(P, .section = 0), fp); + struct dns_rr_i _I = { 0 }; + dns_p_dump3(P, &_I, fp); } /* dns_p_dump() */ @@ -2792,8 +2797,7 @@ size_t dns_d_cname(void *dst, size_t lim, const void *dn, size_t len, struct dns { error = ENAMETOOLONG; goto error; } for (depth = 0; depth < 7; depth++) { - dns_rr_i_init(memset(&i, 0, sizeof i), P); - + memset(&i, 0, sizeof i); i.section = DNS_S_ALL & ~DNS_S_QD; i.name = host; i.type = DNS_T_CNAME; @@ -3218,15 +3222,11 @@ int dns_rr_i_shuffle(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, str } /* dns_rr_i_shuffle() */ -struct dns_rr_i *dns_rr_i_init(struct dns_rr_i *i, struct dns_packet *P) { +void dns_rr_i_init(struct dns_rr_i *i) { static const struct dns_rr_i i_initializer; - (void)P; - i->state = i_initializer.state; i->saved = i->state; - - return i; } /* dns_rr_i_init() */ @@ -3262,7 +3262,8 @@ unsigned dns_rr_grep(struct dns_rr *rr, unsigned lim, struct dns_rr_i *i, struct return count; error: - *error_ = error; + if (error_) + *error_ = error; return count; } /* dns_rr_grep() */ @@ -5274,7 +5275,8 @@ error: struct dns_packet *dns_hosts_query(struct dns_hosts *hosts, struct dns_packet *Q, int *error_) { - struct dns_packet *P = dns_p_new(512); + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 }; + struct dns_packet *P = dns_p_init(&_P.p, 512); struct dns_packet *A = 0; struct dns_rr rr; struct dns_hosts_entry *ent; @@ -6835,6 +6837,7 @@ unsigned dns_hints_grep(struct sockaddr **sa, socklen_t *sa_len, unsigned lim, s struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q, int *error_) { + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 }; struct dns_packet *A, *P; struct dns_rr rr; char zone[DNS_D_MAXNAME + 1]; @@ -6843,8 +6846,11 @@ struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q struct sockaddr *sa; socklen_t slen; int error; + struct dns_rr_i _I = { 0 }; + + _I.section = DNS_S_QUESTION; - if (!dns_rr_grep(&rr, 1, dns_rr_i_new(Q, .section = DNS_S_QUESTION), Q, &error)) + if (!dns_rr_grep(&rr, 1, &_I, Q, &error)) goto error; if (!(zlen = dns_d_expand(zone, sizeof zone, rr.dn.p, Q, &error))) @@ -6852,7 +6858,7 @@ struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q else if (zlen >= sizeof zone) goto toolong; - P = dns_p_new(512); + P = dns_p_init(&_P.p, 512); dns_header(P)->qr = 1; if ((error = dns_rr_copy(P, &rr, Q))) @@ -7110,7 +7116,8 @@ static int dns_socket(struct sockaddr *local, int type, int *error_) { #if defined SO_NOSIGPIPE if (type != SOCK_DGRAM) { - if (0 != setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, sizeof (int))) + const int v = 1; + if (0 != setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &v, sizeof (int))) goto soerr; } #endif @@ -7486,11 +7493,12 @@ error: static _Bool dns_so_tcp_keep(struct dns_socket *so) { struct sockaddr_storage remote; + socklen_t l = sizeof remote; if (so->tcp == -1) return 0; - if (0 != getpeername(so->tcp, (struct sockaddr *)&remote, &(socklen_t){ sizeof remote })) + if (0 != getpeername(so->tcp, (struct sockaddr *)&remote, &l)) return 0; return 0 == dns_sa_cmp(&remote, &so->remote); @@ -7521,9 +7529,13 @@ static unsigned char *dns_so_tcp_recv_buffer(struct dns_socket *so) { } -#if defined __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warray-bounds" + +#if GPGRT_GCC_VERSION >= 80000 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Warray-bounds" +#elif defined __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Warray-bounds" #endif static int dns_so_tcp_send(struct dns_socket *so) { @@ -7589,8 +7601,10 @@ static int dns_so_tcp_recv(struct dns_socket *so) { return 0; } /* dns_so_tcp_recv() */ -#if __clang__ -#pragma clang diagnostic pop +#if GPGRT_GCC_VERSION >= 80000 +# pragma GCC diagnostic pop +#elif __clang__ +# pragma clang diagnostic pop #endif @@ -7634,7 +7648,7 @@ retry: goto udp_connect_retry; } else if (error == ECONNREFUSED) /* Error for previous socket operation may - be reserverd asynchronously. */ + be reserverd(?) asynchronously. */ goto udp_connect_retry; if (error) @@ -8449,7 +8463,8 @@ error: static struct dns_packet *dns_res_glue(struct dns_resolver *R, struct dns_packet *Q) { - struct dns_packet *P = dns_p_new(512); + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 }; + struct dns_packet *P = dns_p_init(&_P.p, 512); char qname[DNS_D_MAXNAME + 1]; size_t qlen; enum dns_type qtype; @@ -8521,12 +8536,22 @@ static int dns_res_nameserv_cmp(struct dns_rr *a, struct dns_rr *b, struct dns_r struct dns_ns ns; int cmp, error; - if (!(error = dns_ns_parse(&ns, a, P))) - glued[0] = !!dns_rr_grep(&x, 1, dns_rr_i_new(P, .section = (DNS_S_ALL & ~DNS_S_QD), .name = ns.host, .type = DNS_T_A), P, &error); + if (!(error = dns_ns_parse(&ns, a, P))) { + struct dns_rr_i _I = { 0 }; - if (!(error = dns_ns_parse(&ns, b, P))) - glued[1] = !!dns_rr_grep(&y, 1, dns_rr_i_new(P, .section = (DNS_S_ALL & ~DNS_S_QD), .name = ns.host, .type = DNS_T_A), P, &error); + _I.section = (DNS_S_ALL & ~DNS_S_QD); + _I.name = ns.host; + _I.type = DNS_T_A; + glued[0] = !!dns_rr_grep(&x, 1, &_I, P, &error); + } + if (!(error = dns_ns_parse(&ns, b, P))) { + struct dns_rr_i _I = { 0 }; + _I.section = (DNS_S_ALL & ~DNS_S_QD); + _I.name = ns.host; + _I.type = DNS_T_A; + glued[1] = !!dns_rr_grep(&y, 1, &_I, P, &error); + } if ((cmp = glued[1] - glued[0])) { return cmp; } else if ((cmp = (dns_rr_offset(&y) < i->args[0]) - (dns_rr_offset(&x) < i->args[0]))) { @@ -8727,7 +8752,7 @@ exec: F->state++; /* FALL THROUGH */ case DNS_R_ITERATE: - dns_rr_i_init(&F->hints_i, F->hints); + dns_rr_i_init(&F->hints_i); F->hints_i.section = DNS_S_AUTHORITY; F->hints_i.type = DNS_T_NS; @@ -8746,7 +8771,7 @@ exec: dgoto(R->sp, DNS_R_SWITCH); } - dns_rr_i_init(&F->hints_j, F->hints); + dns_rr_i_init(&F->hints_j); /* Assume there are glue records */ dgoto(R->sp, DNS_R_FOREACH_A); @@ -8799,14 +8824,14 @@ exec: if (!dns_rr_i_count(&F->hints_j)) { /* Check if we have in fact servers with an IPv6 address. */ - dns_rr_i_init(&F->hints_j, F->hints); + dns_rr_i_init(&F->hints_j); F->hints_j.name = u.ns.host; F->hints_j.type = DNS_T_AAAA; F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { /* We do. Reinitialize iterator and handle it. */ - dns_rr_i_init(&F->hints_j, F->hints); + dns_rr_i_init(&F->hints_j); dgoto(R->sp, DNS_R_FOREACH_AAAA); } @@ -8935,14 +8960,14 @@ exec: if (!dns_rr_i_count(&F->hints_j)) { /* Check if we have in fact servers with an IPv4 address. */ - dns_rr_i_init(&F->hints_j, F->hints); + dns_rr_i_init(&F->hints_j); F->hints_j.name = u.ns.host; F->hints_j.type = DNS_T_A; F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { /* We do. Reinitialize iterator and handle it. */ - dns_rr_i_init(&F->hints_j, F->hints); + dns_rr_i_init(&F->hints_j); dgoto(R->sp, DNS_R_FOREACH_A); } @@ -9080,7 +9105,7 @@ exec: R->smart.section = DNS_S_AN; R->smart.type = R->qtype; - dns_rr_i_init(&R->smart, F->answer); + dns_rr_i_init(&R->smart); F->state++; /* FALL THROUGH */ case DNS_R_SMART0_A: @@ -9824,7 +9849,7 @@ exec: return error; dns_strlcpy(ai->i_cname, ai->cname, sizeof ai->i_cname); - dns_rr_i_init(&ai->i, ai->answer); + dns_rr_i_init(&ai->i); ai->i.section = DNS_S_AN; ai->i.name = ai->i_cname; ai->i.type = dns_ai_qtype(ai); @@ -9871,7 +9896,7 @@ exec: ai->state++; /* FALL THROUGH */ case DNS_AI_S_ITERATE_G: dns_strlcpy(ai->g_cname, ai->cname, sizeof ai->g_cname); - dns_rr_i_init(&ai->g, ai->glue); + dns_rr_i_init(&ai->g); ai->g.section = DNS_S_ALL & ~DNS_S_QD; ai->g.name = ai->g_cname; ai->g.type = ai->af.qtype; @@ -9890,8 +9915,14 @@ exec: return dns_ai_setent(ent, &any, rr.type, ai); case DNS_AI_S_SUBMIT_G: + { + struct dns_rr_i _I = { 0 }; + + _I.section = DNS_S_QD; + _I.name = ai->g.name; + _I.type = ai->g.type; /* skip if already queried */ - if (dns_rr_grep(&rr, 1, dns_rr_i_new(ai->glue, .section = DNS_S_QD, .name = ai->g.name, .type = ai->g.type), ai->glue, &error)) + if (dns_rr_grep(&rr, 1, &_I, ai->glue, &error)) dns_ai_goto(DNS_AI_S_FOREACH_I); /* skip if we recursed (CNAME chains should have been handled in the resolver) */ if (++ai->g_depth > 1) @@ -9900,7 +9931,8 @@ exec: if ((error = dns_res_submit(ai->res, ai->g.name, ai->g.type, DNS_C_IN))) return error; - ai->state++; /* FALL THROUGH */ + ai->state++; + } /* FALL THROUGH */ case DNS_AI_S_CHECK_G: if ((error = dns_res_check(ai->res))) return error; @@ -10074,8 +10106,9 @@ static const struct { { "AR", DNS_S_ADDITIONAL }, }; -const char *(dns_strsection)(enum dns_section section, void *_dst, size_t lim) { - struct dns_buf dst = DNS_B_INTO(_dst, lim); +const char *(dns_strsection)(enum dns_section section) { + char _dst[DNS_STRMAXLEN + 1] = { 0 }; + struct dns_buf dst = DNS_B_INTO(_dst, sizeof _dst); unsigned i; for (i = 0; i < lengthof(dns_sections); i++) { @@ -10123,8 +10156,9 @@ static const struct { { "IN", DNS_C_IN }, }; -const char *(dns_strclass)(enum dns_class type, void *_dst, size_t lim) { - struct dns_buf dst = DNS_B_INTO(_dst, lim); +const char *(dns_strclass)(enum dns_class type) { + char _dst[DNS_STRMAXLEN + 1] = { 0 }; + struct dns_buf dst = DNS_B_INTO(_dst, sizeof _dst); unsigned i; for (i = 0; i < lengthof(dns_classes); i++) { @@ -10159,8 +10193,9 @@ enum dns_class dns_iclass(const char *name) { } /* dns_iclass() */ -const char *(dns_strtype)(enum dns_type type, void *_dst, size_t lim) { - struct dns_buf dst = DNS_B_INTO(_dst, lim); +const char *(dns_strtype)(enum dns_type type) { + char _dst[DNS_STRMAXLEN + 1] = { 0 }; + struct dns_buf dst = DNS_B_INTO(_dst, sizeof _dst); unsigned i; for (i = 0; i < lengthof(dns_rrtypes); i++) { @@ -10563,7 +10598,9 @@ static struct dns_trace *trace(const char *mode) { static void print_packet(struct dns_packet *P, FILE *fp) { - dns_p_dump3(P, dns_rr_i_new(P, .sort = MAIN.sort), fp); + struct dns_rr_i _I = { 0 }; + I.sort = MAIN.sort; + dns_p_dump3(P, &I, fp); if (MAIN.verbose > 2) hexdump(P->data, P->end, fp); @@ -10571,8 +10608,10 @@ static void print_packet(struct dns_packet *P, FILE *fp) { static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { - struct dns_packet *P = dns_p_new(512); - struct dns_packet *Q = dns_p_new(512); + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 }; + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _Q = { 0 }; + struct dns_packet *P = dns_p_init(&_P.p, 512); + struct dns_packet *Q = dns_p_init(&_Q.p, 512); enum dns_section section; struct dns_rr rr; int error; @@ -10612,10 +10651,16 @@ static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { #if 0 dns_rr_foreach(&rr, Q, .name = "ns8.yahoo.com.") { #else + char _p[DNS_D_MAXNAME + 1] = { 0 }; + const char *dn = "ns8.yahoo.com"; + char *_name = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR); struct dns_rr rrset[32]; - struct dns_rr_i *rri = dns_rr_i_new(Q, .name = dns_d_new("ns8.yahoo.com", DNS_D_ANCHOR), .sort = MAIN.sort); + struct dns_rr_i _I = { 0 }; + struct dns_rr_i *rri = &I; unsigned rrcount = dns_rr_grep(rrset, lengthof(rrset), rri, Q, &error); + I.name = _name; + I.sort = MAIN.sort; for (unsigned i = 0; i < rrcount; i++) { rr = rrset[i]; #endif @@ -10641,13 +10686,14 @@ static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { static int parse_domain(int argc, char *argv[]) { + char _p[DNS_D_MAXNAME + 1] = { 0 }; char *dn; dn = (argc > 1)? argv[1] : "f.l.google.com"; printf("[%s]\n", dn); - dn = dns_d_new(dn); + dn = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR); do { puts(dn); @@ -10772,7 +10818,8 @@ static int show_hosts(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { static int query_hosts(int argc, char *argv[]) { - struct dns_packet *Q = dns_p_new(512); + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _Q = { 0 }; + struct dns_packet *Q = dns_p_init(&_Q.p, 512); struct dns_packet *A; char qname[DNS_D_MAXNAME + 1]; size_t qlen; @@ -10890,11 +10937,13 @@ static int dump_random(int argc, char *argv[]) { static int send_query(int argc, char *argv[]) { - struct dns_packet *A, *Q = dns_p_new(512); + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _Q = { 0 }; + struct dns_packet *A, *Q = dns_p_init(&_Q.p, 512); char host[INET6_ADDRSTRLEN + 1]; struct sockaddr_storage ss; struct dns_socket *so; int error, type; + struct dns_options opts = { 0 }; memset(&ss, 0, sizeof ss); if (argc > 1) { @@ -10929,7 +10978,7 @@ static int send_query(int argc, char *argv[]) { fprintf(stderr, "querying %s for %s IN %s\n", host, MAIN.qname, dns_strtype(MAIN.qtype)); - if (!(so = dns_so_open((struct sockaddr *)&resconf()->iface, type, dns_opts(), &error))) + if (!(so = dns_so_open((struct sockaddr *)&resconf()->iface, type, &opts, &error))) panic("dns_so_open: %s", dns_strerror(error)); while (!(A = dns_so_query(so, Q, (struct sockaddr *)&ss, &error))) { @@ -10984,9 +11033,10 @@ static int show_hints(int argc, char *argv[]) { if (0 == strcmp(how, "plain")) { dns_hints_dump(hints, stdout); } else { + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 }; struct dns_packet *query, *answer; - query = dns_p_new(512); + query = dns_p_init(&_P.p, 512); if ((error = dns_p_push(query, DNS_S_QUESTION, who, strlen(who), DNS_T_A, DNS_C_IN, 0, 0))) panic("%s: %s", who, dns_strerror(error)); @@ -11012,6 +11062,11 @@ static int resolve_query(int argc DNS_NOTUSED, char *argv[]) { struct dns_packet *ans; const struct dns_stat *st; int error; + struct dns_options opts = { 0 }; + + opts.socks_host = &MAIN.socks_host; + opts.socks_user = MAIN.socks_user; + opts.socks_password = MAIN.socks_password; if (!MAIN.qname) MAIN.qname = "www.google.com"; @@ -11021,9 +11076,7 @@ static int resolve_query(int argc DNS_NOTUSED, char *argv[]) { resconf()->options.recurse = recurse; if (!(R = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), - dns_opts(.socks_host=&MAIN.socks_host, - .socks_user=MAIN.socks_user, - .socks_password=MAIN.socks_password), &error))) + &opts, &error))) panic("%s: %s", MAIN.qname, dns_strerror(error)); dns_res_settrace(R, trace("w+b")); @@ -11067,6 +11120,7 @@ static int resolve_addrinfo(int argc DNS_NOTUSED, char *argv[]) { struct addrinfo *ent; char pretty[512]; int error; + struct dns_options opts = { 0 }; if (!MAIN.qname) MAIN.qname = "www.google.com"; @@ -11074,7 +11128,7 @@ static int resolve_addrinfo(int argc DNS_NOTUSED, char *argv[]) { resconf()->options.recurse = recurse; - if (!(res = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), dns_opts(), &error))) + if (!(res = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), &opts, &error))) panic("%s: %s", MAIN.qname, dns_strerror(error)); if (!(ai = dns_ai_open(MAIN.qname, "80", MAIN.qtype, &ai_hints, res, &error))) @@ -11145,7 +11199,8 @@ static int echo_port(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { panic("127.0.0.1:5353: %s", dns_strerror(errno)); for (;;) { - struct dns_packet *pkt = dns_p_new(512); + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } _P = { 0 }; + struct dns_packet *pkt = dns_p_init(&_P.p, 512); struct sockaddr_storage ss; socklen_t slen = sizeof ss; ssize_t count; diff --git a/dirmngr/dns.h b/dirmngr/dns.h index 30d0b45af..024d6dcc8 100644 --- a/dirmngr/dns.h +++ b/dirmngr/dns.h @@ -132,19 +132,6 @@ DNS_PUBLIC int *dns_debug_p(void); /* * C O M P I L E R A N N O T A T I O N S * - * GCC with -Wextra, and clang by default, complain about overrides in - * initializer lists. Overriding previous member initializers is well - * defined behavior in C. dns.c relies on this behavior to define default, - * overrideable member values when instantiating configuration objects. - * - * dns_quietinit() guards a compound literal expression with pragmas to - * silence these shrill warnings. This alleviates the burden of requiring - * third-party projects to adjust their compiler flags. - * - * NOTE: If you take the address of the compound literal, take the address - * of the transformed expression, otherwise the compound literal lifetime is - * tied to the scope of the GCC statement expression. - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if defined __clang__ @@ -152,21 +139,15 @@ DNS_PUBLIC int *dns_debug_p(void); #define DNS_PRAGMA_QUIET _Pragma("clang diagnostic ignored \"-Winitializer-overrides\"") #define DNS_PRAGMA_POP _Pragma("clang diagnostic pop") -#define dns_quietinit(...) \ - DNS_PRAGMA_PUSH DNS_PRAGMA_QUIET __VA_ARGS__ DNS_PRAGMA_POP #elif (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || __GNUC__ > 4 #define DNS_PRAGMA_PUSH _Pragma("GCC diagnostic push") #define DNS_PRAGMA_QUIET _Pragma("GCC diagnostic ignored \"-Woverride-init\"") #define DNS_PRAGMA_POP _Pragma("GCC diagnostic pop") -/* GCC parses the _Pragma operator less elegantly than clang. */ -#define dns_quietinit(...) \ - __extension__ ({ DNS_PRAGMA_PUSH DNS_PRAGMA_QUIET __VA_ARGS__; DNS_PRAGMA_POP }) #else #define DNS_PRAGMA_PUSH #define DNS_PRAGMA_QUIET #define DNS_PRAGMA_POP -#define dns_quietinit(...) __VA_ARGS__ #endif #if defined __GNUC__ @@ -291,25 +272,15 @@ enum dns_rcode { */ #define DNS_STRMAXLEN 47 /* "QUESTION|ANSWER|AUTHORITY|ADDITIONAL" */ -DNS_PUBLIC const char *dns_strsection(enum dns_section, void *, size_t); -#define dns_strsection3(a, b, c) \ - dns_strsection((a), (b), (c)) -#define dns_strsection1(a) dns_strsection((a), (char [DNS_STRMAXLEN + 1]){ 0 }, DNS_STRMAXLEN + 1) -#define dns_strsection(...) DNS_PP_CALL(DNS_PP_XPASTE(dns_strsection, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) +DNS_PUBLIC const char *dns_strsection(enum dns_section); DNS_PUBLIC enum dns_section dns_isection(const char *); -DNS_PUBLIC const char *dns_strclass(enum dns_class, void *, size_t); -#define dns_strclass3(a, b, c) dns_strclass((a), (b), (c)) -#define dns_strclass1(a) dns_strclass((a), (char [DNS_STRMAXLEN + 1]){ 0 }, DNS_STRMAXLEN + 1) -#define dns_strclass(...) DNS_PP_CALL(DNS_PP_XPASTE(dns_strclass, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) +DNS_PUBLIC const char *dns_strclass(enum dns_class); DNS_PUBLIC enum dns_class dns_iclass(const char *); -DNS_PUBLIC const char *dns_strtype(enum dns_type, void *, size_t); -#define dns_strtype3(a, b, c) dns_strtype((a), (b), (c)) -#define dns_strtype1(a) dns_strtype((a), (char [DNS_STRMAXLEN + 1]){ 0 }, DNS_STRMAXLEN + 1) -#define dns_strtype(...) DNS_PP_CALL(DNS_PP_XPASTE(dns_strtype, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) +DNS_PUBLIC const char *dns_strtype(enum dns_type); DNS_PUBLIC enum dns_type dns_itype(const char *); @@ -422,9 +393,6 @@ struct dns_packet { #define dns_p_sizeof(P) dns_p_calcsize((P)->end) -/** takes size of maximum desired payload */ -#define dns_p_new(n) (dns_p_init((struct dns_packet *)&(union { unsigned char b[dns_p_calcsize((n))]; struct dns_packet p; }){ { 0 } }, dns_p_calcsize((n)))) - /** takes size of entire packet structure as allocated */ DNS_PUBLIC struct dns_packet *dns_p_init(struct dns_packet *, size_t); @@ -464,11 +432,6 @@ DNS_PUBLIC int dns_p_study(struct dns_packet *); #define DNS_D_CLEAVE 2 /* cleave sub-domain */ #define DNS_D_TRIM 4 /* remove superfluous dots */ -#define dns_d_new3(a, b, f) dns_d_init(&(char[DNS_D_MAXNAME + 1]){ 0 }, DNS_D_MAXNAME + 1, (a), (b), (f)) -#define dns_d_new2(a, f) dns_d_new3((a), strlen((a)), (f)) -#define dns_d_new1(a) dns_d_new3((a), strlen((a)), DNS_D_ANCHOR) -#define dns_d_new(...) DNS_PP_CALL(DNS_PP_XPASTE(dns_d_new, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) - DNS_PUBLIC char *dns_d_init(void *, size_t, const void *, size_t, int); DNS_PUBLIC size_t dns_d_anchor(void *, size_t, const void *, size_t); @@ -521,9 +484,6 @@ DNS_PUBLIC int dns_rr_cmp(struct dns_rr *, struct dns_packet *, struct dns_rr *, DNS_PUBLIC size_t dns_rr_print(void *, size_t, struct dns_rr *, struct dns_packet *, int *); -#define dns_rr_i_new(P, ...) \ - dns_rr_i_init(&dns_quietinit((struct dns_rr_i){ 0, __VA_ARGS__ }), (P)) - struct dns_rr_i { enum dns_section section; const void *name; @@ -551,7 +511,7 @@ DNS_PUBLIC int dns_rr_i_order(struct dns_rr *, struct dns_rr *, struct dns_rr_i DNS_PUBLIC int dns_rr_i_shuffle(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *); -DNS_PUBLIC struct dns_rr_i *dns_rr_i_init(struct dns_rr_i *, struct dns_packet *); +DNS_PUBLIC void dns_rr_i_init(struct dns_rr_i *); #define dns_rr_i_save(i) ((i)->saved = (i)->state) #define dns_rr_i_rewind(i) ((i)->state = (i)->saved) @@ -560,7 +520,7 @@ DNS_PUBLIC struct dns_rr_i *dns_rr_i_init(struct dns_rr_i *, struct dns_packet * DNS_PUBLIC unsigned dns_rr_grep(struct dns_rr *, unsigned, struct dns_rr_i *, struct dns_packet *, int *); #define dns_rr_foreach_(rr, P, ...) \ - for (struct dns_rr_i DNS_PP_XPASTE(i, __LINE__) = *dns_rr_i_new((P), __VA_ARGS__); dns_rr_grep((rr), 1, &DNS_PP_XPASTE(i, __LINE__), (P), &(int){ 0 }); ) + for (struct dns_rr_i DNS_PP_XPASTE(i, __LINE__) = { __VA_ARGS__ }; dns_rr_grep((rr), 1, &DNS_PP_XPASTE(i, __LINE__), (P), NULL); ) #define dns_rr_foreach(...) dns_rr_foreach_(__VA_ARGS__) @@ -1001,7 +961,6 @@ struct dns_hints_i { } state; }; /* struct dns_hints_i */ -#define dns_hints_i_new(...) (&(struct dns_hints_i){ __VA_ARGS__ }) DNS_PUBLIC unsigned dns_hints_grep(struct sockaddr **, socklen_t *, unsigned, struct dns_hints_i *, struct dns_hints *); @@ -1053,9 +1012,6 @@ DNS_PUBLIC void dns_cache_close(struct dns_cache *); #define DNS_OPTS_INITIALIZER_ { 0, 0 }, 0, 0 #define DNS_OPTS_INITIALIZER { DNS_OPTS_INITIALIZER_ } -#define DNS_OPTS_INIT(...) { DNS_OPTS_INITIALIZER_, __VA_ARGS__ } - -#define dns_opts(...) (&dns_quietinit((struct dns_options)DNS_OPTS_INIT(__VA_ARGS__))) struct dns_options { /* diff --git a/dirmngr/domaininfo.c b/dirmngr/domaininfo.c index a2effffef..f6263b06d 100644 --- a/dirmngr/domaininfo.c +++ b/dirmngr/domaininfo.c @@ -119,7 +119,7 @@ domaininfo_print_stats (void) } -/* Return true if DOMAIN definitely does not support WKD. Noet that +/* Return true if DOMAIN definitely does not support WKD. Note that * DOMAIN is expected to be lowercase. */ int domaininfo_is_wkd_not_supported (const char *domain) diff --git a/dirmngr/http-ntbtls.c b/dirmngr/http-ntbtls.c index ed4cdd496..924b8b25f 100644 --- a/dirmngr/http-ntbtls.c +++ b/dirmngr/http-ntbtls.c @@ -55,7 +55,7 @@ gnupg_http_tls_verify_cb (void *opaque, log_assert (ctrl && ctrl->magic == SERVER_CONTROL_MAGIC); log_assert (!ntbtls_check_context (tls)); - /* Get the peer's certs fron ntbtls. */ + /* Get the peer's certs from ntbtls. */ for (idx = 0; (cert = ntbtls_x509_get_peer_cert (tls, idx)); idx++) { diff --git a/dirmngr/http.c b/dirmngr/http.c index 5fb7eed04..d6856fe05 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -1350,6 +1350,8 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, uri->v6lit = 0; uri->onion = 0; uri->explicit_port = 0; + uri->off_host = 0; + uri->off_path = 0; /* A quick validity check. */ if (strspn (p, VALID_URI_CHARS) != n) @@ -1393,7 +1395,19 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, { p += 2; if ((p2 = strchr (p, '/'))) - *p2++ = 0; + { + if (p2 - uri->buffer > 10000) + return GPG_ERR_BAD_URI; + uri->off_path = p2 - uri->buffer; + *p2++ = 0; + } + else + { + n = (p - uri->buffer) + strlen (p); + if (n > 10000) + return GPG_ERR_BAD_URI; + uri->off_path = n; + } /* Check for username/password encoding */ if ((p3 = strchr (p, '@'))) @@ -1412,11 +1426,19 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, *p3++ = '\0'; /* worst case, uri->host should have length 0, points to \0 */ uri->host = p + 1; + if (p - uri->buffer > 10000) + return GPG_ERR_BAD_URI; + uri->off_host = (p + 1) - uri->buffer; uri->v6lit = 1; p = p3; } else - uri->host = p; + { + uri->host = p; + if (p - uri->buffer > 10000) + return GPG_ERR_BAD_URI; + uri->off_host = p - uri->buffer; + } if ((p3 = strchr (p, ':'))) { @@ -3496,3 +3518,207 @@ uri_query_lookup (parsed_uri_t uri, const char *key) return NULL; } + + +/* Return true if both URI point to the same host for the purpose of + * redirection check. A is the original host and B the host given in + * the Location header. As a temporary workaround a fixed list of + * exceptions is also consulted. */ +static int +same_host_p (parsed_uri_t a, parsed_uri_t b) +{ + static struct + { + const char *from; /* NULL uses the last entry from the table. */ + const char *to; + } allow[] = + { + { "protonmail.com", "api.protonmail.com" }, + { NULL, "api.protonmail.ch" }, + { "protonmail.ch", "api.protonmail.com" }, + { NULL, "api.protonmail.ch" } + }; + int i; + const char *from; + + if (!a->host || !b->host) + return 0; + + if (!ascii_strcasecmp (a->host, b->host)) + return 1; + + from = NULL; + for (i=0; i < DIM (allow); i++) + { + if (allow[i].from) + from = allow[i].from; + if (!from) + continue; + if (!ascii_strcasecmp (from, a->host) + && !ascii_strcasecmp (allow[i].to, b->host)) + return 1; + } + + return 0; +} + + +/* Prepare a new URL for a HTTP redirect. INFO has flags controlling + * the operation, STATUS_CODE is used for diagnostics, LOCATION is the + * value of the "Location" header, and R_URL reveives the new URL on + * success or NULL or error. Note that INFO->ORIG_URL is + * required. */ +gpg_error_t +http_prepare_redirect (http_redir_info_t *info, unsigned int status_code, + const char *location, char **r_url) +{ + gpg_error_t err; + parsed_uri_t locuri; + parsed_uri_t origuri; + char *newurl; + char *p; + + *r_url = NULL; + + if (!info || !info->orig_url) + return gpg_error (GPG_ERR_INV_ARG); + + if (!info->silent) + log_info (_("URL '%s' redirected to '%s' (%u)\n"), + info->orig_url, location? location:"[none]", status_code); + + if (!info->redirects_left) + { + if (!info->silent) + log_error (_("too many redirections\n")); + return gpg_error (GPG_ERR_NO_DATA); + } + info->redirects_left--; + + if (!location || !*location) + return gpg_error (GPG_ERR_NO_DATA); + + err = http_parse_uri (&locuri, location, 0); + if (err) + return err; + + /* Make sure that an onion address only redirects to another + * onion address, or that a https address only redirects to a + * https address. */ + if (info->orig_onion && !locuri->onion) + { + http_release_parsed_uri (locuri); + return gpg_error (GPG_ERR_FORBIDDEN); + } + if (!info->allow_downgrade && info->orig_https && !locuri->use_tls) + { + http_release_parsed_uri (locuri); + return gpg_error (GPG_ERR_FORBIDDEN); + } + + if (info->trust_location) + { + /* We trust the Location - return it verbatim. */ + http_release_parsed_uri (locuri); + newurl = xtrystrdup (location); + if (!newurl) + { + err = gpg_error_from_syserror (); + http_release_parsed_uri (locuri); + return err; + } + } + else if ((err = http_parse_uri (&origuri, info->orig_url, 0))) + { + http_release_parsed_uri (locuri); + return err; + } + else if (same_host_p (origuri, locuri)) + { + /* The host is the same or on an exception list and thus we can + * take the location verbatim. */ + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + newurl = xtrystrdup (location); + if (!newurl) + { + err = gpg_error_from_syserror (); + http_release_parsed_uri (locuri); + return err; + } + } + else + { + /* We take only the host and port from the URL given in the + * Location. This limits the effects of redirection attacks by + * rogue hosts returning an URL to servers in the client's own + * network. We don't even include the userinfo because they + * should be considered similar to the path and query parts. + */ + if (!(locuri->off_path - locuri->off_host)) + { + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + return gpg_error (GPG_ERR_BAD_URI); + } + if (!(origuri->off_path - origuri->off_host)) + { + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + return gpg_error (GPG_ERR_BAD_URI); + } + + newurl = xtrymalloc (strlen (origuri->original) + + (locuri->off_path - locuri->off_host) + 1); + if (!newurl) + { + err = gpg_error_from_syserror (); + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + return err; + } + /* Build new URL from + * uriguri: scheme userinfo ---- ---- path rest + * locuri: ------ -------- host port ---- ---- + */ + p = newurl; + memcpy (p, origuri->original, origuri->off_host); + p += origuri->off_host; + memcpy (p, locuri->original + locuri->off_host, + (locuri->off_path - locuri->off_host)); + p += locuri->off_path - locuri->off_host; + strcpy (p, origuri->original + origuri->off_path); + + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + if (!info->silent) + log_info (_("redirection changed to '%s'\n"), newurl); + } + + *r_url = newurl; + return 0; +} + + +/* Return string describing the http STATUS. Returns an empty string + * for an unknown status. */ +const char * +http_status2string (unsigned int status) +{ + switch (status) + { + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP version Not Supported"; + case 506: return "Variant Also Negation"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + } + + return ""; +} diff --git a/dirmngr/http.h b/dirmngr/http.h index a86abbee7..492e86726 100644 --- a/dirmngr/http.h +++ b/dirmngr/http.h @@ -58,6 +58,8 @@ struct parsed_uri_s char *auth; /* username/password for basic auth. */ char *host; /* Host (converted to lowercase). */ unsigned short port; /* Port (always set if the host is set). */ + unsigned short off_host; /* Offset to the HOST respective PATH parts */ + unsigned short off_path; /* in the original URI buffer. */ char *path; /* Path. */ uri_tuple_t params; /* ";xxxxx" */ uri_tuple_t query; /* "?xxx=yyy" */ @@ -100,6 +102,21 @@ typedef struct http_session_s *http_session_t; struct http_context_s; typedef struct http_context_s *http_t; +/* An object used to track redirection infos. */ +struct http_redir_info_s +{ + unsigned int redirects_left; /* Number of still possible redirects. */ + const char *orig_url; /* The original requested URL. */ + unsigned int orig_onion:1; /* Original request was an onion address. */ + unsigned int orig_https:1; /* Original request was a http address. */ + unsigned int silent:1; /* No diagnostics. */ + unsigned int allow_downgrade:1;/* Allow a downgrade from https to http. */ + unsigned int trust_location:1; /* Trust the received Location header. */ +}; +typedef struct http_redir_info_s http_redir_info_t; + + + /* A TLS verify callback function. */ typedef gpg_error_t (*http_verify_cb_t) (void *opaque, http_t http, @@ -176,5 +193,11 @@ gpg_error_t http_verify_server_credentials (http_session_t sess); char *http_escape_string (const char *string, const char *specials); char *http_escape_data (const void *data, size_t datalen, const char *specials); +gpg_error_t http_prepare_redirect (http_redir_info_t *info, + unsigned int status_code, + const char *location, char **r_url); + +const char *http_status2string (unsigned int status); + #endif /*GNUPG_COMMON_HTTP_H*/ diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c index c1ecafb58..3651ca7db 100644 --- a/dirmngr/ks-action.c +++ b/dirmngr/ks-action.c @@ -88,7 +88,7 @@ ks_action_help (ctrl_t ctrl, const char *url) return err; } - /* Call all engines to give them a chance to print a help sting. */ + /* Call all engines to give them a chance to print a help string. */ err = ks_hkp_help (ctrl, parsed_uri); if (!err) err = ks_http_help (ctrl, parsed_uri); diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index 630309991..4d660b87e 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -35,6 +35,7 @@ # include <netdb.h> #endif /*!HAVE_W32_SYSTEM*/ +#include <npth.h> #include "dirmngr.h" #include "misc.h" #include "../common/userids.h" @@ -108,6 +109,8 @@ struct hostinfo_s resolved from a pool name and its allocated size.*/ static hostinfo_t *hosttable; static int hosttable_size; +/* A mutex used to serialize access to the hosttable. */ +static npth_mutex_t hosttable_lock; /* The number of host slots we initially allocate for HOSTTABLE. */ #define INITIAL_HOSTTABLE_SIZE 50 @@ -756,9 +759,15 @@ ks_hkp_mark_host (ctrl_t ctrl, const char *name, int alive) if (!name || !*name || !strcmp (name, "localhost")) return 0; + if (npth_mutex_lock (&hosttable_lock)) + log_fatal ("failed to acquire mutex\n"); + idx = find_hostinfo (name); if (idx == -1) - return gpg_error (GPG_ERR_NOT_FOUND); + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } hi = hosttable[idx]; if (alive && hi->dead) @@ -817,6 +826,10 @@ ks_hkp_mark_host (ctrl_t ctrl, const char *name, int alive) } } + leave: + if (npth_mutex_unlock (&hosttable_lock)) + log_fatal ("failed to release mutex\n"); + return err; } @@ -837,7 +850,9 @@ ks_hkp_print_hosttable (ctrl_t ctrl) if (err) return err; - /* FIXME: We need a lock for the hosttable. */ + if (npth_mutex_lock (&hosttable_lock)) + log_fatal ("failed to acquire mutex\n"); + curtime = gnupg_get_time (); for (idx=0; idx < hosttable_size; idx++) if ((hi=hosttable[idx])) @@ -930,12 +945,12 @@ ks_hkp_print_hosttable (ctrl_t ctrl) diedstr? ")":"" ); xfree (died); if (err) - return err; + goto leave; if (hi->cname) err = ks_printf_help (ctrl, " . %s", hi->cname); if (err) - return err; + goto leave; if (hi->pool) { @@ -950,14 +965,21 @@ ks_hkp_print_hosttable (ctrl_t ctrl) put_membuf( &mb, "", 1); p = get_membuf (&mb, NULL); if (!p) - return gpg_error_from_syserror (); + { + err = gpg_error_from_syserror (); + goto leave; + } err = ks_print_help (ctrl, p); xfree (p); if (err) - return err; + goto leave; } } - return 0; + + leave: + if (npth_mutex_unlock (&hosttable_lock)) + log_fatal ("failed to release mutex\n"); + return err; } @@ -1026,9 +1048,16 @@ make_host_part (ctrl_t ctrl, protocol = KS_PROTOCOL_HKP; } + if (npth_mutex_lock (&hosttable_lock)) + log_fatal ("failed to acquire mutex\n"); + portstr[0] = 0; err = map_host (ctrl, host, srvtag, force_reselect, protocol, &hostname, portstr, r_httpflags, r_httphost); + + if (npth_mutex_unlock (&hosttable_lock)) + log_fatal ("failed to release mutex\n"); + if (err) return err; @@ -1102,6 +1131,9 @@ ks_hkp_housekeeping (time_t curtime) int idx; hostinfo_t hi; + if (npth_mutex_lock (&hosttable_lock)) + log_fatal ("failed to acquire mutex\n"); + for (idx=0; idx < hosttable_size; idx++) { hi = hosttable[idx]; @@ -1118,6 +1150,9 @@ ks_hkp_housekeeping (time_t curtime) log_info ("resurrected host '%s'", hi->name); } } + + if (npth_mutex_unlock (&hosttable_lock)) + log_fatal ("failed to release mutex\n"); } @@ -1129,6 +1164,9 @@ ks_hkp_reload (void) int idx, count; hostinfo_t hi; + if (npth_mutex_lock (&hosttable_lock)) + log_fatal ("failed to acquire mutex\n"); + for (idx=count=0; idx < hosttable_size; idx++) { hi = hosttable[idx]; @@ -1142,6 +1180,9 @@ ks_hkp_reload (void) } if (count) log_info ("number of resurrected hosts: %d", count); + + if (npth_mutex_unlock (&hosttable_lock)) + log_fatal ("failed to release mutex\n"); } @@ -1160,18 +1201,21 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr, gpg_error_t err; http_session_t session = NULL; http_t http = NULL; - int redirects_left = MAX_REDIRECTS; + http_redir_info_t redirinfo = { MAX_REDIRECTS }; estream_t fp = NULL; char *request_buffer = NULL; parsed_uri_t uri = NULL; - int is_onion; *r_fp = NULL; err = http_parse_uri (&uri, request, 0); if (err) goto leave; - is_onion = uri->onion; + redirinfo.orig_url = request; + redirinfo.orig_onion = uri->onion; + redirinfo.allow_downgrade = 1; + /* FIXME: I am not sure whey we allow a downgrade for hkp requests. + * Needs at least an explanation here.. */ err = http_session_new (&session, httphost, ((ctrl->http_no_crl? HTTP_FLAG_NO_CRL : 0) @@ -1252,45 +1296,18 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr, case 302: case 307: { - const char *s = http_get_header (http, "Location"); - - log_info (_("URL '%s' redirected to '%s' (%u)\n"), - request, s?s:"[none]", http_get_status_code (http)); - if (s && *s && redirects_left-- ) - { - if (is_onion) - { - /* Make sure that an onion address only redirects to - * another onion address. */ - http_release_parsed_uri (uri); - uri = NULL; - err = http_parse_uri (&uri, s, 0); - if (err) - goto leave; - - if (! uri->onion) - { - err = gpg_error (GPG_ERR_FORBIDDEN); - goto leave; - } - } + xfree (request_buffer); + err = http_prepare_redirect (&redirinfo, http_get_status_code (http), + http_get_header (http, "Location"), + &request_buffer); + if (err) + goto leave; - xfree (request_buffer); - request_buffer = xtrystrdup (s); - if (request_buffer) - { - request = request_buffer; - http_close (http, 0); - http = NULL; - goto once_more; - } - err = gpg_error_from_syserror (); - } - else - err = gpg_error (GPG_ERR_NO_DATA); - log_error (_("too many redirections\n")); + request = request_buffer; + http_close (http, 0); + http = NULL; } - goto leave; + goto once_more; case 501: err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); @@ -1335,12 +1352,12 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr, down to zero. */ static int handle_send_request_error (ctrl_t ctrl, gpg_error_t err, const char *request, - unsigned int *tries_left) + unsigned int http_status, unsigned int *tries_left) { int retry = 0; /* Fixme: Should we disable all hosts of a protocol family if a - * request for an address of that familiy returned ENETDOWN? */ + * request for an address of that family returned ENETDOWN? */ switch (gpg_err_code (err)) { @@ -1378,6 +1395,27 @@ handle_send_request_error (ctrl_t ctrl, gpg_error_t err, const char *request, } break; + case GPG_ERR_NO_DATA: + { + switch (http_status) + { + case 502: /* Bad Gateway */ + log_info ("marking host dead due to a %u (%s)\n", + http_status, http_status2string (http_status)); + if (mark_host_dead (request) && *tries_left) + retry = 1; + break; + + case 503: /* Service Unavailable */ + case 504: /* Gateway Timeout */ + log_info ("selecting a different host due to a %u (%s)", + http_status, http_status2string (http_status)); + retry = 1; + break; + } + } + break; + default: break; } @@ -1399,13 +1437,14 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, { gpg_error_t err; KEYDB_SEARCH_DESC desc; - char fprbuf[2+40+1]; + char fprbuf[2+64+1]; char *hostport = NULL; char *request = NULL; estream_t fp = NULL; int reselect; unsigned int httpflags; char *httphost = NULL; + unsigned int http_status; unsigned int tries = SEND_REQUEST_RETRIES; *r_fp = NULL; @@ -1417,6 +1456,7 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, err = classify_user_id (pattern, &desc, 1); if (err) return err; + log_assert (desc.fprlen <= 64); switch (desc.mode) { case KEYDB_SEARCH_MODE_EXACT: @@ -1434,17 +1474,10 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, (ulong)desc.u.kid[0], (ulong)desc.u.kid[1]); pattern = fprbuf; break; - case KEYDB_SEARCH_MODE_FPR16: - fprbuf[0] = '0'; - fprbuf[1] = 'x'; - bin2hex (desc.u.fpr, 16, fprbuf+2); - pattern = fprbuf; - break; - case KEYDB_SEARCH_MODE_FPR20: case KEYDB_SEARCH_MODE_FPR: fprbuf[0] = '0'; fprbuf[1] = 'x'; - bin2hex (desc.u.fpr, 20, fprbuf+2); + bin2hex (desc.u.fpr, desc.fprlen, fprbuf+2); pattern = fprbuf; break; default: @@ -1487,14 +1520,20 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, /* Send the request. */ err = send_request (ctrl, request, hostport, httphost, httpflags, - NULL, NULL, &fp, r_http_status); - if (handle_send_request_error (ctrl, err, request, &tries)) + NULL, NULL, &fp, &http_status); + if (handle_send_request_error (ctrl, err, request, http_status, &tries)) { reselect = 1; goto again; } + if (r_http_status) + *r_http_status = http_status; if (err) - goto leave; + { + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + dirmngr_status (ctrl, "SOURCE", hostport, NULL); + goto leave; + } err = dirmngr_status (ctrl, "SOURCE", hostport, NULL); if (err) @@ -1541,7 +1580,7 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) { gpg_error_t err; KEYDB_SEARCH_DESC desc; - char kidbuf[2+40+1]; + char kidbuf[2+64+1]; const char *exactname = NULL; char *searchkey = NULL; char *hostport = NULL; @@ -1550,6 +1589,7 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) int reselect; char *httphost = NULL; unsigned int httpflags; + unsigned int http_status; unsigned int tries = SEND_REQUEST_RETRIES; *r_fp = NULL; @@ -1561,6 +1601,7 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) err = classify_user_id (keyspec, &desc, 1); if (err) return err; + log_assert (desc.fprlen <= 64); switch (desc.mode) { case KEYDB_SEARCH_MODE_SHORT_KID: @@ -1570,21 +1611,21 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) snprintf (kidbuf, sizeof kidbuf, "0x%08lX%08lX", (ulong)desc.u.kid[0], (ulong)desc.u.kid[1]); break; - case KEYDB_SEARCH_MODE_FPR20: case KEYDB_SEARCH_MODE_FPR: - /* This is a v4 fingerprint. */ + if (desc.fprlen < 20) + { + log_error ("HKP keyservers do not support v3 fingerprints\n"); + return gpg_error (GPG_ERR_INV_USER_ID); + } kidbuf[0] = '0'; kidbuf[1] = 'x'; - bin2hex (desc.u.fpr, 20, kidbuf+2); + bin2hex (desc.u.fpr, desc.fprlen, kidbuf+2); break; case KEYDB_SEARCH_MODE_EXACT: exactname = desc.u.name; break; - case KEYDB_SEARCH_MODE_FPR16: - log_error ("HKP keyservers do not support v3 fingerprints\n"); - /* fall through */ default: return gpg_error (GPG_ERR_INV_USER_ID); } @@ -1622,14 +1663,18 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) /* Send the request. */ err = send_request (ctrl, request, hostport, httphost, httpflags, - NULL, NULL, &fp, NULL); - if (handle_send_request_error (ctrl, err, request, &tries)) + NULL, NULL, &fp, &http_status); + if (handle_send_request_error (ctrl, err, request, http_status, &tries)) { reselect = 1; goto again; } if (err) - goto leave; + { + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + dirmngr_status (ctrl, "SOURCE", hostport, NULL); + goto leave; + } err = dirmngr_status (ctrl, "SOURCE", hostport, NULL); if (err) @@ -1693,6 +1738,7 @@ ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen) int reselect; char *httphost = NULL; unsigned int httpflags; + unsigned int http_status; unsigned int tries = SEND_REQUEST_RETRIES; parm.datastring = NULL; @@ -1731,8 +1777,8 @@ ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen) /* Send the request. */ err = send_request (ctrl, request, hostport, httphost, 0, - put_post_cb, &parm, &fp, NULL); - if (handle_send_request_error (ctrl, err, request, &tries)) + put_post_cb, &parm, &fp, &http_status); + if (handle_send_request_error (ctrl, err, request, http_status, &tries)) { reselect = 1; goto again; @@ -1749,3 +1795,13 @@ ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen) xfree (httphost); return err; } + +void +ks_hkp_init (void) +{ + int err; + + err = npth_mutex_init (&hosttable_lock, NULL); + if (err) + log_fatal ("error initializing mutex: %s\n", strerror (err)); +} diff --git a/dirmngr/ks-engine-http.c b/dirmngr/ks-engine-http.c index 9e6b9e1f5..0f3e2db4a 100644 --- a/dirmngr/ks-engine-http.c +++ b/dirmngr/ks-engine-http.c @@ -74,17 +74,18 @@ ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags, http_session_t session = NULL; unsigned int session_flags; http_t http = NULL; - int redirects_left = MAX_REDIRECTS; + http_redir_info_t redirinfo = { MAX_REDIRECTS }; estream_t fp = NULL; char *request_buffer = NULL; parsed_uri_t uri = NULL; - int is_onion, is_https; err = http_parse_uri (&uri, url, 0); if (err) goto leave; - is_onion = uri->onion; - is_https = uri->use_tls; + redirinfo.orig_url = url; + redirinfo.orig_onion = uri->onion; + redirinfo.orig_https = uri->use_tls; + redirinfo.allow_downgrade = !!(flags & KS_HTTP_FETCH_ALLOW_DOWNGRADE); /* By default we only use the system provided certificates with this * fetch command. */ @@ -158,53 +159,20 @@ ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags, case 302: case 307: { - const char *s = http_get_header (http, "Location"); - - log_info (_("URL '%s' redirected to '%s' (%u)\n"), - url, s?s:"[none]", http_get_status_code (http)); - if (s && *s && redirects_left-- ) - { - if (is_onion || is_https) - { - /* Make sure that an onion address only redirects to - * another onion address, or that a https address - * only redirects to a https address. */ - http_release_parsed_uri (uri); - uri = NULL; - err = http_parse_uri (&uri, s, 0); - if (err) - goto leave; - - if (is_onion && !uri->onion) - { - err = gpg_error (GPG_ERR_FORBIDDEN); - goto leave; - } - if (!(flags & KS_HTTP_FETCH_ALLOW_DOWNGRADE) - && is_https && !uri->use_tls) - { - err = gpg_error (GPG_ERR_FORBIDDEN); - goto leave; - } - } - - xfree (request_buffer); - request_buffer = xtrystrdup (s); - if (request_buffer) - { - url = request_buffer; - http_close (http, 0); - http = NULL; - http_session_release (session); - goto once_more; - } - err = gpg_error_from_syserror (); - } - else - err = gpg_error (GPG_ERR_NO_DATA); - log_error (_("too many redirections\n")); + xfree (request_buffer); + err = http_prepare_redirect (&redirinfo, http_get_status_code (http), + http_get_header (http, "Location"), + &request_buffer); + if (err) + goto leave; + + url = request_buffer; + http_close (http, 0); + http = NULL; + http_session_release (session); + session = NULL; } - goto leave; + goto once_more; default: log_error (_("error accessing '%s': http status %u\n"), diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c index f50ba50d0..d6af26ec4 100644 --- a/dirmngr/ks-engine-ldap.c +++ b/dirmngr/ks-engine-ldap.c @@ -376,8 +376,6 @@ keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact) (ulong) desc.u.kid[0], (ulong) desc.u.kid[1]); break; - case KEYDB_SEARCH_MODE_FPR16: - case KEYDB_SEARCH_MODE_FPR20: case KEYDB_SEARCH_MODE_FPR: case KEYDB_SEARCH_MODE_ISSUER: case KEYDB_SEARCH_MODE_ISSUER_SN: @@ -1694,26 +1692,16 @@ extract_attributes (LDAPMod ***modlist, char *line) if (is_pub || is_sub) { - char *size = fields[2]; - int val = atoi (size); - size = NULL; + char padded[6]; + int val; - if (val > 0) - { - /* We zero pad this on the left to make PGP happy. */ - char padded[6]; - if (val < 99999 && val > 0) - { - snprintf (padded, sizeof padded, "%05u", val); - size = padded; - } - } - - if (size) - { - if (is_pub || is_sub) - modlist_add (modlist, "pgpKeySize", size); - } + val = atoi (fields[2]); + if (val < 99999 && val > 0) + { + /* We zero pad this on the left to make PGP happy. */ + snprintf (padded, sizeof padded, "%05u", val); + modlist_add (modlist, "pgpKeySize", padded); + } } if (is_pub) @@ -1736,10 +1724,7 @@ extract_attributes (LDAPMod ***modlist, char *line) } if (algo) - { - if (is_pub) - modlist_add (modlist, "pgpKeyType", algo); - } + modlist_add (modlist, "pgpKeyType", algo); } if (is_pub || is_sub || is_sig) diff --git a/dirmngr/ldap.c b/dirmngr/ldap.c index cb3c0b763..a04bb97a2 100644 --- a/dirmngr/ldap.c +++ b/dirmngr/ldap.c @@ -388,7 +388,7 @@ parse_one_pattern (const char *pattern) } /* Take the string STRING and escape it according to the URL rules. - Retun a newly allocated string. */ + Return a newly allocated string. */ static char * escape4url (const char *string) { diff --git a/dirmngr/misc.c b/dirmngr/misc.c index 1270b834d..9cedf911c 100644 --- a/dirmngr/misc.c +++ b/dirmngr/misc.c @@ -515,7 +515,7 @@ host_and_port_from_url (const char *url, int *port) if ((p = strchr (buf, '/'))) *p++ = 0; strlwr (buf); - if ((p = strchr (p, ':'))) + if ((p = strchr (buf, ':'))) { *p++ = 0; *port = atoi (p); @@ -637,7 +637,7 @@ armor_data (char **r_string, const void *data, size_t datalen) } -/* Copy all data from IN to OUT. OUT may be NULL to use this fucntion +/* Copy all data from IN to OUT. OUT may be NULL to use this function * as a dummy reader. */ gpg_error_t copy_stream (estream_t in, estream_t out) diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c index ca28960e4..79c252d87 100644 --- a/dirmngr/ocsp.c +++ b/dirmngr/ocsp.c @@ -343,7 +343,7 @@ validate_responder_cert (ctrl_t ctrl, ksba_cert_t cert, Note, that in theory we could simply ask the client via an inquire to validate a certificate but this might involve - calling DirMngr again recursivly - we can't do that as of now + calling DirMngr again recursively - we can't do that as of now (neither DirMngr nor gpgsm have the ability for concurrent access to DirMngr. */ @@ -391,7 +391,7 @@ check_signature_core (ctrl_t ctrl, ksba_cert_t cert, gcry_sexp_t s_sig, } -/* Check the signature of an OCSP repsonse. OCSP is the context, +/* Check the signature of an OCSP response. OCSP is the context, S_SIG the signature value and MD the handle of the hash we used for the response. This function automagically finds the correct public key. If SIGNER_FPR_LIST is not NULL, the default OCSP reponder has been @@ -653,6 +653,33 @@ ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr, if (err) goto leave; + /* It is sometimes useful to know the responder ID. */ + if (opt.verbose) + { + char *resp_name; + ksba_sexp_t resp_keyid; + + err = ksba_ocsp_get_responder_id (ocsp, &resp_name, &resp_keyid); + if (err) + log_info (_("error getting responder ID: %s\n"), gpg_strerror (err)); + else + { + log_info ("responder id: "); + if (resp_name) + log_printf ("'/%s' ", resp_name); + if (resp_keyid) + { + log_printf ("{"); + dump_serial (resp_keyid); + log_printf ("} "); + } + log_printf ("\n"); + } + ksba_free (resp_name); + ksba_free (resp_keyid); + err = 0; + } + /* We got a useful answer, check that the answer has a valid signature. */ sigval = ksba_ocsp_get_sig_val (ocsp, produced_at); if (!sigval || !*produced_at) @@ -761,7 +788,7 @@ ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr, err = gpg_error (GPG_ERR_TIME_CONFLICT); } - /* Check that we are not beyound NEXT_UPDATE (plus some extra time). */ + /* Check that we are not beyond NEXT_UPDATE (plus some extra time). */ if (*next_update) { gnupg_copy_time (tmp_time, next_update); diff --git a/dirmngr/server.c b/dirmngr/server.c index 2519fd601..4a242539b 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -731,7 +731,7 @@ cmd_dns_cert (assuan_context_t ctx, char *line) /* We lowercase ascii characters but the DANE I-D does not allow this. FIXME: Check after the release of the RFC whether to change this. */ - mbox = mailbox_from_userid (line); + mbox = mailbox_from_userid (line, 0); if (!mbox || !(domain = strchr (mbox, '@'))) { err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id"); @@ -837,8 +837,11 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) gpg_error_t err = 0; char *mbox = NULL; char *domainbuf = NULL; - char *domain; /* Points to mbox or domainbuf. */ - char *domain_orig;/* Points to mbox. */ + char *domain; /* Points to mbox or domainbuf. This is used to + * connect to the host. */ + char *domain_orig;/* Points to mbox. This is the used for the + * query; i.e. the domain part of the + * addrspec. */ char sha1buf[20]; char *uri = NULL; char *encodedhash = NULL; @@ -847,6 +850,7 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) int is_wkd_query; /* True if this is a real WKD query. */ int no_log = 0; char portstr[20] = { 0 }; + int subdomain_mode = 0; opt_submission_addr = has_option (line, "--submission-address"); opt_policy_flags = has_option (line, "--policy-flags"); @@ -855,7 +859,7 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) line = skip_options (line); is_wkd_query = !(opt_policy_flags || opt_submission_addr); - mbox = mailbox_from_userid (line); + mbox = mailbox_from_userid (line, 0); if (!mbox || !(domain = strchr (mbox, '@'))) { err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id"); @@ -864,7 +868,8 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) *domain++ = 0; domain_orig = domain; - /* First check whether we already know that the domain does not + + /* Let's check whether we already know that the domain does not * support WKD. */ if (is_wkd_query) { @@ -875,8 +880,41 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) } } - /* Check for SRV records. */ - if (1) + + /* First try the new "openpgp" subdomain. We check that the domain + * is valid because it is later used as an unescaped filename part + * of the URI. */ + if (is_valid_domain_name (domain_orig)) + { + dns_addrinfo_t aibuf; + + domainbuf = strconcat ( "openpgpkey.", domain_orig, NULL); + if (!domainbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* FIXME: We should put a cache into dns-stuff because the same + * query (with a different port and socket type, though) will be + * done later by http function. */ + err = resolve_dns_name (ctrl, domainbuf, 0, 0, 0, &aibuf, NULL); + if (err) + { + err = 0; + xfree (domainbuf); + domainbuf = NULL; + } + else /* Got a subdomain. */ + { + free_dns_addrinfo (aibuf); + subdomain_mode = 1; + domain = domainbuf; + } + } + + /* Check for SRV records unless we have a subdomain. */ + if (!subdomain_mode) { struct srventry *srvs; unsigned int srvscount; @@ -931,6 +969,7 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) xfree (srvs); } + /* Prepare the hash of the local part. */ gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, mbox, strlen (mbox)); encodedhash = zb32_encode (sha1buf, 8*20); if (!encodedhash) @@ -944,7 +983,10 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) uri = strconcat ("https://", domain, portstr, - "/.well-known/openpgpkey/submission-address", + "/.well-known/openpgpkey/", + subdomain_mode? domain_orig : "", + subdomain_mode? "/" : "", + "submission-address", NULL); } else if (opt_policy_flags) @@ -952,24 +994,39 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) uri = strconcat ("https://", domain, portstr, - "/.well-known/openpgpkey/policy", + "/.well-known/openpgpkey/", + subdomain_mode? domain_orig : "", + subdomain_mode? "/" : "", + "policy", NULL); } else { - uri = strconcat ("https://", - domain, - portstr, - "/.well-known/openpgpkey/hu/", - encodedhash, - NULL); - no_log = 1; - if (uri) + char *escapedmbox; + + escapedmbox = http_escape_string (mbox, "%;?&="); + if (escapedmbox) { - err = dirmngr_status_printf (ctrl, "SOURCE", "https://%s%s", - domain, portstr); - if (err) - goto leave; + uri = strconcat ("https://", + domain, + portstr, + "/.well-known/openpgpkey/", + subdomain_mode? domain_orig : "", + subdomain_mode? "/" : "", + "hu/", + encodedhash, + "?l=", + escapedmbox, + NULL); + xfree (escapedmbox); + no_log = 1; + if (uri) + { + err = dirmngr_status_printf (ctrl, "SOURCE", "https://%s%s", + domain, portstr); + if (err) + goto leave; + } } } if (!uri) @@ -2680,6 +2737,20 @@ cmd_reloaddirmngr (assuan_context_t ctx, char *line) } +static const char hlp_flushcrls[] = + "FLUSHCRLS\n" + "\n" + "Remove all cached CRLs from memory and\n" + "the file system."; +static gpg_error_t +cmd_flushcrls (assuan_context_t ctx, char *line) +{ + (void)line; + + return leave_cmd (ctx, crl_cache_flush () ? GPG_ERR_GENERAL : 0); +} + + /* Tell the assuan library about our commands. */ static int @@ -2710,6 +2781,7 @@ register_commands (assuan_context_t ctx) { "LOADSWDB", cmd_loadswdb, hlp_loadswdb }, { "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr }, { "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr }, + { "FLUSHCRLS", cmd_flushcrls, hlp_flushcrls }, { NULL, NULL } }; int i, j, rc; diff --git a/dirmngr/t-http-basic.c b/dirmngr/t-http-basic.c new file mode 100644 index 000000000..edf82efb9 --- /dev/null +++ b/dirmngr/t-http-basic.c @@ -0,0 +1,199 @@ +/* t-http-basic.c - Basic regression tests for http.c + * Copyright (C) 2018 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <stdlib.h> + +#include "../common/util.h" +#include "t-support.h" +#include "http.h" + +#define PGM "t-http-basic" + + +static void +test_http_prepare_redirect (void) +{ + static struct { + const char *url; + const char *location; + const char *expect_url; + gpg_error_t expect_err; + } tests[] = { + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + NULL, + "", + GPG_ERR_NO_DATA + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "", + "", + GPG_ERR_NO_DATA + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "foo//bla", + "", + GPG_ERR_BAD_URI + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://foo.gnupg.org:8080/.not-so-well-known/openpgpkey/hu/12345678", + "http://foo.gnupg.org:8080/.well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http:///.no-so-well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + GPG_ERR_BAD_URI + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org:8080/.not-so-well-known/openpgpkey/hu/12345678", + "http://gnupg.org:8080/.not-so-well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org:8/.not-so-well-known/openpgpkey/hu/12345678", + "http://gnupg.org:8/.not-so-well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org:/.no-so-well-known/openpgpkey/hu/12345678", + "http://gnupg.org:/.no-so-well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/", + "http://gnupg.org/", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.net", + "http://gnupg.net/.well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org", + "http://gnupg.org", + "http://gnupg.org", + 0 + }, + { + "http://gnupg.org", + "http://foo.gnupg.org", + "http://foo.gnupg.org", + 0 + }, + { + "http://gnupg.org/", + "http://foo.gnupg.org", + "http://foo.gnupg.org/", + 0 + }, + { + "http://gnupg.org", + "http://foo.gnupg.org/", + "http://foo.gnupg.org", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/something-else", + "http://gnupg.org/something-else", + 0 + }, + }; + int tidx; + http_redir_info_t ri; + gpg_error_t err; + char *newurl; + + err = http_prepare_redirect (NULL, 301, tests[0].location, &newurl); + if (gpg_err_code (err) != GPG_ERR_INV_ARG) + fail (0); + memset (&ri, 0, sizeof ri); + err = http_prepare_redirect (&ri, 301, tests[0].location, &newurl); + if (gpg_err_code (err) != GPG_ERR_INV_ARG) + fail (0); + memset (&ri, 0, sizeof ri); + ri.silent = 1; + ri.orig_url = "http://example.org"; + err = http_prepare_redirect (&ri, 301, tests[0].location, &newurl); + if (gpg_err_code (err) != GPG_ERR_NO_DATA) + fail (0); + + for (tidx = 0; tidx < DIM (tests); tidx++) + { + memset (&ri, 0, sizeof ri); + ri.silent = 1; + ri.redirects_left = 1; + ri.orig_url = tests[tidx].url; + + err = http_prepare_redirect (&ri, 301, tests[tidx].location, &newurl); + if (err && newurl) + fail (tidx); + if (err && gpg_err_code (err) != tests[tidx].expect_err) + fail (tidx); + if (err) + continue; + if (!newurl) + fail (tidx); + if (strcmp (tests[tidx].expect_url, newurl)) + { + fprintf (stderr, "want: '%s'\n", tests[tidx].expect_url); + fprintf (stderr, "got : '%s'\n", newurl); + fail (tidx); + } + + xfree (newurl); + } +} + + +int +main (int argc, char **argv) +{ + (void)argc; + (void)argv; + + test_http_prepare_redirect (); + + return 0; +} diff --git a/dirmngr/t-http.c b/dirmngr/t-http.c index 2fc0a465c..70d7f3fac 100644 --- a/dirmngr/t-http.c +++ b/dirmngr/t-http.c @@ -137,7 +137,7 @@ my_http_tls_verify_cb (void *opaque, (void)session; (void)http_flags; - /* Get the peer's certs fron ntbtls. */ + /* Get the peer's certs from ntbtls. */ for (idx = 0; (cert = ntbtls_x509_get_peer_cert (tls_context, idx)); idx++) { @@ -394,9 +394,9 @@ main (int argc, char **argv) else { printf ("Auth : %s\n", uri->auth? uri->auth:"[none]"); - printf ("Host : %s\n", uri->host); + printf ("Host : %s (off=%hu)\n", uri->host, uri->off_host); printf ("Port : %u\n", uri->port); - printf ("Path : %s\n", uri->path); + printf ("Path : %s (off=%hu)\n", uri->path, uri->off_path); for (r = uri->params; r; r = r->next) { printf ("Params: %s", r->name); diff --git a/dirmngr/workqueue.c b/dirmngr/workqueue.c index 2cb8573e8..a47cdebc8 100644 --- a/dirmngr/workqueue.c +++ b/dirmngr/workqueue.c @@ -116,7 +116,7 @@ workqueue_add_task (wqtask_t func, const char *args, unsigned int session_id, /* Run the task described by ITEM. ITEM must have been detached from - * the workqueue; its ownership is transferred to this fucntion. */ + * the workqueue; its ownership is transferred to this function. */ static void run_a_task (ctrl_t ctrl, wqitem_t item) { diff --git a/doc/DETAILS b/doc/DETAILS index eb6d7dd4b..74a63ef00 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -59,7 +59,7 @@ described here. - uat :: User attribute (same as user id except for field 10). - sig :: Signature - rev :: Revocation signature - - rvs :: Recocation signature (standalone) [since 2.2.9] + - rvs :: Revocation signature (standalone) [since 2.2.9] - fpr :: Fingerprint (fingerprint is in field 10) - pkd :: Public key data [*] - grp :: Keygrip @@ -126,7 +126,7 @@ described here. *** Field 4 - Public key algorithm The values here are those from the OpenPGP specs or if they are - greather than 255 the algorithm ids as used by Libgcrypt. + greater than 255 the algorithm ids as used by Libgcrypt. *** Field 5 - KeyID @@ -544,7 +544,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: *** DECRYPTION_KEY <fpr> <fpr2> <otrust> This line is emitted when a public key decryption succeeded in providing a session key. <fpr> is the hexified fingerprint of the - actual key used for descryption. <fpr2> is the fingerprint of the + actual key used for decryption. <fpr2> is the fingerprint of the primary key. <otrust> is the letter with the ownertrust; this is in general a 'u' which stands for ultimately trusted. *** DECRYPTION_INFO <mdc_method> <sym_algo> [<aead_algo>] @@ -700,7 +700,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: - 0 :: No specific reason given - 1 :: Not Found - - 2 :: Ambigious specification + - 2 :: Ambiguous specification - 3 :: Wrong key usage - 4 :: Key revoked - 5 :: Key expired @@ -1016,7 +1016,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: - 2 :: bad PIN *** SC_OP_SUCCESS - A smart card operaion succeeded. This status is only printed for + A smart card operation succeeded. This status is only printed for certain operation and is mostly useful to check whether a PIN change really worked. @@ -1073,7 +1073,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: Deleting a key failed. Reason codes are: - 1 :: No such key - 2 :: Must delete secret key first - - 3 :: Ambigious specification + - 3 :: Ambiguous specification - 4 :: Key is stored on a smartcard. *** PROGRESS <what> <char> <cur> <total> [<units>] diff --git a/doc/HACKING b/doc/HACKING index 17c58269b..4781bf62c 100644 --- a/doc/HACKING +++ b/doc/HACKING @@ -150,7 +150,7 @@ Note that such a comment will be removed if the git commit option if ( 42 == foo ) #+end_src this is harder to read and modern compilers are pretty good in - detecing accidential assignments. It is also suggested not to + detecing accidental assignments. It is also suggested not to compare to 0 or NULL but to test the value direct or with a '!'; this makes it easier to see that a boolean test is done. - We use our own printf style functions like =es_printf=, and @@ -342,7 +342,7 @@ Note that such a comment will be removed if the git commit option - g10/main.h :: Prototypes and some constants - g10/mainproc.c :: Message processing - g10/armor.c :: Ascii armor filter - - g10/mdfilter.c :: Filter to calculate hashs + - g10/mdfilter.c :: Filter to calculate hashes - g10/textfilter.c :: Filter to handle CR/LF and trailing white space - g10/cipher.c :: En-/Decryption filter - g10/misc.c :: Utility functions @@ -395,7 +395,7 @@ The *secure versions allocate memory in the secure memory. That is, swapping out of this memory is avoided and is gets overwritten on free. Use this for passphrases, session keys and other sensitive material. This memory set aside for secure memory is linited to a few -k. In general the function don't print a memeory message and +k. In general the function don't print a memory message and terminate the process if there is not enough memory available. The "try" versions of the functions return NULL instead. diff --git a/doc/Makefile.am b/doc/Makefile.am index cb69cd993..0720dd366 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -69,7 +69,7 @@ nobase_dist_doc_DATA = FAQ DETAILS HACKING DCO TRANSLATE OpenPGP KEYSERVER \ gnupg_TEXINFOS = \ gpg.texi gpgsm.texi gpg-agent.texi scdaemon.texi instguide.texi \ tools.texi debugging.texi glossary.texi contrib.texi gpl.texi \ - sysnotes.texi dirmngr.texi wks.texi \ + sysnotes.texi dirmngr.texi wks.texi gpg-card.texi \ gnupg-module-overview.svg \ gnupg-card-architecture.fig \ howtos.texi howto-create-a-server-cert.texi @@ -89,12 +89,13 @@ YAT2M_OPTIONS = -I $(srcdir) \ --release "GnuPG @PACKAGE_VERSION@" --source "GNU Privacy Guard 2.2" myman_sources = gnupg7.texi gpg.texi gpgsm.texi gpg-agent.texi \ - dirmngr.texi scdaemon.texi tools.texi wks.texi + dirmngr.texi scdaemon.texi tools.texi wks.texi \ + gpg-card.texi myman_pages = gpgsm.1 gpg-agent.1 dirmngr.8 scdaemon.1 \ watchgnupg.1 gpgconf.1 addgnupghome.8 gpg-preset-passphrase.1 \ gpg-connect-agent.1 gpgparsemail.1 symcryptrun.1 gpgtar.1 \ applygnupgdefaults.8 gpg-wks-client.1 gpg-wks-server.1 \ - dirmngr-client.1 + dirmngr-client.1 gpg-card.1 if USE_GPG2_HACK myman_pages += gpg2.1 gpgv2.1 else @@ -1,5 +1,5 @@ -Add an infor page for watchgnupg. +Add an info page for watchgnupg. > * How to mark a CA certificate as trusted. @@ -57,7 +57,7 @@ or In general you should first import the root certificates and then down to the end user certificate. You may put all into one file and gpgsm -will do the right thing in this case independend of the order. +will do the right thing in this case independent of the order. While verifying a signature, all included certificates are automagically imported. @@ -82,7 +82,7 @@ you get an output like: uid:::::::::CN=Werner Koch,OU=test,O=g10 Code,C=de:: uid:::::::::<[email protected]>:: -This should be familar to advanced gpg-users; see doc/DETAILS in gpg +This should be familiar to advanced gpg-users; see doc/DETAILS in gpg 1.3 (CVS HEAD) for a description of the records. The value in the "grp" tagged record is the so called keygrip and you should find a file ~/.gnupg/private-keys-v1.d/C92DB9CFD588ADE846BE3AC4E7A2E1B11A4A2ADB.key diff --git a/doc/dirmngr.texi b/doc/dirmngr.texi index 76be5286c..f5910a884 100644 --- a/doc/dirmngr.texi +++ b/doc/dirmngr.texi @@ -1096,7 +1096,7 @@ as a binary blob. @c In the end the same fucntionality is used, albeit hidden by a couple @c of indirection and argument and result code mangling. It furthere @c ingetrages OCSP checking depending on options are the way it is -@c called. GPGSM still uses this command but might eventuall switch over +@c called. GPGSM still uses this command but might eventually switch over @c to CHECKCRL and CHECKOCSP so that ISVALID can be retired. @c @c diff --git a/doc/faq.org b/doc/faq.org index ddbeafaf8..2f873e600 100644 --- a/doc/faq.org +++ b/doc/faq.org @@ -1096,7 +1096,7 @@ update this FAQ in the next month. See the section "Changes" for recent updates As of 1.0.3, keys generated with gpg are created with preferences to TWOFISH (and AES since 1.0.4) and that also means that they have the capability to use the new MDC encryption method. This will go into - OpenPGP soon, and is also suppoted by PGP 7. This new method avoids + OpenPGP soon, and is also supported by PGP 7. This new method avoids a (not so new) attack on all email encryption systems. This in turn means that pre-1.0.3 gpg binaries have problems with diff --git a/doc/gnupg.texi b/doc/gnupg.texi index 336414870..78d4669da 100644 --- a/doc/gnupg.texi +++ b/doc/gnupg.texi @@ -142,15 +142,16 @@ the administration and the architecture. * Specify a User ID:: How to Specify a User Id. * Trust Values:: How GnuPG displays trust values. -* Helper Tools:: Description of small helper tools -* Web Key Service:: Tools for the Web Key Service +* Smart Card Tool:: Tool to administrate smart cards. +* Helper Tools:: Description of small helper tools. +* Web Key Service:: Tools for the Web Key Service. * Howtos:: How to do certain things. * System Notes:: Notes pertaining to certain OSes. -* Debugging:: How to solve problems +* Debugging:: How to solve problems. * Copying:: GNU General Public License says - how you can copy and share GnuPG + how you can copy and share GnuPG. * Contributors:: People who have contributed to GnuPG. * Glossary:: Short description of terms used. @@ -186,6 +187,7 @@ the administration and the architecture. @cindex trust values @include trust-values.texi +@include gpg-card.texi @include tools.texi @include wks.texi @@ -237,5 +239,3 @@ the administration and the architecture. @bye - - diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index bcce03329..d518c246b 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -585,16 +585,19 @@ local gpg-agent and use its private keys. This enables decrypting or signing data on a remote machine without exposing the private keys to the remote machine. -@anchor{option --enable-extended-key-format} @item --enable-extended-key-format +@itemx --disable-extended-key-format @opindex enable-extended-key-format -This option creates keys in the extended private key format. Changing -the passphrase of a key will also convert the key to that new format. -Using this option makes the private keys unreadable for gpg-agent -versions before 2.1.12. The advantage of the extended private key -format is that it is text based and can carry additional meta data. -Note that this option also changes the key protection format to use -OCB mode. +@opindex disable-extended-key-format +Since version 2.3 keys are created in the extended private key format. +Changing the passphrase of a key will also convert the key to that new +format. This new key format is supported since GnuPG version 2.1.12 +and thus there should be no need to disable it. The disable option +allows to revert to the old behavior for new keys; be aware that keys +are never migrated back to the old format. However if the enable +option has been used the disable option won't have an effect. The +advantage of the extended private key format is that it is text based +and can carry additional meta data. @anchor{option --enable-ssh-support} @item --enable-ssh-support @@ -669,12 +672,19 @@ For an heavy loaded gpg-agent with many concurrent connection this option avoids sign or decrypt errors due to out of secure memory error returns. +@item --s2k-calibration @var{milliseconds} +@opindex s2k-calibration +Change the default calibration time to @var{milliseconds}. The given +value is capped at 60 seconds; a value of 0 resets to the compiled-in +default. This option is re-read on a SIGHUP (or @code{gpgconf +--reload gpg-agent}) and the S2K count is then re-calibrated. + @item --s2k-count @var{n} @opindex s2k-count Specify the iteration count used to protect the passphrase. This option can be used to override the auto-calibration done by default. -The auto-calibration computes a count which requires 100ms to mangle -a given passphrase. +The auto-calibration computes a count which requires by default 100ms +to mangle a given passphrase. See also @option{--s2k-calibration}. To view the actually used iteration count and the milliseconds required for an S2K operation use: diff --git a/doc/gpg-card.texi b/doc/gpg-card.texi new file mode 100644 index 000000000..aa49f81e7 --- /dev/null +++ b/doc/gpg-card.texi @@ -0,0 +1,517 @@ +@c card-tool.texi - man page for gpg-card-tool +@c Copyright (C) 2019 g10 Code GmbH +@c This is part of the GnuPG manual. +@c For copying conditions, see the file GnuPG.texi. + +@include defs.inc + +@node Smart Card Tool +@chapter Smart Card Tool + +GnuPG comes with tool to administrate smart cards and USB tokens. This +tool is an extension of the @option{--edit-key} command available with +@command{gpg}. + +@menu +* gpg-card:: Administrate smart cards. +@end menu + +@c +@c GPG-CARD-TOOL +@c +@manpage gpg-card.1 +@node gpg-card +@section Administrate smart cards. +@ifset manverb +.B gpg-card +\- Administrate Smart Cards +@end ifset + +@mansect synopsis +@ifset manverb +.B gpg-card +.RI [ options ] +.br +.B gpg-card +.RI [ options ] +.I command +.RI { +.B -- +.I command +.RI } +@end ifset + +@mansect description +The @command{gpg-card} is used to administrate smart cards and USB +tokens. It provides a superset of features from @command{gpg +--card-edit} an can be considered a frontend to @command{scdaemon} +which is a daemon started by @command{gpg-agent} to handle smart +cards. + +If @command{gpg-card} is invoked without commands an interactive +mode is used. + +If @command{gpg-card} is invoked with one or more commands the +same commands as available in the interactive mode are run from the +command line. These commands need to be delimited with a double-dash. +If a double-dash or a shell specific character is required as part of +a command the entire command needs to be put in quotes. If one of +those commands returns an error the remaining commands are mot anymore +run unless the command was prefixed with a single dash. + +A list of commands is available by using the command @code{help} and a +detailed description of each command is printed by using @code{help +COMMAND}. + +See the NOTES sections for instructions pertaining to specific cards +or card applications. + +@mansect options +@noindent +@command{gpg-card} understands these options: + +@table @gnupgtabopt + +@item --with-colons +@opindex with-colons +This option has currently no effect. + +@item --status-fd @var{n} +@opindex status-fd +Write special status strings to the file descriptor @var{n}. This +program returns only the status messages SUCCESS or FAILURE which are +helpful when the caller uses a double fork approach and can't easily +get the return code of the process. + +@item --verbose +@opindex verbose +Enable extra informational output. + +@item --quiet +@opindex quiet +Disable almost all informational output. + +@item --version +@opindex version +Print version of the program and exit. + +@item --help +@opindex help +Display a brief help page and exit. + +@item --no-autostart +@opindex no-autostart +Do not start the gpg-agent if it has not yet been started and its +service is required. This option is mostly useful on machines where +the connection to gpg-agent has been redirected to another machines. + +@item --agent-program @var{file} +@opindex agent-program +Specify the agent program to be started if none is running. The +default value is determined by running @command{gpgconf} with the +option @option{--list-dirs}. + +@item --gpg-program @var{file} +@opindex gpg-program +Specify a non-default gpg binary to be used by certain commands. + +@item --gpgsm-program @var{file} +@opindex gpgsm-program +Specify a non-default gpgsm binary to be used by certain commands. + +@end table + +@mansect notes (OpenPGP) +The support for OpenPGP cards in @command{gpg-card} is not yet +complete. For missing features, please continue to use @code{gpg +--card-edit}. + +@mansect notes (PIV) +@noindent +GnuPG has support for PIV cards (``Personal Identity Verification'' +as specified by NIST Special Publication 800-73-4). This section +describes how to initialize (personalize) a fresh Yubikey token +featuring the PIV application (requires Yubikey-5). We assume that +the credentials have not yet been changed and thus are: +@table @asis +@item Authentication key +This is a 24 byte key described by the hex string +@code{010203040506070801020304050607080102030405060708}. +@item PIV Application PIN +This is the string @code{123456}. +@item PIN Unblocking Key +This is the string @code{12345678}. +@end table +See the example section on how to change these defaults. For +production use it is important to use secure values for them. Note that +the Authentication Key is not queried via the usual Pinentry dialog +but needs to be entered manually or read from a file. The use of a +dedicated machine to personalize tokens is strongly suggested. + +To see what is on the card, the command @code{list} can be given. We +will use the interactive mode in the following (the string +@emph{gpg/card>} is the prompt). An example output for a fresh card +is: + +@example +gpg/card> list +Reader ...........: 1050:0407:X:0 +Card type ........: yubikey +Card firmware ....: 5.1.2 +Serial number ....: D2760001240102010006090746250000 +Application type .: OpenPGP +Version ..........: 2.1 +[...] +@end example + +It can be seen by the ``Application type'' line that GnuPG selected the +OpenPGP application of the Yubikey. This is because GnuPG assigns the +highest priority to the OpenPGP application. To use the PIV +application of the Yubikey, the OpenPGP application needs to be +disabled: + +@example +gpg/card> yubikey disable all opgp +gpg/card> yubikey list +Application USB NFC +----------------------- +OTP yes yes +U2F yes yes +OPGP no no +PIV yes no +OATH yes yes +FIDO2 yes yes +gpg/card> reset +@end example + +The @code{reset} is required so that the GnuPG system rereads the +card. Note that disabled applications keep all their data and can at +any time be re-enabled (see @emph{help yubikey}). Now a @emph{list} +command shows this: + +@example +gpg/card> list +Reader ...........: 1050:0407:X:0 +Card type ........: yubikey +Card firmware ....: 5.1.2 +Serial number ....: FF020001008A77C1 +Application type .: PIV +Version ..........: 1.0 +Displayed s/n ....: yk-9074625 +PIN usage policy .: app-pin +PIN retry counter : - 3 - +PIV authentication: [none] + keyref .....: PIV.9A +Card authenticat. : [none] + keyref .....: PIV.9E +Digital signature : [none] + keyref .....: PIV.9C +Key management ...: [none] + keyref .....: PIV.9D +@end example + +Note that the ``Displayed s/sn'' is printed on the token and also +shown in Pinentry prompts asking for the PIN. The four standard key +slots are always shown, if other key slots are initialized they are +shown as well. The @emph{PIV authentication} key (internal reference +@emph{PIV.9A}) is used to authenticate the card and the card holder. +The use of the associated private key is protected by the Application +PIN which needs to be provided once and the key can the be used until +the card is reset or removed from the reader or USB port. GnuPG uses +this key with its @emph{Secure Shell} support. The @emph{Card +authentication} key (@emph{PIV.9E}) is also known as the CAK and used +to support physical access applications. The private key is not +protected by a PIN and can thus immediately be used. The @emph{Digital +signature} key (@emph{PIV.9C}) is used to digitally sign documents. +The use of the associated private key is protected by the Application +PIN which needs to be provided for each signing operation. The +@emph{Key management} key (@emph{PIV.9D}) is used for encryption. The +use of the associated private key is protected by the Application PIN +which needs to be provided only once so that decryption operations can +then be done until the card is reset or removed from the reader or USB +port. + +We now generate tree of the four keys. Note that GnuPG does currently +not use the the @emph{Card authentication} key but because it is +mandatory by the specs we create it anyway. Key generation requires +that we authenticate to the card. This can be done either on the +command line (which would reveal the key): + +@example +gpg/card> auth 010203040506070801020304050607080102030405060708 +@end example + +or by reading the key from a file. That file needs to consist of one +LF terminated line with the hex encoded key (as above): + +@example +gpg/card> auth < myauth.key +@end example + +As usual @samp{help auth} gives help for this command. An error +message is printed if a non-matching key is used. The authentication +is valid until a reset of the card or until the card is removed from +the reader or the USB port. Note that that in non-interactive mode +the @samp{<} needs to be quoted so that the shell does not interpret +it as a its own redirection symbol. + +@noindent +Here are the actual commands to generate the keys: + +@example +gpg/card> generate --algo=nistp384 PIV.9A +PIV card no. yk-9074625 detected +gpg/card> generate --algo=nistp256 PIV.9E +PIV card no. yk-9074625 detected +gpg/card> generate --algo=rsa2048 PIV.9C +PIV card no. yk-9074625 detected +@end example + +If a key has already been created for one of the slots an error will +be printed; to create a new key anyway the option @samp{--force} can be +used. Note that only the private and public keys have been created +but no certificates are stored in the key slots. In fact, GnuPG uses +its own non-standard method to store just the public key in place of +the the certificate. Other application will not be able to make use +these keys until @command{gpgsm} or another tool has been used to +create and store the respective certificates. Let us see what the +list command now shows: + +@example +gpg/card> list +Reader ...........: 1050:0407:X:0 +Card type ........: yubikey +Card firmware ....: 5.1.2 +Serial number ....: FF020001008A77C1 +Application type .: PIV +Version ..........: 1.0 +Displayed s/n ....: yk-9074625 +PIN usage policy .: app-pin +PIN retry counter : - 3 - +PIV authentication: 213D1825FDE0F8240CB4E4229F01AF90AC658C2E + keyref .....: PIV.9A (auth) + algorithm ..: nistp384 +Card authenticat. : 7A53E6CFFE7220A0E646B4632EE29E5A7104499C + keyref .....: PIV.9E (auth) + algorithm ..: nistp256 +Digital signature : 32A6C6FAFCB8421878608AAB452D5470DD3223ED + keyref .....: PIV.9C (sign,cert) + algorithm ..: rsa2048 +Key management ...: [none] + keyref .....: PIV.9D +@end example + +The primary information for each key is the @emph{keygrip}, a 40 byte +hex-string identifying the key. This keygrip is a unique identifier +for the specific parameters of a key. It is used by +@command{gpg-agent} and other parts of GnuPG to associate a private +key to its protocol specific certificate format (X.509, OpenPGP, or +SecureShell). Below the keygrip the key reference along with the key +usage capabilities are show. Finally the algorithm is printed in the +format used by @command {gpg}. At that point no other information is +shown because for these new keys gpg won't be able to find matching +certificates. + +Although we could have created the @emph{Key management} key also with +the generate command, we will create that key off-card so that a +backup exists. To accomplish this a key needs to be created with +either @command{gpg} or @command{gpgsm} or imported in one of these +tools. In our example we create a self-signed X.509 certificate (exit +the gpg-card tool, first): + +@example +$ gpgsm --gen-key -o encr.crt + (1) RSA + (2) Existing key + (3) Existing key from card +Your selection? 1 +What keysize do you want? (3072) 2048 +Requested keysize is 2048 bits +Possible actions for a RSA key: + (1) sign, encrypt + (2) sign + (3) encrypt +Your selection? 3 +Enter the X.509 subject name: CN=Encryption key for yk-9074625,O=example,C=DE +Enter email addresses (end with an empty line): +> otto@@example.net +> +Enter DNS names (optional; end with an empty line): +> +Enter URIs (optional; end with an empty line): +> +Create self-signed certificate? (y/N) y +These parameters are used: + Key-Type: RSA + Key-Length: 2048 + Key-Usage: encrypt + Serial: random + Name-DN: CN=Encryption key for yk-9074625,O=example,C=DE + Name-Email: otto@@example.net + +Proceed with creation? (y/N) +Now creating self-signed certificate. This may take a while ... +gpgsm: about to sign the certificate for key: &34798AAFE0A7565088101CC4AE31C5C8C74461CB +gpgsm: certificate created +Ready. +$ gpgsm --import encr.crt +gpgsm: certificate imported +gpgsm: total number processed: 1 +gpgsm: imported: 1 +@end example + +Note the last steps which imported the created certificate. If you +you instead created a certificate signing request (CSR) instead of a +self-signed certificate and sent this off to a CA you would do the +same import step with the certificate received from the CA. Take note +of the keygrip (prefixed with an ampersand) as shown during the +certificate creation or listed it again using @samp{gpgsm +--with-keygrip -k otto@@example.net}. Now to move the key and +certificate to the card start @command{gpg-card} again and enter: + +@example +gpg/card> writekey PIV.9D 34798AAFE0A7565088101CC4AE31C5C8C74461CB +gpg/card> writecert PIV.9D < encr.crt +@end example + +If you entered a passphrase to protect the private key, you will be +asked for it via the Pinentry prompt. On success the key and the +certificate has been written to the card and a @code{list} command +shows: + +@example +[...] +Key management ...: 34798AAFE0A7565088101CC4AE31C5C8C74461CB + keyref .....: PIV.9D (encr) + algorithm ..: rsa2048 + used for ...: X.509 + user id ..: CN=Encryption key for yk-9074625,O=example,C=DE + user id ..: <otto@@example.net> +@end example + +In case the same key (identified by the keygrip) has been used for +several certificates you will see several ``used for'' parts. With +this the encryption key is now fully functional and can be used to +decrypt messages encrypted to this certificate. @sc{Take care:} the +original key is still stored on-disk and should be moved to a backup +medium. This can simply be done by copying the file +@file{34798AAFE0A7565088101CC4AE31C5C8C74461CB.key} from the directory +@file{~/.gnupg/private-keys-v1.d/} to the backup medium and deleting +the file at its original place. + +The final example is to create a self-signed certificate for digital +signatures. Leave @command{gpg-card} using @code{quit} or by pressing +Control-D and use gpgsm: + +@example +$ gpgsm --learn +$ gpgsm --gen-key -o sign.crt +Please select what kind of key you want: + (1) RSA + (2) Existing key + (3) Existing key from card +Your selection? 3 +Serial number of the card: FF020001008A77C1 +Available keys: + (1) 213D1825FDE0F8240CB4E4229F01AF90AC658C2E PIV.9A nistp384 + (2) 7A53E6CFFE7220A0E646B4632EE29E5A7104499C PIV.9E nistp256 + (3) 32A6C6FAFCB8421878608AAB452D5470DD3223ED PIV.9C rsa2048 + (4) 34798AAFE0A7565088101CC4AE31C5C8C74461CB PIV.9D rsa2048 +Your selection? 3 +Possible actions for a RSA key: + (1) sign, encrypt + (2) sign + (3) encrypt +Your selection? 2 +Enter the X.509 subject name: CN=Signing key for yk-9074625,O=example,C=DE +Enter email addresses (end with an empty line): +> otto@@example.net +> +Enter DNS names (optional; end with an empty line): +> +Enter URIs (optional; end with an empty line): +> +Create self-signed certificate? (y/N) +These parameters are used: + Key-Type: card:PIV.9C + Key-Length: 1024 + Key-Usage: sign + Serial: random + Name-DN: CN=Signing key for yk-9074625,O=example,C=DE + Name-Email: otto@@example.net + +Proceed with creation? (y/N) y +Now creating self-signed certificate. This may take a while ... +gpgsm: about to sign the certificate for key: &32A6C6FAFCB8421878608AAB452D5470DD3223ED +gpgsm: certificate created +Ready. +$ gpgsm --import sign.crt +gpgsm: certificate imported +gpgsm: total number processed: 1 +gpgsm: imported: 1 +@end example + +The use of @samp{gpgsm --learn} is currently necessary so that +gpg-agent knows what keys are available on the card. The need for +this command will eventually be removed. The remaining commands are +similar to the creation of an on-disk key. However, here we select +the @samp{Digital signature} key. During the creation process you +will be asked for the Application PIN of the card. The final step is +to write the certificate to the card using @command{gpg-card}: + +@example +gpg/card> writecert PIV.9C < sign.crt +@end example + +By running list again we will see the fully initialized card: + +@example +Reader ...........: 1050:0407:X:0 +Card type ........: yubikey +Card firmware ....: 5.1.2 +Serial number ....: FF020001008A77C1 +Application type .: PIV +Version ..........: 1.0 +Displayed s/n ....: yk-9074625 +PIN usage policy .: app-pin +PIN retry counter : - [verified] - +PIV authentication: 213D1825FDE0F8240CB4E4229F01AF90AC658C2E + keyref .....: PIV.9A (auth) + algorithm ..: nistp384 +Card authenticat. : 7A53E6CFFE7220A0E646B4632EE29E5A7104499C + keyref .....: PIV.9E (auth) + algorithm ..: nistp256 +Digital signature : 32A6C6FAFCB8421878608AAB452D5470DD3223ED + keyref .....: PIV.9C (sign,cert) + algorithm ..: rsa2048 + used for ...: X.509 + user id ..: CN=Signing key for yk-9074625,O=example,C=DE + user id ..: <otto@@example.net> +Key management ...: 34798AAFE0A7565088101CC4AE31C5C8C74461CB + keyref .....: PIV.9D (encr) + algorithm ..: rsa2048 + used for ...: X.509 + user id ..: CN=Encryption key for yk-9074625,O=example,C=DE + user id ..: <otto@@example.net> +@end example + +It is now possible to sign and to encrypt with this card using gpgsm +and to use the @samp{PIV authentication} key with ssh: + +@example +$ ssh-add -l +384 SHA256:0qnJ0Y0ehWxKcx2frLfEljf6GCdlO55OZed5HqGHsaU cardno:yk-9074625 (ECDSA) +@end example + +As usual use ssh-add with the uppercase @samp{-L} to list the public +ssh key. To use the certificates with Thunderbird or Mozilla, please +consult the Scute manual for details. + + + +@c @mansect examples + +@mansect see also +@ifset isman +@command{scdaemon}(1) +@end ifset diff --git a/doc/gpg.texi b/doc/gpg.texi index 7f55cc7e3..e6829b911 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -624,9 +624,9 @@ fingerprint (preferred) or their keyid. @end table -@c ******************************************* -@c ******* KEY MANGEMENT COMMANDS ********** -@c ******************************************* +@c ******************************************** +@c ******* KEY MANAGEMENT COMMANDS ********** +@c ******************************************** @node OpenPGP Key Management @subsection How to manage your keys @@ -1328,6 +1328,10 @@ give the opposite meaning. The options are: meaningful when using @option{--with-colons} along with @option{--check-signatures}. + @item show-only-fpr-mbox + @opindex list-options:show-only-fpr-mbox + For each valid user-id which also has a valid mail address print + only the fingerprint and the mail address. @end table @item --verify-options @var{parameters} @@ -1724,7 +1728,8 @@ Set what trust model GnuPG should follow. The models are: @opindex trust-model:auto Select the trust model depending on whatever the internal trust database says. This is the default model if such a database already - exists. + exists. Note that a tofu trust model is not considered here and + must be enabled explicitly. @end table @item --auto-key-locate @var{mechanisms} @@ -1782,7 +1787,9 @@ list. The default is "local,wkd". @item clear Clear all defined mechanisms. This is useful to override - mechanisms given in a config file. + mechanisms given in a config file. Note that a @code{nodefault} in + @var{mechanisms} will also be cleared unless it is given after the + @code{clear}. @end table @@ -1888,32 +1895,12 @@ are available for all keyserver types, some common options are: retrieving keys by subkey id. @item timeout - Tell the keyserver helper program how long (in seconds) to try and - perform a keyserver action before giving up. Note that performing - multiple actions at the same time uses this timeout value per action. - For example, when retrieving multiple keys via @option{--receive-keys}, the - timeout applies separately to each key retrieval, and not to the - @option{--receive-keys} command as a whole. Defaults to 30 seconds. - - @item http-proxy=@var{value} - This option is deprecated. - Set the proxy to use for HTTP and HKP keyservers. - This overrides any proxy defined in @file{dirmngr.conf}. - - @item verbose - This option has no more function since GnuPG 2.1. Use the - @code{dirmngr} configuration options instead. - - @item debug - This option has no more function since GnuPG 2.1. Use the - @code{dirmngr} configuration options instead. - - @item check-cert - This option has no more function since GnuPG 2.1. Use the - @code{dirmngr} configuration options instead. - + @itemx http-proxy=@var{value} + @itemx verbose + @itemx debug + @itemx check-cert @item ca-cert-file - This option has no more function since GnuPG 2.1. Use the + These options have no more function since GnuPG 2.1. Use the @code{dirmngr} configuration options instead. @end table @@ -2342,6 +2329,11 @@ opposite meaning. The options are: on the keyring. This option is the same as running the @option{--edit-key} command "clean" after import. Defaults to no. + @item import-drop-uids + Do not import any user ids or their binding signatures. This option + can be used to update only the subkeys or other non-user id related + information. + @item repair-keys. After import, fix various problems with the keys. For example, this reorders signatures, and strips duplicate signatures. Defaults to yes. @@ -2506,6 +2498,11 @@ opposite meaning. The options are: running the @option{--edit-key} command "minimize" before export except that the local copy of the key is not modified. Defaults to no. + @item export-drop-uids + Do no export any user id or attribute packets or their associates + signatures. Note that due to missing user ids the resulting output is + not strictly RFC-4880 compliant. + @item export-pka Instead of outputting the key material output PKA records suitable to put into DNS zone files. An ORIGIN line is printed before each @@ -2612,7 +2609,7 @@ These options are obsolete and have no effect since GnuPG 2.1. @item --force-aead @opindex force-aead Force the use of AEAD encryption over MDC encryption. AEAD is a -modern and faster way to do authenticated encrytion than the old MDC +modern and faster way to do authenticated encryption than the old MDC method. See also options @option{--aead-algo} and @option{--chunk-size}. @@ -2768,7 +2765,7 @@ This option is obsolete; it is handled as an alias for @option{--pgp7} @item --pgp7 @opindex pgp7 -Set up all options to be as PGP 7 compliant as possible. This allowd +Set up all options to be as PGP 7 compliant as possible. This allowed the ciphers IDEA, 3DES, CAST5,AES128, AES192, AES256, and TWOFISH., the hashes MD5, SHA1 and RIPEMD160, and the compression algorithms none and ZIP. This option implies @option{--escape-from-lines} and @@ -3040,7 +3037,7 @@ same thing. @opindex aead-algo Specify that the AEAD algorithm @var{name} is to be used. This is useful for symmetric encryption where no key preference are available -to select the AEAD algorithm. Runing @command{@gpgname} with option +to select the AEAD algorithm. Running @command{@gpgname} with option @option{--version} shows the available AEAD algorithms. In general, you do not want to use this option as it allows you to violate the OpenPGP standard. The option @option{--personal-aead-preferences} is @@ -3313,7 +3310,7 @@ command has the same effect as using @option{--list-keys} with @option{--with-sig-list}. Note that in contrast to @option{--check-signatures} the key signatures are not verified. This command can be used to create a list of signing keys missing in the -lcoal keyring; for example: +local keyring; for example: @example gpg --list-sigs --with-colons USERID | \ diff --git a/doc/gpgv.texi b/doc/gpgv.texi index a05286171..2dd9576b6 100644 --- a/doc/gpgv.texi +++ b/doc/gpgv.texi @@ -59,13 +59,14 @@ no configuration files and only a few options are implemented. That does also mean that it does not check for expired or revoked keys. -By default a keyring named @file{trustedkeys.kbx} is used; if that -does not exist a keyring named @file{trustedkeys.gpg} is used. The -default keyring is assumed to be in the home directory of GnuPG, -either the default home directory or the one set by an option or an -environment variable. The option @code{--keyring} may be used to -specify a different keyring or even multiple keyrings. - +If no @code{--keyring} option is given, @code{gpgv} looks for a +``default'' keyring named @file{trustedkeys.kbx} (preferred) or +@file{trustedkeys.gpg} in the home directory of GnuPG, either the +default home directory or the one set by the @code{--homedir} option +or the @code{GNUPGHOME} environment variable. If any @code{--keyring} +option is used, @code{gpgv} will not look for the default keyring. The +@code{--keyring} option may be used multiple times and all specified +keyrings will be used together. @noindent @mansect options diff --git a/doc/specify-user-id.texi b/doc/specify-user-id.texi index b363c2ace..64e354bdf 100644 --- a/doc/specify-user-id.texi +++ b/doc/specify-user-id.texi @@ -135,7 +135,7 @@ RFC-2253 encoded DN of the issuer. See note above. @item By keygrip. This is indicated by an ampersand followed by the 40 hex digits of a keygrip. @command{gpgsm} prints the keygrip when using the command -@option{--dump-cert}. It does not yet work for OpenPGP keys. +@option{--dump-cert}. @cartouche @example @@ -171,6 +171,3 @@ Using the RFC-2253 format of DNs has the drawback that it is not possible to map them back to the original encoding, however we don't have to do this because our key database stores this encoding as meta data. - - - diff --git a/doc/tools.texi b/doc/tools.texi index 7becf67e2..119f698d6 100644 --- a/doc/tools.texi +++ b/doc/tools.texi @@ -1561,7 +1561,7 @@ string @code{true} or @code{yes}. The evaluation is done by passing /subst /let i 3 /while $i - /echo loop couter is $i + /echo loop counter is $i /let i $@{- $i 1@} /end @end smallexample @@ -1962,7 +1962,7 @@ Extract all files from an encrypted archive. @item --sign @itemx -s -Make a signed archive from the given files and directories. Thsi can +Make a signed archive from the given files and directories. This can be combined with option @option{--encrypt} to create a signed and then encrypted archive. @@ -2014,10 +2014,11 @@ Do not actually output the extracted files. @item --directory @var{dir} @itemx -C @var{dir} @opindex directory -Extract the files into the directory @var{dir}. The -default is to take the directory name from -the input filename. If no input filename is known a directory named -@file{GPGARCH} is used. +Extract the files into the directory @var{dir}. The default is to +take the directory name from the input filename. If no input filename +is known a directory named @file{GPGARCH} is used. For tarball +creation, switch to directory @var{dir} before performing any +operations. @item --files-from @var{file} @itemx -T @var{file} @@ -2031,7 +2032,7 @@ linefeed to separate file names. @item --openpgp @opindex openpgp -This option has no effect becuase OpenPGP encryption and signing is +This option has no effect because OpenPGP encryption and signing is the default. @item --cms diff --git a/doc/wks.texi b/doc/wks.texi index 4508ae2a1..f132b3186 100644 --- a/doc/wks.texi +++ b/doc/wks.texi @@ -61,11 +61,12 @@ Service provider. This is usuallay done to upload a key into a Web Key Directory. With the @option{--supported} command the caller can test whether a -site supports the Web Key Service. The argument is an arbitray +site supports the Web Key Service. The argument is an arbitrary address in the to be tested domain. For example @file{foo@@example.net}. The command returns success if the Web Key Service is supported. The operation is silent; to get diagnostic -output use the option @option{--verbose}. +output use the option @option{--verbose}. See option +@option{--with-colons} for a variant of this command. With the @option{--check} command the caller can test whether a key exists for a supplied mail address. The command returns success if a @@ -89,6 +90,17 @@ decrypted MIME message. The result of these commands are another mail which can be send in the same way as the mail created with @option{--create}. +The command @option{--install-key} manually installs a key into a +local directory (see option @option{-C}) reflecting the structure of a +WKD. The arguments are a file with the keyblock and the user-id to +install. If the first argument resembles a fingerprint the key is +taken from the current keyring; to force the use of a file, prefix the +first argument with "./". If no arguments are given the parameters +are read from stdin; the expected format are lines with the +fingerprint and the mailbox separated by a space. The command +@option{--remove-key} removes a key from that directory, its only +argument is a user-id. + @command{gpg-wks-client} is not commonly invoked directly and thus it is not installed in the bin directory. Here is an example how it can be invoked manually to check for a Web Key Directory entry for @@ -109,6 +121,44 @@ $(gpgconf --list-dirs libexecdir)/gpg-wks-client --check foo@@example.net Directly send created mails using the @command{sendmail} command. Requires installation of that command. +@item --with-colons +@opindex with-colons +This option has currently only an effect on the @option{--supported} +command. If it is used all arguments on the command line are taken +as domain names and tested for WKD support. The output format is one +line per domain with colon delimited fields. The currently specified +fields are (future versions may specify additional fields): + +@table @asis + + @item 1 - domain + This is the domain name. Although quoting is not required for valid + domain names this field is specified to be quoted in standard C + manner. + + @item 2 - WKD + If the value is true the domain supports the Web Key Directory. + + @item 3 - WKS + If the value is true the domain supports the Web Key Service + protocol to upload keys to the directory. + + @item 4 - error-code + This may contain an gpg-error code to describe certain + failures. Use @samp{gpg-error CODE} to explain the code. + + @item 5 - protocol-version + The minimum protocol version supported by the server. + + @item 6 - auth-submit + The auth-submit flag from the policy file of the server. + + @item 7 - mailbox-only + The mailbox-only flag from the policy file of the server. +@end table + + + @item --output @var{file} @itemx -o @opindex output @@ -122,6 +172,13 @@ This program returns only the status messages SUCCESS or FAILURE which are helpful when the caller uses a double fork approach and can't easily get the return code of the process. +@item -C @var{dir} +@itemx --directory @var{dir} +@opindex directory +Use @var{dir} as top level directory for the commands +@option{--install-key} and @option{--remove-key}. The default is +@file{openpgpkey}. + @item --verbose @opindex verbose Enable extra informational output. @@ -206,7 +263,7 @@ mail is processed. Commonly this command is used with the option @option{--send} to directly send the crerated mails back. See below for an installation example. -The command @option{--cron} is used for regualr cleanup tasks. For +The command @option{--cron} is used for regular cleanup tasks. For example non-confirmed requested should be removed after their expire time. It is best to run this command once a day from a cronjob. @@ -215,9 +272,9 @@ Further it creates missing directories for the configuration and prints warnings pertaining to problems in the configuration. The command @option{--check-key} (or just @option{--check}) checks -whether a key with the given user-id is installed. The process return -success in this case; to also print a diagnostic, use option -@option{-v}. If the key is not installed a diagnostics is printed and +whether a key with the given user-id is installed. The process returns +success in this case; to also print a diagnostic use the option +@option{-v}. If the key is not installed a diagnostic is printed and the process returns failure; to suppress the diagnostic, use option @option{-q}. More than one user-id can be given; see also option @option{with-file}. @@ -226,7 +283,9 @@ The command @option{--install-key} manually installs a key into the WKD. The arguments are a file with the keyblock and the user-id to install. If the first argument resembles a fingerprint the key is taken from the current keyring; to force the use of a file, prefix the -first argument with "./". +first argument with "./". If no arguments are given the parameters +are read from stdin; the expected format are lines with the +fingerprint and the mailbox separated by a space. The command @option{--remove-key} uninstalls a key from the WKD. The process returns success in this case; to also print a diagnostic, use @@ -243,6 +302,12 @@ The command @option{--revoke-key} is not yet functional. @table @gnupgtabopt +@item -C @var{dir} +@itemx --directory @var{dir} +@opindex directory +Use @var{dir} as top level directory for domains. The default is +@file{/var/lib/gnupg/wks}. + @item --from @var{mailaddr} @opindex from Use @var{mailaddr} as the default sender address. @@ -256,21 +321,22 @@ Add the mail header "@var{name}: @var{value}" to all outgoing mails. Directly send created mails using the @command{sendmail} command. Requires installation of that command. -@item --output @var{file} -@itemx -o +@item -o @var{file} +@itemx --output @var{file} @opindex output Write the created mail also to @var{file}. Note that the value @code{-} for @var{file} would write it to stdout. @item --with-dir @opindex with-dir -Also print the directory name for each domain listed by command -@option{--list-domains}. +When used with the command @option{--list-domains} print for each +installed domain the domain name and its directory name. @item --with-file @opindex with-file -With command @option{--check-key} print for each user-id, the address, -'i' for installed key or 'n' for not installed key, and the filename. +When used with the command @option{--check-key} print for each user-id, +the address, 'i' for installed key or 'n' for not installed key, and +the filename. @item --verbose @opindex verbose @@ -316,7 +382,7 @@ Finally run $ gpg-wks-server --list-domains @end example -to create the required sub-directories with the permission set +to create the required sub-directories with the permissions set correctly. For each domain a submission address needs to be configured. All service mails are directed to that address. It can be the same address for all configured domains, for example: @@ -326,7 +392,7 @@ be the same address for all configured domains, for example: $ echo key-submission@@example.net >submission-address @end example -The protocol requires that the key to be published is sent with an +The protocol requires that the key to be published is send with an encrypted mail to the service. Thus you need to create a key for the submission address: diff --git a/doc/yat2m.c b/doc/yat2m.c index c7bec338f..be0ef17fd 100644 --- a/doc/yat2m.c +++ b/doc/yat2m.c @@ -55,7 +55,7 @@ .B whateever you want @end ifset - alternativly a special comment may be used: + alternatively a special comment may be used: @c man:.B whatever you want @@ -704,7 +704,7 @@ write_th (FILE *fp) /* Process the texinfo command COMMAND (without the leading @) and - write output if needed to FP. REST is the remainer of the line + write output if needed to FP. REST is the remainder of the line which should either point to an opening brace or to a white space. The function returns the number of characters already processed from REST. LEN is the usable length of REST. TABLE_LEVEL is used to @@ -1197,7 +1197,7 @@ parse_file (const char *fname, FILE *fp, char **section_name, int in_pause) if (*p == '@' && !strncmp (p+1, "item", 4)) item_indent = p - line; /* Set a new indent level. */ else if (p - line < item_indent) - item_indent = 0; /* Switch off indention. */ + item_indent = 0; /* Switch off indentation. */ if (item_indent) { diff --git a/g10/armor.c b/g10/armor.c index cc8096862..972766503 100644 --- a/g10/armor.c +++ b/g10/armor.c @@ -1,4 +1,4 @@ -/* armor.c - Armor flter +/* armor.c - Armor filter * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, * 2007 Free Software Foundation, Inc. * @@ -37,17 +37,10 @@ #define MAX_LINELEN 20000 -#define CRCINIT 0xB704CE -#define CRCPOLY 0X864CFB -#define CRCUPDATE(a,c) do { \ - a = ((a) << 8) ^ crc_table[((a)&0xff >> 16) ^ (c)]; \ - a &= 0x00ffffff; \ - } while(0) -static u32 crc_table[256]; -static byte bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; -static byte asctobin[256]; /* runtime initialized */ +static const byte bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; +static u32 asctobin[4][256]; /* runtime initialized */ static int is_initialized; @@ -121,9 +114,22 @@ armor_filter_context_t * new_armor_context (void) { armor_filter_context_t *afx; + gpg_error_t err; afx = xcalloc (1, sizeof *afx); - afx->refcount = 1; + if (afx) + { + err = gcry_md_open (&afx->crc_md, GCRY_MD_CRC24_RFC2440, 0); + if (err != 0) + { + log_error ("gcry_md_open failed for GCRY_MD_CRC24_RFC2440: %s", + gpg_strerror (err)); + xfree (afx); + return NULL; + } + + afx->refcount = 1; + } return afx; } @@ -138,6 +144,7 @@ release_armor_context (armor_filter_context_t *afx) log_assert (afx->refcount); if ( --afx->refcount ) return; + gcry_md_close (afx->crc_md); xfree (afx); } @@ -161,35 +168,42 @@ push_armor_filter (armor_filter_context_t *afx, iobuf_t iobuf) static void initialize(void) { - int i, j; - u32 t; - byte *s; - - /* init the crc lookup table */ - crc_table[0] = 0; - for(i=j=0; j < 128; j++ ) { - t = crc_table[j]; - if( t & 0x00800000 ) { - t <<= 1; - crc_table[i++] = t ^ CRCPOLY; - crc_table[i++] = t; - } - else { - t <<= 1; - crc_table[i++] = t; - crc_table[i++] = t ^ CRCPOLY; - } - } - /* build the helptable for radix64 to bin conversion */ - for(i=0; i < 256; i++ ) - asctobin[i] = 255; /* used to detect invalid characters */ + u32 i; + const byte *s; + + /* Build the helptable for radix64 to bin conversion. Value 0xffffffff is + used to detect invalid characters. */ + memset (asctobin, 0xff, sizeof(asctobin)); for(s=bintoasc,i=0; *s; s++,i++ ) - asctobin[*s] = i; + { + asctobin[0][*s] = i << (0 * 6); + asctobin[1][*s] = i << (1 * 6); + asctobin[2][*s] = i << (2 * 6); + asctobin[3][*s] = i << (3 * 6); + } is_initialized=1; } +static inline u32 +get_afx_crc (armor_filter_context_t *afx) +{ + const byte *crc_buf; + u32 crc; + + crc_buf = gcry_md_read (afx->crc_md, GCRY_MD_CRC24_RFC2440); + + crc = crc_buf[0]; + crc <<= 8; + crc |= crc_buf[1]; + crc <<= 8; + crc |= crc_buf[2]; + + return crc; +} + + /* * Check whether this is an armored file. See also * parse-packet.c for details on this code. @@ -592,7 +606,7 @@ check_input( armor_filter_context_t *afx, IOBUF a ) afx->faked = 1; else { afx->inp_checked = 1; - afx->crc = CRCINIT; + gcry_md_reset (afx->crc_md); afx->idx = 0; afx->radbuf[0] = 0; } @@ -768,7 +782,7 @@ fake_packet( armor_filter_context_t *afx, IOBUF a, } } afx->inp_checked = 1; - afx->crc = CRCINIT; + gcry_md_reset (afx->crc_md); afx->idx = 0; afx->radbuf[0] = 0; } @@ -793,14 +807,14 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, byte *buf, size_t size ) { byte val; - int c=0, c2; /*init c because gcc is not clever enough for the continue*/ + int c; + u32 binc; int checkcrc=0; int rc = 0; size_t n = 0; - int idx, i, onlypad=0; - u32 crc; + int idx, onlypad=0; + int skip_fast = 0; - crc = afx->crc; idx = afx->idx; val = afx->radbuf[0]; for( n=0; n < size; ) { @@ -820,6 +834,122 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, } again: + binc = asctobin[0][c]; + + if( binc != 0xffffffffUL ) + { + if( idx == 0 && skip_fast == 0 + && afx->buffer_pos + (16 - 1) < afx->buffer_len + && n + 12 < size) + { + /* Fast path for radix64 to binary conversion. */ + u32 b0,b1,b2,b3; + + /* Speculatively load 15 more input bytes. */ + b0 = binc << (3 * 6); + b0 |= asctobin[2][afx->buffer[afx->buffer_pos + 0]]; + b0 |= asctobin[1][afx->buffer[afx->buffer_pos + 1]]; + b0 |= asctobin[0][afx->buffer[afx->buffer_pos + 2]]; + b1 = asctobin[3][afx->buffer[afx->buffer_pos + 3]]; + b1 |= asctobin[2][afx->buffer[afx->buffer_pos + 4]]; + b1 |= asctobin[1][afx->buffer[afx->buffer_pos + 5]]; + b1 |= asctobin[0][afx->buffer[afx->buffer_pos + 6]]; + b2 = asctobin[3][afx->buffer[afx->buffer_pos + 7]]; + b2 |= asctobin[2][afx->buffer[afx->buffer_pos + 8]]; + b2 |= asctobin[1][afx->buffer[afx->buffer_pos + 9]]; + b2 |= asctobin[0][afx->buffer[afx->buffer_pos + 10]]; + b3 = asctobin[3][afx->buffer[afx->buffer_pos + 11]]; + b3 |= asctobin[2][afx->buffer[afx->buffer_pos + 12]]; + b3 |= asctobin[1][afx->buffer[afx->buffer_pos + 13]]; + b3 |= asctobin[0][afx->buffer[afx->buffer_pos + 14]]; + + /* Check if any of the input bytes were invalid. */ + if( (b0 | b1 | b2 | b3) != 0xffffffffUL ) + { + /* All 16 bytes are valid. */ + buf[n + 0] = b0 >> (2 * 8); + buf[n + 1] = b0 >> (1 * 8); + buf[n + 2] = b0 >> (0 * 8); + buf[n + 3] = b1 >> (2 * 8); + buf[n + 4] = b1 >> (1 * 8); + buf[n + 5] = b1 >> (0 * 8); + buf[n + 6] = b2 >> (2 * 8); + buf[n + 7] = b2 >> (1 * 8); + buf[n + 8] = b2 >> (0 * 8); + buf[n + 9] = b3 >> (2 * 8); + buf[n + 10] = b3 >> (1 * 8); + buf[n + 11] = b3 >> (0 * 8); + afx->buffer_pos += 16 - 1; + n += 12; + continue; + } + else if( b0 == 0xffffffffUL ) + { + /* byte[1..3] have invalid character(s). Switch to slow + path. */ + skip_fast = 1; + } + else if( b1 == 0xffffffffUL ) + { + /* byte[4..7] have invalid character(s), first 4 bytes are + valid. */ + buf[n + 0] = b0 >> (2 * 8); + buf[n + 1] = b0 >> (1 * 8); + buf[n + 2] = b0 >> (0 * 8); + afx->buffer_pos += 4 - 1; + n += 3; + skip_fast = 1; + continue; + } + else if( b2 == 0xffffffffUL ) + { + /* byte[8..11] have invalid character(s), first 8 bytes are + valid. */ + buf[n + 0] = b0 >> (2 * 8); + buf[n + 1] = b0 >> (1 * 8); + buf[n + 2] = b0 >> (0 * 8); + buf[n + 3] = b1 >> (2 * 8); + buf[n + 4] = b1 >> (1 * 8); + buf[n + 5] = b1 >> (0 * 8); + afx->buffer_pos += 8 - 1; + n += 6; + skip_fast = 1; + continue; + } + else /*if( b3 == 0xffffffffUL )*/ + { + /* byte[12..15] have invalid character(s), first 12 bytes + are valid. */ + buf[n + 0] = b0 >> (2 * 8); + buf[n + 1] = b0 >> (1 * 8); + buf[n + 2] = b0 >> (0 * 8); + buf[n + 3] = b1 >> (2 * 8); + buf[n + 4] = b1 >> (1 * 8); + buf[n + 5] = b1 >> (0 * 8); + buf[n + 6] = b2 >> (2 * 8); + buf[n + 7] = b2 >> (1 * 8); + buf[n + 8] = b2 >> (0 * 8); + afx->buffer_pos += 12 - 1; + n += 9; + skip_fast = 1; + continue; + } + } + + switch(idx) + { + case 0: val = binc << 2; break; + case 1: val |= (binc>>4)&3; buf[n++]=val;val=(binc<<4)&0xf0;break; + case 2: val |= (binc>>2)&15; buf[n++]=val;val=(binc<<6)&0xc0;break; + case 3: val |= binc&0x3f; buf[n++] = val; break; + } + idx = (idx+1) % 4; + + continue; + } + + skip_fast = 0; + if( c == '\n' || c == ' ' || c == '\r' || c == '\t' ) continue; else if( c == '=' ) { /* pad character: stop */ @@ -850,10 +980,10 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, if (afx->buffer_pos + 6 < afx->buffer_len && afx->buffer[afx->buffer_pos + 0] == '3' && afx->buffer[afx->buffer_pos + 1] == 'D' - && asctobin[afx->buffer[afx->buffer_pos + 2]] != 255 - && asctobin[afx->buffer[afx->buffer_pos + 3]] != 255 - && asctobin[afx->buffer[afx->buffer_pos + 4]] != 255 - && asctobin[afx->buffer[afx->buffer_pos + 5]] != 255 + && asctobin[0][afx->buffer[afx->buffer_pos + 2]] != 0xffffffffUL + && asctobin[0][afx->buffer[afx->buffer_pos + 3]] != 0xffffffffUL + && asctobin[0][afx->buffer[afx->buffer_pos + 4]] != 0xffffffffUL + && asctobin[0][afx->buffer[afx->buffer_pos + 5]] != 0xffffffffUL && afx->buffer[afx->buffer_pos + 6] == '\n') { afx->buffer_pos += 2; @@ -868,27 +998,20 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, checkcrc++; break; } - else if( (c = asctobin[(c2=c)]) == 255 ) { - log_error(_("invalid radix64 character %02X skipped\n"), c2); + else { + log_error(_("invalid radix64 character %02X skipped\n"), c); continue; } - switch(idx) { - case 0: val = c << 2; break; - case 1: val |= (c>>4)&3; buf[n++]=val;val=(c<<4)&0xf0;break; - case 2: val |= (c>>2)&15; buf[n++]=val;val=(c<<6)&0xc0;break; - case 3: val |= c&0x3f; buf[n++] = val; break; - } - idx = (idx+1) % 4; } - for(i=0; i < n; i++ ) - crc = (crc << 8) ^ crc_table[((crc >> 16)&0xff) ^ buf[i]]; - crc &= 0x00ffffff; - afx->crc = crc; afx->idx = idx; afx->radbuf[0] = val; + if( n ) + gcry_md_write (afx->crc_md, buf, n); + if( checkcrc ) { + gcry_md_final (afx->crc_md); afx->any_data = 1; afx->inp_checked=0; afx->faked = 0; @@ -911,19 +1034,19 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, continue; break; } - if( c == -1 ) + if( !afx->buffer_len ) log_error(_("premature eof (no CRC)\n")); else { u32 mycrc = 0; idx = 0; do { - if( (c = asctobin[c]) == 255 ) + if( (binc = asctobin[0][c]) == 0xffffffffUL ) break; switch(idx) { - case 0: val = c << 2; break; - case 1: val |= (c>>4)&3; mycrc |= val << 16;val=(c<<4)&0xf0;break; - case 2: val |= (c>>2)&15; mycrc |= val << 8;val=(c<<6)&0xc0;break; - case 3: val |= c&0x3f; mycrc |= val; break; + case 0: val = binc << 2; break; + case 1: val |= (binc>>4)&3; mycrc |= val << 16;val=(binc<<4)&0xf0;break; + case 2: val |= (binc>>2)&15; mycrc |= val << 8;val=(binc<<6)&0xc0;break; + case 3: val |= binc&0x3f; mycrc |= val; break; } for(;;) { if( afx->buffer_pos < afx->buffer_len ) @@ -945,7 +1068,7 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, if( !afx->buffer_len ) break; /* eof */ } while( ++idx < 4 ); - if( c == -1 ) { + if( !afx->buffer_len ) { log_info(_("premature eof (in CRC)\n")); rc = invalid_crc(); } @@ -957,10 +1080,10 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, log_info(_("malformed CRC\n")); rc = invalid_crc(); } - else if( mycrc != afx->crc ) { - log_info (_("CRC error; %06lX - %06lX\n"), - (ulong)afx->crc, (ulong)mycrc); - rc = invalid_crc(); + else if( mycrc != get_afx_crc (afx) ) { + log_info (_("CRC error; %06lX - %06lX\n"), + (ulong)get_afx_crc (afx), (ulong)mycrc); + rc = invalid_crc(); } else { rc = 0; @@ -997,6 +1120,121 @@ radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn, return rc; } +static void +armor_output_buf_as_radix64 (armor_filter_context_t *afx, IOBUF a, + byte *buf, size_t size) +{ + byte radbuf[sizeof (afx->radbuf)]; + byte outbuf[64 + sizeof (afx->eol)]; + unsigned int eollen = strlen (afx->eol); + u32 in, in2; + int idx, idx2; + int i; + + idx = afx->idx; + idx2 = afx->idx2; + memcpy (radbuf, afx->radbuf, sizeof (afx->radbuf)); + + if (size && (idx || idx2)) + { + /* preload eol to outbuf buffer */ + memcpy (outbuf + 4, afx->eol, sizeof (afx->eol)); + + for (; size && (idx || idx2); buf++, size--) + { + radbuf[idx++] = *buf; + if (idx > 2) + { + idx = 0; + in = (u32)radbuf[0] << (2 * 8); + in |= (u32)radbuf[1] << (1 * 8); + in |= (u32)radbuf[2] << (0 * 8); + outbuf[0] = bintoasc[(in >> 18) & 077]; + outbuf[1] = bintoasc[(in >> 12) & 077]; + outbuf[2] = bintoasc[(in >> 6) & 077]; + outbuf[3] = bintoasc[(in >> 0) & 077]; + if (++idx2 >= (64/4)) + { /* pgp doesn't like 72 here */ + idx2=0; + iobuf_write (a, outbuf, 4 + eollen); + } + else + { + iobuf_write (a, outbuf, 4); + } + } + } + } + + if (size >= (64/4)*3) + { + /* preload eol to outbuf buffer */ + memcpy (outbuf + 64, afx->eol, sizeof(afx->eol)); + + do + { + /* idx and idx2 == 0 */ + + for (i = 0; i < (64/8); i++) + { + in = (u32)buf[0] << (2 * 8); + in |= (u32)buf[1] << (1 * 8); + in |= (u32)buf[2] << (0 * 8); + in2 = (u32)buf[3] << (2 * 8); + in2 |= (u32)buf[4] << (1 * 8); + in2 |= (u32)buf[5] << (0 * 8); + outbuf[i*8+0] = bintoasc[(in >> 18) & 077]; + outbuf[i*8+1] = bintoasc[(in >> 12) & 077]; + outbuf[i*8+2] = bintoasc[(in >> 6) & 077]; + outbuf[i*8+3] = bintoasc[(in >> 0) & 077]; + outbuf[i*8+4] = bintoasc[(in2 >> 18) & 077]; + outbuf[i*8+5] = bintoasc[(in2 >> 12) & 077]; + outbuf[i*8+6] = bintoasc[(in2 >> 6) & 077]; + outbuf[i*8+7] = bintoasc[(in2 >> 0) & 077]; + buf+=6; + size-=6; + } + + /* pgp doesn't like 72 here */ + iobuf_write (a, outbuf, 64 + eollen); + } + while (size >= (64/4)*3); + + /* restore eol for tail handling */ + if (size) + memcpy (outbuf + 4, afx->eol, sizeof (afx->eol)); + } + + for (; size; buf++, size--) + { + radbuf[idx++] = *buf; + if (idx > 2) + { + idx = 0; + in = (u32)radbuf[0] << (2 * 8); + in |= (u32)radbuf[1] << (1 * 8); + in |= (u32)radbuf[2] << (0 * 8); + outbuf[0] = bintoasc[(in >> 18) & 077]; + outbuf[1] = bintoasc[(in >> 12) & 077]; + outbuf[2] = bintoasc[(in >> 6) & 077]; + outbuf[3] = bintoasc[(in >> 0) & 077]; + if (++idx2 >= (64/4)) + { /* pgp doesn't like 72 here */ + idx2=0; + iobuf_write (a, outbuf, 4 + eollen); + } + else + { + iobuf_write (a, outbuf, 4); + } + } + } + + memcpy (afx->radbuf, radbuf, sizeof (afx->radbuf)); + afx->idx = idx; + afx->idx2 = idx2; +} + /**************** * This filter is used to handle the armor stuff */ @@ -1006,7 +1244,7 @@ armor_filter( void *opaque, int control, { size_t size = *ret_len; armor_filter_context_t *afx = opaque; - int rc=0, i, c; + int rc=0, c; byte radbuf[3]; int idx, idx2; size_t n=0; @@ -1188,43 +1426,13 @@ armor_filter( void *opaque, int control, afx->status++; afx->idx = 0; afx->idx2 = 0; - afx->crc = CRCINIT; - + gcry_md_reset (afx->crc_md); } - crc = afx->crc; - idx = afx->idx; - idx2 = afx->idx2; - for(i=0; i < idx; i++ ) - radbuf[i] = afx->radbuf[i]; - - for(i=0; i < size; i++ ) - crc = (crc << 8) ^ crc_table[((crc >> 16)&0xff) ^ buf[i]]; - crc &= 0x00ffffff; - - for( ; size; buf++, size-- ) { - radbuf[idx++] = *buf; - if( idx > 2 ) { - idx = 0; - c = bintoasc[(*radbuf >> 2) & 077]; - iobuf_put(a, c); - c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077]; - iobuf_put(a, c); - c = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077]; - iobuf_put(a, c); - c = bintoasc[radbuf[2]&077]; - iobuf_put(a, c); - if( ++idx2 >= (64/4) ) - { /* pgp doesn't like 72 here */ - iobuf_writestr(a,afx->eol); - idx2=0; - } - } - } - for(i=0; i < idx; i++ ) - afx->radbuf[i] = radbuf[i]; - afx->idx = idx; - afx->idx2 = idx2; - afx->crc = crc; + + if( size ) { + gcry_md_write (afx->crc_md, buf, size); + armor_output_buf_as_radix64 (afx, a, buf, size); + } } else if( control == IOBUFCTRL_INIT ) { @@ -1250,7 +1458,8 @@ armor_filter( void *opaque, int control, if( afx->cancel ) ; else if( afx->status ) { /* pad, write cecksum, and bottom line */ - crc = afx->crc; + gcry_md_final (afx->crc_md); + crc = get_afx_crc (afx); idx = afx->idx; idx2 = afx->idx2; if( idx ) { @@ -1350,221 +1559,3 @@ make_radix64_string( const byte *data, size_t len ) *p = 0; return buffer; } - - -/*********************************************** - * For the pipemode command we can't use the armor filter for various - * reasons, so we use this new unarmor_pump stuff to remove the armor - */ - -enum unarmor_state_e { - STA_init = 0, - STA_bypass, - STA_wait_newline, - STA_wait_dash, - STA_first_dash, - STA_compare_header, - STA_found_header_wait_newline, - STA_skip_header_lines, - STA_skip_header_lines_non_ws, - STA_read_data, - STA_wait_crc, - STA_read_crc, - STA_ready -}; - -struct unarmor_pump_s { - enum unarmor_state_e state; - byte val; - int checkcrc; - int pos; /* counts from 0..3 */ - u32 crc; - u32 mycrc; /* the one store in the data */ -}; - - - -UnarmorPump -unarmor_pump_new (void) -{ - UnarmorPump x; - - if( !is_initialized ) - initialize(); - x = xmalloc_clear (sizeof *x); - return x; -} - -void -unarmor_pump_release (UnarmorPump x) -{ - xfree (x); -} - -/* - * Get the next character from the ascii armor taken from the IOBUF - * created earlier by unarmor_pump_new(). - * Return: c = Character - * 256 = ignore this value - * -1 = End of current armor - * -2 = Premature EOF (not used) - * -3 = Invalid armor - */ -int -unarmor_pump (UnarmorPump x, int c) -{ - int rval = 256; /* default is to ignore the return value */ - - switch (x->state) { - case STA_init: - { - byte tmp[2]; - tmp[0] = c; - tmp[1] = 0; - if ( is_armored (tmp) ) - x->state = c == '-'? STA_first_dash : STA_wait_newline; - else { - x->state = STA_bypass; - return c; - } - } - break; - case STA_bypass: - return c; /* return here to avoid crc calculation */ - case STA_wait_newline: - if (c == '\n') - x->state = STA_wait_dash; - break; - case STA_wait_dash: - x->state = c == '-'? STA_first_dash : STA_wait_newline; - break; - case STA_first_dash: /* just need for initialization */ - x->pos = 0; - x->state = STA_compare_header; /* fall through */ - case STA_compare_header: - if ( "-----BEGIN PGP SIGNATURE-----"[++x->pos] == c ) { - if ( x->pos == 28 ) - x->state = STA_found_header_wait_newline; - } - else - x->state = c == '\n'? STA_wait_dash : STA_wait_newline; - break; - case STA_found_header_wait_newline: - /* to make CR,LF issues easier we simply allow for white space - behind the 5 dashes */ - if ( c == '\n' ) - x->state = STA_skip_header_lines; - else if ( c != '\r' && c != ' ' && c != '\t' ) - x->state = STA_wait_dash; /* garbage after the header line */ - break; - case STA_skip_header_lines: - /* i.e. wait for one empty line */ - if ( c == '\n' ) { - x->state = STA_read_data; - x->crc = CRCINIT; - x->val = 0; - x->pos = 0; - } - else if ( c != '\r' && c != ' ' && c != '\t' ) - x->state = STA_skip_header_lines_non_ws; - break; - case STA_skip_header_lines_non_ws: - /* like above but we already encountered non white space */ - if ( c == '\n' ) - x->state = STA_skip_header_lines; - break; - case STA_read_data: - /* fixme: we don't check for the trailing dash lines but rely - * on the armor stop characters */ - if( c == '\n' || c == ' ' || c == '\r' || c == '\t' ) - break; /* skip all kind of white space */ - - if( c == '=' ) { /* pad character: stop */ - if( x->pos == 1 ) /* in this case val has some value */ - rval = x->val; - x->state = STA_wait_crc; - break; - } - - { - int c2; - if( (c = asctobin[(c2=c)]) == 255 ) { - log_error(_("invalid radix64 character %02X skipped\n"), c2); - break; - } - } - - switch(x->pos) { - case 0: - x->val = c << 2; - break; - case 1: - x->val |= (c>>4)&3; - rval = x->val; - x->val = (c<<4)&0xf0; - break; - case 2: - x->val |= (c>>2)&15; - rval = x->val; - x->val = (c<<6)&0xc0; - break; - case 3: - x->val |= c&0x3f; - rval = x->val; - break; - } - x->pos = (x->pos+1) % 4; - break; - case STA_wait_crc: - if( c == '\n' || c == ' ' || c == '\r' || c == '\t' || c == '=' ) - break; /* skip ws and pad characters */ - /* assume that we are at the next line */ - x->state = STA_read_crc; - x->pos = 0; - x->mycrc = 0; /* fall through */ - case STA_read_crc: - if( (c = asctobin[c]) == 255 ) { - rval = -1; /* ready */ - if( x->crc != x->mycrc ) { - log_info (_("CRC error; %06lX - %06lX\n"), - (ulong)x->crc, (ulong)x->mycrc); - if ( invalid_crc() ) - rval = -3; - } - x->state = STA_ready; /* not sure whether this is correct */ - break; - } - - switch(x->pos) { - case 0: - x->val = c << 2; - break; - case 1: - x->val |= (c>>4)&3; - x->mycrc |= x->val << 16; - x->val = (c<<4)&0xf0; - break; - case 2: - x->val |= (c>>2)&15; - x->mycrc |= x->val << 8; - x->val = (c<<6)&0xc0; - break; - case 3: - x->val |= c&0x3f; - x->mycrc |= x->val; - break; - } - x->pos = (x->pos+1) % 4; - break; - case STA_ready: - rval = -1; - break; - } - - if ( !(rval & ~255) ) { /* compute the CRC */ - x->crc = (x->crc << 8) ^ crc_table[((x->crc >> 16)&0xff) ^ rval]; - x->crc &= 0x00ffffff; - } - - return rval; -} diff --git a/g10/build-packet.c b/g10/build-packet.c index b4e03d007..07fccb099 100644 --- a/g10/build-packet.c +++ b/g10/build-packet.c @@ -243,12 +243,15 @@ build_packet_and_meta (iobuf_t out, PACKET *pkt) /* - * Write the mpi A to OUT. + * Write the mpi A to OUT. If R_NWRITTEN is not NULL the number of + * bytes written is stored there. To only get the number of bytes + * which would be written NULL may be passed for OUT. */ gpg_error_t -gpg_mpi_write (iobuf_t out, gcry_mpi_t a) +gpg_mpi_write (iobuf_t out, gcry_mpi_t a, unsigned int *r_nwritten) { - int rc; + gpg_error_t err; + unsigned int nwritten = 0; if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) { @@ -277,9 +280,17 @@ gpg_mpi_write (iobuf_t out, gcry_mpi_t a) /* gcry_log_debughex (" ", p, (nbits+7)/8); */ lenhdr[0] = nbits >> 8; lenhdr[1] = nbits; - rc = iobuf_write (out, lenhdr, 2); - if (!rc && p) - rc = iobuf_write (out, p, (nbits+7)/8); + err = out? iobuf_write (out, lenhdr, 2) : 0; + if (!err) + { + nwritten += 2; + if (p) + { + err = out? iobuf_write (out, p, (nbits+7)/8) : 0; + if (!err) + nwritten += (nbits+7)/8; + } + } } else { @@ -287,18 +298,25 @@ gpg_mpi_write (iobuf_t out, gcry_mpi_t a) size_t nbytes; nbytes = DIM(buffer); - rc = gcry_mpi_print (GCRYMPI_FMT_PGP, buffer, nbytes, &nbytes, a ); - if( !rc ) - rc = iobuf_write( out, buffer, nbytes ); - else if (gpg_err_code(rc) == GPG_ERR_TOO_SHORT ) + err = gcry_mpi_print (GCRYMPI_FMT_PGP, buffer, nbytes, &nbytes, a ); + if (!err) + { + err = out? iobuf_write (out, buffer, nbytes) : 0; + if (!err) + nwritten += nbytes; + } + else if (gpg_err_code (err) == GPG_ERR_TOO_SHORT ) { log_info ("mpi too large (%u bits)\n", gcry_mpi_get_nbits (a)); - /* The buffer was too small. We better tell the user about the MPI. */ - rc = gpg_error (GPG_ERR_TOO_LARGE); + /* The buffer was too small. We better tell the user about + * the MPI. */ + err = gpg_error (GPG_ERR_TOO_LARGE); } } - return rc; + if (r_nwritten) + *r_nwritten = nwritten; + return err; } @@ -463,29 +481,29 @@ static int do_key (iobuf_t out, int ctb, PKT_public_key *pk) { gpg_error_t err = 0; - /* The length of the body is stored in the packet's header, which - occurs before the body. Unfortunately, we don't know the length - of the packet's body until we've written all of the data! To - work around this, we first write the data into this temporary - buffer, then generate the header, and finally copy the contents - of this buffer to OUT. */ - iobuf_t a = iobuf_temp(); + iobuf_t a; int i, nskey, npkey; + u32 pkbytes = 0; + int is_v5; - log_assert (pk->version == 0 || pk->version == 4); + log_assert (pk->version == 0 || pk->version == 4 || pk->version == 5); log_assert (ctb_pkttype (ctb) == PKT_PUBLIC_KEY || ctb_pkttype (ctb) == PKT_PUBLIC_SUBKEY || ctb_pkttype (ctb) == PKT_SECRET_KEY || ctb_pkttype (ctb) == PKT_SECRET_SUBKEY); - /* Write the version number - if none is specified, use 4 */ - if ( !pk->version ) - iobuf_put ( a, 4 ); - else - iobuf_put ( a, pk->version ); - write_32 (a, pk->timestamp ); + /* The length of the body is stored in the packet's header, which + * occurs before the body. Unfortunately, we don't know the length + * of the packet's body until we've written all of the data! To + * work around this, we first write the data into this temporary + * buffer, then generate the header, and finally copy the content + * of this buffer to OUT. */ + a = iobuf_temp(); + + /* Note that the Version number, Timestamp, Algo, and the v5 Key + * material count are written at the end of the function. */ - iobuf_put (a, pk->pubkey_algo ); + is_v5 = (pk->version == 5); /* Get number of secret and public parameters. They are held in one array: the public ones followed by the secret ones. */ @@ -509,11 +527,13 @@ do_key (iobuf_t out, int ctb, PKT_public_key *pk) || (pk->pubkey_algo == PUBKEY_ALGO_ECDH && (i == 0 || i == 2))) err = gpg_mpi_write_nohdr (a, pk->pkey[i]); else - err = gpg_mpi_write (a, pk->pkey[i]); + err = gpg_mpi_write (a, pk->pkey[i], NULL); if (err) goto leave; } + /* Record the length of the public key part. */ + pkbytes = iobuf_get_temp_length (a); if (pk->seckey_info) { @@ -523,9 +543,26 @@ do_key (iobuf_t out, int ctb, PKT_public_key *pk) /* Build the header for protected (encrypted) secret parameters. */ if (ski->is_protected) { - /* OpenPGP protection according to rfc2440. */ - iobuf_put (a, ski->sha1chk? 0xfe : 0xff); - iobuf_put (a, ski->algo); + iobuf_put (a, ski->sha1chk? 0xfe : 0xff); /* S2k usage. */ + if (is_v5) + { + /* For a v5 key determine the count of the following + * key-protection material and write it. */ + int count = 1; /* Pubkey algo octet. */ + if (ski->s2k.mode >= 1000) + count += 6; /* GNU specific mode descriptor. */ + else + count += 2; /* Mode and hash algo. */ + if (ski->s2k.mode == 1 || ski->s2k.mode == 3) + count += 8; /* Salt. */ + if (ski->s2k.mode == 3) + count++; /* S2K.COUNT */ + if (ski->s2k.mode != 1001 && ski->s2k.mode != 1002) + count += ski->ivlen; + + iobuf_put (a, count); + } + iobuf_put (a, ski->algo); /* Pubkey algo octet. */ if (ski->s2k.mode >= 1000) { /* These modes are not possible in OpenPGP, we use them @@ -556,13 +593,24 @@ do_key (iobuf_t out, int ctb, PKT_public_key *pk) } else /* Not protected. */ - iobuf_put (a, 0 ); + { + iobuf_put (a, 0 ); /* S2K usage = not protected. */ + if (is_v5) + iobuf_put (a, 0); /* Zero octets of key-protection + * material follows. */ + } if (ski->s2k.mode == 1001) - ; /* GnuPG extension - don't write a secret key at all. */ + { + /* GnuPG extension - don't write a secret key at all. */ + if (is_v5) + write_32 (a, 0); /* Zero octets of key material. */ + } else if (ski->s2k.mode == 1002) { /* GnuPG extension - divert to OpenPGP smartcard. */ + if (is_v5) + write_32 (a, 1 + ski->ivlen); /* Length of the serial number or 0 for no serial number. */ iobuf_put (a, ski->ivlen ); /* The serial number gets stored in the IV field. */ @@ -576,15 +624,36 @@ do_key (iobuf_t out, int ctb, PKT_public_key *pk) log_assert (gcry_mpi_get_flag (pk->pkey[npkey], GCRYMPI_FLAG_OPAQUE)); p = gcry_mpi_get_opaque (pk->pkey[npkey], &ndatabits); + /* For v5 keys we first write the number of octets of the + * following encrypted key material. */ + if (is_v5) + write_32 (a, p? (ndatabits+7)/8 : 0); if (p) iobuf_write (a, p, (ndatabits+7)/8 ); } else { /* Non-protected key. */ + if (is_v5) + { + unsigned int skbytes = 0; + unsigned int n; + int j; + + for (j=i; j < nskey; j++ ) + { + if ((err = gpg_mpi_write (NULL, pk->pkey[j], &n))) + goto leave; + skbytes += n; + } + + write_32 (a, skbytes); + } + for ( ; i < nskey; i++ ) - if ( (err = gpg_mpi_write (a, pk->pkey[i]))) + if ( (err = gpg_mpi_write (a, pk->pkey[i], NULL))) goto leave; + write_16 (a, ski->csum ); } } @@ -593,11 +662,23 @@ do_key (iobuf_t out, int ctb, PKT_public_key *pk) if (!err) { /* Build the header of the packet - which we must do after - writing all the other stuff, so that we know the length of - the packet */ - write_header2 (out, ctb, iobuf_get_temp_length(a), 0); + * writing all the other stuff, so that we know the length of + * the packet */ + u32 len = iobuf_get_temp_length (a); + len += 1; /* version number */ + len += 4; /* timestamp */ + len += 1; /* algo */ + if (is_v5) + len += 4; /* public key material count */ + + write_header2 (out, ctb, len, 0); /* And finally write it out to the real stream. */ - err = iobuf_write_temp (out, a); + iobuf_put (out, pk->version? pk->version : 4); /* version number */ + write_32 (out, pk->timestamp ); + iobuf_put (out, pk->pubkey_algo); /* algo */ + if (is_v5) + write_32 (out, pkbytes); /* public key material count */ + err = iobuf_write_temp (out, a); /* pub and sec key material */ } iobuf_close (a); /* Close the temporary buffer */ @@ -688,7 +769,7 @@ do_pubkey_enc( IOBUF out, int ctb, PKT_pubkey_enc *enc ) if (enc->pubkey_algo == PUBKEY_ALGO_ECDH && i == 1) rc = gpg_mpi_write_nohdr (a, enc->data[i]); else - rc = gpg_mpi_write (a, enc->data[i]); + rc = gpg_mpi_write (a, enc->data[i], NULL); } if (!rc) @@ -1135,10 +1216,10 @@ build_sig_subpkt_from_sig (PKT_signature *sig, PKT_public_key *pksk) /* Write the new ISSUER_FPR subpacket. */ fingerprint_from_pk (pksk, buf+1, &fprlen); - if (fprlen == 20) + if (fprlen == 20 || fprlen == 32) { buf[0] = pksk->version; - build_sig_subpkt (sig, SIGSUBPKT_ISSUER_FPR, buf, 21); + build_sig_subpkt (sig, SIGSUBPKT_ISSUER_FPR, buf, fprlen + 1); } /* Write the timestamp. */ @@ -1297,7 +1378,7 @@ string_to_notation(const char *string,int is_utf8) } notation->name=xmalloc((s-string)+1); - strncpy(notation->name,string,s-string); + memcpy(notation->name,string,s-string); notation->name[s-string]='\0'; if(!saw_at && !opt.expert) @@ -1536,7 +1617,7 @@ do_signature( IOBUF out, int ctb, PKT_signature *sig ) else iobuf_put( a, sig->version ); if ( sig->version < 4 ) - iobuf_put (a, 5 ); /* Constant */ + iobuf_put (a, 5 ); /* Constant used by pre-v4 signatures. */ iobuf_put (a, sig->sig_class ); if ( sig->version < 4 ) { @@ -1567,7 +1648,7 @@ do_signature( IOBUF out, int ctb, PKT_signature *sig ) if ( !n ) write_fake_data( a, sig->data[0] ); for (i=0; i < n && !rc ; i++ ) - rc = gpg_mpi_write (a, sig->data[i] ); + rc = gpg_mpi_write (a, sig->data[i], NULL); if (!rc) { diff --git a/g10/call-agent.c b/g10/call-agent.c index 755f2e30b..83777534e 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -609,6 +609,8 @@ learn_status_cb (void *opaque, const char *line) parm->extcap.ki = abool; else if (!strcmp (p, "aac")) parm->extcap.aac = abool; + else if (!strcmp (p, "bt")) + parm->extcap.bt = abool; else if (!strcmp (p, "kdf")) parm->extcap.kdf = abool; else if (!strcmp (p, "si")) @@ -705,6 +707,21 @@ learn_status_cb (void *opaque, const char *line) xfree (parm->private_do[no]); parm->private_do[no] = unescape_status_string (line); } + else if (keywordlen == 3 && !memcmp (keyword, "KDF", 3)) + { + parm->kdf_do_enabled = 1; + } + else if (keywordlen == 5 && !memcmp (keyword, "UIF-", 4) + && strchr("123", keyword[4])) + { + unsigned char *data; + int no = keyword[4] - '1'; + + log_assert (no >= 0 && no <= 2); + data = unescape_status_string (line); + parm->uif[no] = (data[0] != 0xff); + xfree (data); + } return 0; } @@ -1201,7 +1218,7 @@ agent_scd_cardlist (strlist_t *result) memset (&parm, 0, sizeof parm); *result = NULL; - err = start_agent (NULL, 1); + err = start_agent (NULL, 1 | FLAG_FOR_CARD_SUPPRESS_ERRORS); if (err) return err; @@ -1444,19 +1461,19 @@ gpg_agent_get_confirmation (const char *desc) } -/* Return the S2K iteration count as computed by gpg-agent. */ -gpg_error_t -agent_get_s2k_count (unsigned long *r_count) +/* Return the S2K iteration count as computed by gpg-agent. On error + * print a warning and return a default value. */ +unsigned long +agent_get_s2k_count (void) { gpg_error_t err; membuf_t data; char *buf; - - *r_count = 0; + unsigned long count = 0; err = start_agent (NULL, 0); if (err) - return err; + goto leave; init_membuf (&data, 32); err = assuan_transact (agent_ctx, "GETINFO s2k_count", @@ -1472,11 +1489,23 @@ agent_get_s2k_count (unsigned long *r_count) err = gpg_error_from_syserror (); else { - *r_count = strtoul (buf, NULL, 10); + count = strtoul (buf, NULL, 10); xfree (buf); } } - return err; + + leave: + if (err || count < 65536) + { + /* Don't print an error if an older agent is used. */ + if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER) + log_error (_("problem with the agent: %s\n"), gpg_strerror (err)); + + /* Default to 65536 which was used up to 2.0.13. */ + count = 65536; + } + + return count; } diff --git a/g10/call-agent.h b/g10/call-agent.h index 59e4ff486..8619a34f8 100644 --- a/g10/call-agent.h +++ b/g10/call-agent.h @@ -69,8 +69,11 @@ struct agent_card_info_s unsigned int ki:1; /* Key import available. */ unsigned int aac:1; /* Algorithm attributes are changeable. */ unsigned int kdf:1; /* KDF object to support PIN hashing available. */ + unsigned int bt:1; /* Button for confirmation available. */ } extcap; unsigned int status_indicator; + int kdf_do_enabled; /* True if card has a KDF object. */ + int uif[3]; /* True if User Interaction Flag is on. */ }; @@ -143,7 +146,7 @@ gpg_error_t agent_clear_passphrase (const char *cache_id); gpg_error_t gpg_agent_get_confirmation (const char *desc); /* Return the S2K iteration count as computed by gpg-agent. */ -gpg_error_t agent_get_s2k_count (unsigned long *r_count); +unsigned long agent_get_s2k_count (void); /* Check whether a secret key for public key PK is available. Returns 0 if the secret key is available. */ @@ -192,14 +195,14 @@ gpg_error_t agent_keywrap_key (ctrl_t ctrl, int forexport, gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, const void *key, size_t keylen, int unattended, int force, - u32 *keyid, u32 *mainkeyid, int pubkey_algo); + u32 *keyid, u32 *mainkeyid, int pubkey_algo); /* Receive a key from the agent. */ gpg_error_t agent_export_key (ctrl_t ctrl, const char *keygrip, const char *desc, int openpgp_protected, char **cache_nonce_addr, unsigned char **r_result, size_t *r_resultlen, - u32 *keyid, u32 *mainkeyid, int pubkey_algo); + u32 *keyid, u32 *mainkeyid, int pubkey_algo); /* Delete a key from the agent. */ gpg_error_t agent_delete_key (ctrl_t ctrl, const char *hexkeygrip, diff --git a/g10/call-dirmngr.c b/g10/call-dirmngr.c index 11663b9b1..8f83c087f 100644 --- a/g10/call-dirmngr.c +++ b/g10/call-dirmngr.c @@ -608,6 +608,12 @@ gpg_dirmngr_ks_search (ctrl_t ctrl, const char *searchstr, NULL, NULL, ks_status_cb, &stparm); if (!err) err = cb (cb_value, 0, NULL); /* Send EOF. */ + else if (parm.stparm->source) + { + /* Error but we received a SOURCE status. Tell via callback but + * ignore errors. */ + parm.data_cb (parm.data_cb_value, 1, parm.stparm->source); + } xfree (get_membuf (&parm.saveddata, NULL)); xfree (parm.helpbuf); @@ -650,6 +656,7 @@ ks_get_data_cb (void *opaque, const void *data, size_t datalen) If R_SOURCE is not NULL the source of the data is stored as a malloced string there. If a source is not known NULL is stored. + Note that this may even be returned after an error. If there are too many patterns the function returns an error. That could be fixed by issuing several search commands or by @@ -737,13 +744,13 @@ gpg_dirmngr_ks_get (ctrl_t ctrl, char **pattern, *r_fp = parm.memfp; parm.memfp = NULL; - if (r_source) + + leave: + if (r_source && stparm.source) { *r_source = stparm.source; stparm.source = NULL; } - - leave: es_fclose (parm.memfp); xfree (stparm.source); xfree (line); @@ -1076,7 +1083,7 @@ ks_put_inq_cb (void *opaque, const char *line) /* Send a key to the configured server. {DATA,DATLEN} contains the key in OpenPGP binary transport format. If KEYBLOCK is not NULL it - has the internal representaion of that key; this is for example + has the internal representation of that key; this is for example used to convey meta data to LDAP keyservers. */ gpg_error_t gpg_dirmngr_ks_put (ctrl_t ctrl, void *data, size_t datalen, kbnode_t keyblock) diff --git a/g10/card-util.c b/g10/card-util.c index e9c0120a1..08844bae3 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -216,6 +216,7 @@ get_manufacturer (unsigned int no) case 0x1337: return "Warsaw Hackerspace"; case 0x2342: return "warpzone"; /* hackerspace Muenster. */ + case 0x4354: return "Confidential Technologies"; /* cotech.de */ case 0x63AF: return "Trustica"; case 0xBD0E: return "Paranoidlabs"; case 0xF517: return "FSIJ"; @@ -275,7 +276,7 @@ print_keygrip (estream_t fp, const unsigned char *grp) { tty_fprintf (fp, " keygrip ....: "); for (i=0; i < 20 ; i++, grp++) - es_fprintf (fp, "%02X", *grp); + tty_fprintf (fp, "%02X", *grp); tty_fprintf (fp, "\n"); } } @@ -511,6 +512,15 @@ current_card_status (ctrl_t ctrl, estream_t fp, es_fprintf (fp, "pinretry:%d:%d:%d:\n", info.chvretry[0], info.chvretry[1], info.chvretry[2]); es_fprintf (fp, "sigcount:%lu:::\n", info.sig_counter); + if (info.extcap.kdf) + { + es_fprintf (fp, "kdf:%s:\n", info.kdf_do_enabled ? "on" : "off"); + } + if (info.extcap.bt) + { + es_fprintf (fp, "uif:%d:%d:%d:\n", + info.uif[0], info.uif[1], info.uif[2]); + } for (i=0; i < 4; i++) { @@ -617,6 +627,17 @@ current_card_status (ctrl_t ctrl, estream_t fp, tty_fprintf (fp, "PIN retry counter : %d %d %d\n", info.chvretry[0], info.chvretry[1], info.chvretry[2]); tty_fprintf (fp, "Signature counter : %lu\n", info.sig_counter); + if (info.extcap.kdf) + { + tty_fprintf (fp, "KDF setting ......: %s\n", + info.kdf_do_enabled ? "on" : "off"); + } + if (info.extcap.bt) + { + tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", + info.uif[0] ? "on" : "off", info.uif[1] ? "on" : "off", + info.uif[2] ? "on" : "off"); + } tty_fprintf (fp, "Signature key ....:"); print_shax_fpr (fp, info.fpr1len? info.fpr1:NULL, info.fpr1len); if (info.fpr1len && info.fpr1time) @@ -647,7 +668,7 @@ current_card_status (ctrl_t ctrl, estream_t fp, info.fpr3len? info.fpr3 : NULL); thefprlen = (info.fpr1len? info.fpr1len : info.fpr2len? info.fpr2len : info.fpr3len? info.fpr3len : 0); - /* If the fingerprint is all 0xff, the key has no asssociated + /* If the fingerprint is all 0xff, the key has no associated OpenPGP certificate. */ if ( thefpr && !fpr_is_ff (thefpr, thefprlen) && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen)) @@ -674,7 +695,7 @@ card_status (ctrl_t ctrl, estream_t fp, const char *serialno) { int err; strlist_t card_list, sl; - char *serialno0; + char *serialno0, *serialno1; int all_cards = 0; if (serialno == NULL) @@ -700,8 +721,6 @@ card_status (ctrl_t ctrl, estream_t fp, const char *serialno) for (sl = card_list; sl; sl = sl->next) { - char *serialno1; - if (!all_cards && strcmp (serialno, sl->d)) continue; @@ -722,7 +741,8 @@ card_status (ctrl_t ctrl, estream_t fp, const char *serialno) } /* Select the original card again. */ - err = agent_scd_serialno (&serialno0, serialno0); + err = agent_scd_serialno (&serialno1, serialno0); + xfree (serialno1); leave: xfree (serialno0); @@ -2019,7 +2039,7 @@ gen_kdf_data (unsigned char *data, int single_salt) p = data; - s2k_char = encode_s2k_iterations (0); + s2k_char = encode_s2k_iterations (agent_get_s2k_count ()); iterations = S2K_DECODE_COUNT (s2k_char); count_4byte[0] = (iterations >> 24) & 0xff; count_4byte[1] = (iterations >> 16) & 0xff; @@ -2110,6 +2130,49 @@ kdf_setup (const char *args) leave: agent_release_card_info (&info); } + +static void +uif (int arg_number, const char *arg_rest) +{ + struct agent_card_info_s info; + int feature_available; + gpg_error_t err; + char name[100]; + unsigned char data[2]; + + memset (&info, 0, sizeof info); + + err = agent_scd_getattr ("EXTCAP", &info); + if (err) + { + log_error (_("error getting card info: %s\n"), gpg_strerror (err)); + return; + } + + feature_available = info.extcap.bt; + agent_release_card_info (&info); + + if (!feature_available) + { + log_error (_("This command is not supported by this card\n")); + tty_printf ("\n"); + return; + } + + snprintf (name, sizeof name, "UIF-%d", arg_number); + if ( !strcmp (arg_rest, "off") ) + data[0] = 0x00; + else if ( !strcmp (arg_rest, "on") ) + data[0] = 0x01; + else if ( !strcmp (arg_rest, "permanent") ) + data[0] = 0x02; + + data[1] = 0x20; + + err = agent_scd_setattr (name, data, 2, NULL); + if (err) + log_error (_("error for setup UIF: %s\n"), gpg_strerror (err)); +} /* Data used by the command parser. This needs to be outside of the function scope to allow readline based command completion. */ @@ -2120,7 +2183,7 @@ enum cmdids cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSEX, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP, - cmdKEYATTR, + cmdKEYATTR, cmdUIF, cmdINVCMD }; @@ -2152,10 +2215,11 @@ static struct { "generate", cmdGENERATE, 1, N_("generate new keys")}, { "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")}, { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, - { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code") }, + { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")}, { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")}, { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")}, { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")}, + { "uif", cmdUIF, 1, N_("change the User Interaction Flag")}, /* Note, that we do not announce these command yet. */ { "privatedo", cmdPRIVATEDO, 0, NULL }, { "readcert", cmdREADCERT, 0, NULL }, @@ -2447,6 +2511,14 @@ card_edit (ctrl_t ctrl, strlist_t commands) key_attr (); break; + case cmdUIF: + if ( arg_number < 1 || arg_number > 3 ) + tty_printf ("usage: uif N [on|off|permanent]\n" + " 1 <= N <= 3\n"); + else + uif (arg_number, arg_rest); + break; + case cmdQUIT: goto leave; diff --git a/g10/cipher-aead.c b/g10/cipher-aead.c index f9a996c80..b14b85444 100644 --- a/g10/cipher-aead.c +++ b/g10/cipher-aead.c @@ -278,7 +278,7 @@ do_flush (cipher_filter_context_t *cfx, iobuf_t a, byte *buf, size_t size) if (DBG_FILTER) log_debug ("chunksize %ju reached;" " cur buflen=%zu using %zu of %zu\n", - (uintmax_t)cfx->chunksize, (uintmax_t)cfx->buflen, + cfx->chunksize, cfx->buflen, n1, n); n = n1; } @@ -142,7 +142,8 @@ write_status ( int no ) /* Write a status line with code NO followed by the string TEXT and - directly followed by the remaining strings up to a NULL. */ + * directly followed by the remaining strings up to a NULL. Embedded + * CR and LFs in the strings (but not in TEXT) are C-style escaped.*/ void write_status_strings (int no, const char *text, ...) { @@ -187,13 +188,13 @@ write_status_text (int no, const char *text) } -/* Write a status line with code NO followed by the outout of the - * printf style FORMAT. The caller needs to make sure that LFs and - * CRs are not printed. */ +/* Write a status line with code NO followed by the output of the + * printf style FORMAT. Embedded CR and LFs are C-style escaped. */ void write_status_printf (int no, const char *format, ...) { va_list arg_ptr; + char *buf; if (!statusfp || !status_currently_allowed (no) ) return; /* Not enabled or allowed. */ @@ -204,7 +205,30 @@ write_status_printf (int no, const char *format, ...) { es_putc ( ' ', statusfp); va_start (arg_ptr, format); - es_vfprintf (statusfp, format, arg_ptr); + buf = gpgrt_vbsprintf (format, arg_ptr); + if (!buf) + log_error ("error printing status line: %s\n", + gpg_strerror (gpg_err_code_from_syserror ())); + else + { + if (strpbrk (buf, "\r\n")) + { + const byte *s; + for (s=buf; *s; s++) + { + if (*s == '\n') + es_fputs ("\\n", statusfp); + else if (*s == '\r') + es_fputs ("\\r", statusfp); + else + es_fputc (*s, statusfp); + } + } + else + es_fputs (buf, statusfp); + gpgrt_free (buf); + } + va_end (arg_ptr); } es_putc ('\n', statusfp); diff --git a/g10/decrypt-data.c b/g10/decrypt-data.c index a3151b5ed..4d9dc86d9 100644 --- a/g10/decrypt-data.c +++ b/g10/decrypt-data.c @@ -42,7 +42,7 @@ static int decode_filter ( void *opaque, int control, IOBUF a, /* Our context object. */ struct decode_filter_context_s { - /* Recounter (max value is 2). We need it becuase we do not know + /* Recounter (max value is 2). We need it because we do not know * whether the iobuf or the outer control code frees this object * first. */ int refcount; @@ -551,31 +551,42 @@ fill_buffer (decode_filter_ctx_t dfx, iobuf_t stream, byte *buffer, size_t nbytes, size_t offset) { size_t nread = offset; - int c; + size_t curr; + int ret; if (dfx->partial) { - for (; nread < nbytes; nread++ ) + while (nread < nbytes) { - if ((c = iobuf_get (stream)) == -1) + curr = nbytes - nread; + + ret = iobuf_read (stream, &buffer[nread], curr); + if (ret == -1) { dfx->eof_seen = 1; /* Normal EOF. */ break; } - buffer[nread] = c; + + nread += ret; } } else { - for (; nread < nbytes && dfx->length; nread++, dfx->length--) + while (nread < nbytes && dfx->length) { - c = iobuf_get (stream); - if (c == -1) + curr = nbytes - nread; + if (curr > dfx->length) + curr = dfx->length; + + ret = iobuf_read (stream, &buffer[nread], curr); + if (ret == -1) { dfx->eof_seen = 3; /* Premature EOF. */ break; } - buffer[nread] = c; + + nread += ret; + dfx->length -= ret; } if (!dfx->length) dfx->eof_seen = 1; /* Normal EOF. */ @@ -647,7 +658,7 @@ aead_underflow (decode_filter_ctx_t dfx, iobuf_t a, byte *buf, size_t *ret_len) * case when a chunk ends within the buffer. */ if (DBG_FILTER) log_debug ("decrypt: chunklen=%ju total=%ju size=%zu len=%zu%s\n", - (uintmax_t)dfx->chunklen, (uintmax_t)dfx->total, size, len, + dfx->chunklen, dfx->total, size, len, dfx->eof_seen? " eof":""); while (len && dfx->chunklen + len >= dfx->chunksize) @@ -683,7 +694,7 @@ aead_underflow (decode_filter_ctx_t dfx, iobuf_t a, byte *buf, size_t *ret_len) len -= n; if (DBG_FILTER) - log_debug ("ndecrypted: %zu (nchunk=%zu) bytes left: %zu at off=%zu\n", + log_debug ("ndecrypted: %zu (nchunk=%ju) bytes left: %zu at off=%zu\n", totallen, dfx->chunklen, len, off); /* Check the tag. */ @@ -765,7 +776,7 @@ aead_underflow (decode_filter_ctx_t dfx, iobuf_t a, byte *buf, size_t *ret_len) dfx->chunklen += len; dfx->total += len; if (DBG_FILTER) - log_debug ("ndecrypted: %zu (nchunk=%zu)\n", totallen, dfx->chunklen); + log_debug ("ndecrypted: %zu (nchunk=%ju)\n", totallen, dfx->chunklen); } if (dfx->eof_seen) @@ -873,7 +884,6 @@ mdc_decode_filter (void *opaque, int control, IOBUF a, decode_filter_ctx_t dfx = opaque; size_t n, size = *ret_len; int rc = 0; - int c; /* Note: We need to distinguish between a partial and a fixed length packet. The first is the usual case as created by GPG. However @@ -894,25 +904,7 @@ mdc_decode_filter (void *opaque, int control, IOBUF a, log_assert (size > 44); /* Our code requires at least this size. */ /* Get at least 22 bytes and put it ahead in the buffer. */ - if (dfx->partial) - { - for (n=22; n < 44; n++) - { - if ( (c = iobuf_get(a)) == -1 ) - break; - buf[n] = c; - } - } - else - { - for (n=22; n < 44 && dfx->length; n++, dfx->length--) - { - c = iobuf_get (a); - if (c == -1) - break; /* Premature EOF. */ - buf[n] = c; - } - } + n = fill_buffer (dfx, a, buf, 44, 22); if (n == 44) { /* We have enough stuff - flush the holdback buffer. */ @@ -923,37 +915,11 @@ mdc_decode_filter (void *opaque, int control, IOBUF a, } else { - memcpy (buf, dfx->holdback, 22); } + /* Fill up the buffer. */ - if (dfx->partial) - { - for (; n < size; n++ ) - { - if ( (c = iobuf_get(a)) == -1 ) - { - dfx->eof_seen = 1; /* Normal EOF. */ - break; - } - buf[n] = c; - } - } - else - { - for (; n < size && dfx->length; n++, dfx->length--) - { - c = iobuf_get(a); - if (c == -1) - { - dfx->eof_seen = 3; /* Premature EOF. */ - break; - } - buf[n] = c; - } - if (!dfx->length) - dfx->eof_seen = 1; /* Normal EOF. */ - } + n = fill_buffer (dfx, a, buf, size, n); /* Move the trailing 22 bytes back to the holdback buffer. We have at least 44 bytes thus a memmove is not needed. */ @@ -1008,7 +974,7 @@ decode_filter( void *opaque, int control, IOBUF a, byte *buf, size_t *ret_len) decode_filter_ctx_t fc = opaque; size_t size = *ret_len; size_t n; - int c, rc = 0; + int rc = 0; if ( control == IOBUFCTRL_UNDERFLOW && fc->eof_seen ) @@ -1020,34 +986,7 @@ decode_filter( void *opaque, int control, IOBUF a, byte *buf, size_t *ret_len) { log_assert (a); - if (fc->partial) - { - for (n=0; n < size; n++ ) - { - c = iobuf_get(a); - if (c == -1) - { - fc->eof_seen = 1; /* Normal EOF. */ - break; - } - buf[n] = c; - } - } - else - { - for (n=0; n < size && fc->length; n++, fc->length--) - { - c = iobuf_get(a); - if (c == -1) - { - fc->eof_seen = 3; /* Premature EOF. */ - break; - } - buf[n] = c; - } - if (!fc->length) - fc->eof_seen = 1; /* Normal EOF. */ - } + n = fill_buffer (fc, a, buf, size, 0); if (n) { if (fc->cipher_hd) diff --git a/g10/delkey.c b/g10/delkey.c index bf8c4e93b..cc5673846 100644 --- a/g10/delkey.c +++ b/g10/delkey.c @@ -69,9 +69,7 @@ do_delete_key (ctrl_t ctrl, const char *username, int secret, int force, /* Search the userid. */ err = classify_user_id (username, &desc, 1); - exactmatch = (desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR16 - || desc.mode == KEYDB_SEARCH_MODE_FPR20); + exactmatch = (desc.mode == KEYDB_SEARCH_MODE_FPR); if (!err) err = keydb_search (hd, &desc, 1, NULL); if (err) diff --git a/g10/encrypt.c b/g10/encrypt.c index 04a9ab214..972d13c7c 100644 --- a/g10/encrypt.c +++ b/g10/encrypt.c @@ -732,7 +732,7 @@ encrypt_crypt (ctrl_t ctrl, int filefd, const char *filename, /* In case 3DES has been selected, print a warning if any key does not have a preference for AES. This should help to - indentify why encrypting to several recipients falls back to + identify why encrypting to several recipients falls back to 3DES. */ if (opt.verbose && cfx.dek->algo == CIPHER_ALGO_3DES) warn_missing_aes_from_pklist (pk_list); @@ -1003,7 +1003,7 @@ encrypt_filter (void *opaque, int control, /* In case 3DES has been selected, print a warning if any key does not have a preference for AES. This - should help to indentify why encrypting to several + should help to identify why encrypting to several recipients falls back to 3DES. */ if (opt.verbose && efx->cfx.dek->algo == CIPHER_ALGO_3DES) diff --git a/g10/export.c b/g10/export.c index e94e959fb..4f6c9137e 100644 --- a/g10/export.c +++ b/g10/export.c @@ -97,7 +97,7 @@ cleanup_export_globals (void) } -/* Option parser for export options. See parse_options fro +/* Option parser for export options. See parse_options for details. */ int parse_export_options(char *str,unsigned int *options,int noisy) @@ -114,6 +114,8 @@ parse_export_options(char *str,unsigned int *options,int noisy) N_("remove unusable parts from key during export")}, {"export-minimal",EXPORT_MINIMAL|EXPORT_CLEAN,NULL, N_("remove as much as possible from key during export")}, + {"export-drop-uids", EXPORT_DROP_UIDS, NULL, + N_("Do not export user id or attribute packets")}, {"export-pka", EXPORT_PKA_FORMAT, NULL, NULL }, {"export-dane", EXPORT_DANE_FORMAT, NULL, NULL }, @@ -136,14 +138,20 @@ parse_export_options(char *str,unsigned int *options,int noisy) int rc; rc = parse_options (str, options, export_opts, noisy); - if (rc && (*options & EXPORT_BACKUP)) + if (!rc) + return 0; + + /* Alter other options we want or don't want for restore. */ + if ((*options & EXPORT_BACKUP)) { - /* Alter other options we want or don't want for restore. */ *options |= (EXPORT_LOCAL_SIGS | EXPORT_ATTRIBUTES | EXPORT_SENSITIVE_REVKEYS); *options &= ~(EXPORT_CLEAN | EXPORT_MINIMAL | EXPORT_PKA_FORMAT | EXPORT_DANE_FORMAT); } + /* Dropping uids also means to drop attributes. */ + if ((*options & EXPORT_DROP_UIDS)) + *options &= ~(EXPORT_ATTRIBUTES); return rc; } @@ -443,10 +451,8 @@ exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node) keyid_from_pk (node->pkt->pkt.public_key, kid); break; - case KEYDB_SEARCH_MODE_FPR16: - case KEYDB_SEARCH_MODE_FPR20: case KEYDB_SEARCH_MODE_FPR: - fingerprint_from_pk (node->pkt->pkt.public_key, fpr,&fprlen); + fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen); break; default: @@ -465,14 +471,8 @@ exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, KBNODE node) result = 1; break; - case KEYDB_SEARCH_MODE_FPR16: - if (!memcmp (desc->u.fpr, fpr, 16)) - result = 1; - break; - - case KEYDB_SEARCH_MODE_FPR20: case KEYDB_SEARCH_MODE_FPR: - if (!memcmp (desc->u.fpr, fpr, 20)) + if (fprlen == desc->fprlen && !memcmp (desc->u.fpr, fpr, desc->fprlen)) result = 1; break; @@ -1171,7 +1171,7 @@ print_status_exported (PKT_public_key *pk) * passphrase-protected. Otherwise, store secret key material in the * clear. * - * CACHE_NONCE_ADDR is used to share nonce for multple key retrievals. + * CACHE_NONCE_ADDR is used to share nonce for multiple key retrievals. */ gpg_error_t receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd, @@ -1461,7 +1461,7 @@ print_pka_or_dane_records (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk, continue; xfree (mbox); - mbox = mailbox_from_userid (uid->name); + mbox = mailbox_from_userid (uid->name, 0); if (!mbox) continue; @@ -1575,7 +1575,7 @@ do_export_one_keyblock (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, if (node->pkt->pkttype == PKT_COMMENT) continue; - /* Skip ring trust packets - they should not ne here anyway. */ + /* Skip ring trust packets - they should not be here anyway. */ if (node->pkt->pkttype == PKT_RING_TRUST) continue; @@ -1650,6 +1650,19 @@ do_export_one_keyblock (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, } } + /* Don't export user ids (and attributes)? This is not RFC-4880 + * compliant but we allow it anyway. */ + if ((options & EXPORT_DROP_UIDS) + && node->pkt->pkttype == PKT_USER_ID) + { + /* Skip until we get to something that is not a user id (or + * attrib) or a signature on it. */ + while (kbctx->next && kbctx->next->pkt->pkttype == PKT_SIGNATURE) + kbctx = kbctx->next; + + continue; + } + /* Don't export attribs? */ if (!(options & EXPORT_ATTRIBUTES) && node->pkt->pkttype == PKT_USER_ID diff --git a/g10/filter.h b/g10/filter.h index 6daf273fa..b2ef3828f 100644 --- a/g10/filter.h +++ b/g10/filter.h @@ -61,7 +61,7 @@ typedef struct { byte radbuf[4]; int idx, idx2; - u32 crc; + gcry_md_hd_t crc_md; int status; /* an internal state flag */ int cancel; @@ -69,8 +69,6 @@ typedef struct { int pending_lf; /* used together with faked */ } armor_filter_context_t; -struct unarmor_pump_s; -typedef struct unarmor_pump_s *UnarmorPump; struct compress_filter_context_s { @@ -172,9 +170,6 @@ armor_filter_context_t *new_armor_context (void); void release_armor_context (armor_filter_context_t *afx); int push_armor_filter (armor_filter_context_t *afx, iobuf_t iobuf); int use_armor_filter( iobuf_t a ); -UnarmorPump unarmor_pump_new (void); -void unarmor_pump_release (UnarmorPump x); -int unarmor_pump (UnarmorPump x, int c); /*-- compress.c --*/ gpg_error_t push_compress_filter (iobuf_t out, compress_filter_context_t *zfx, diff --git a/g10/getkey.c b/g10/getkey.c index ea2dee21d..9dae879d2 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -60,7 +60,7 @@ struct getkey_ctx_s search or not. A search that is exact requires that a key or subkey meet all of the specified criteria. A search that is not exact allows selecting a different key or subkey from the - keyblock that matched the critera. Further, an exact search + keyblock that matched the criteria. Further, an exact search returns the key or subkey that matched whereas a non-exact search typically returns the primary key. See finish_lookup for details. */ @@ -897,8 +897,6 @@ key_byname (ctrl_t ctrl, GETKEY_CTX *retctx, strlist_t namelist, if (!include_unusable && ctx->items[n].mode != KEYDB_SEARCH_MODE_SHORT_KID && ctx->items[n].mode != KEYDB_SEARCH_MODE_LONG_KID - && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR16 - && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR20 && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR) { ctx->items[n].skipfnc = skip_unusable; @@ -1373,7 +1371,7 @@ pubkey_cmp (ctrl_t ctrl, const char *name, struct pubkey_cmp_cookie *old, n; n = find_next_kbnode (n, PKT_USER_ID)) { PKT_user_id *uid = n->pkt->pkt.user_id; - char *mbox = mailbox_from_userid (uid->name); + char *mbox = mailbox_from_userid (uid->name, 0); int match = mbox ? strcasecmp (name, mbox) == 0 : 0; xfree (mbox); @@ -1654,7 +1652,7 @@ get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock, if (r_keyblock) *r_keyblock = NULL; - if (fprint_len == 20 || fprint_len == 16) + if (fprint_len == 32 || fprint_len == 20 || fprint_len == 16) { struct getkey_ctx_s ctx; KBNODE kb = NULL; @@ -1670,9 +1668,9 @@ get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock, return gpg_error_from_syserror (); ctx.nitems = 1; - ctx.items[0].mode = fprint_len == 16 ? KEYDB_SEARCH_MODE_FPR16 - : KEYDB_SEARCH_MODE_FPR20; + ctx.items[0].mode = KEYDB_SEARCH_MODE_FPR; memcpy (ctx.items[0].u.fpr, fprint, fprint_len); + ctx.items[0].fprlen = fprint_len; if (pk) ctx.req_usage = pk->req_usage; rc = lookup (ctrl, &ctx, 0, &kb, &found_key); @@ -1745,8 +1743,6 @@ get_keyblock_byfprint_fast (kbnode_t *r_keyblock, KEYDB_HANDLE *r_hd, for (i = 0; i < MAX_FINGERPRINT_LEN && i < fprint_len; i++) fprbuf[i] = fprint[i]; - while (i < MAX_FINGERPRINT_LEN) - fprbuf[i++] = 0; hd = keydb_new (); if (!hd) @@ -1770,7 +1766,7 @@ get_keyblock_byfprint_fast (kbnode_t *r_keyblock, KEYDB_HANDLE *r_hd, if (r_hd) *r_hd = hd; - err = keydb_search_fpr (hd, fprbuf); + err = keydb_search_fpr (hd, fprbuf, fprint_len); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { if (!r_hd) @@ -2577,10 +2573,22 @@ merge_selfsigs_main (ctrl_t ctrl, kbnode_t keyblock, int *r_revoked, xrealloc (pk->revkey, sizeof (struct revocation_key) * (pk->numrevkeys + sig->numrevkeys)); - for (i = 0; i < sig->numrevkeys; i++) - memcpy (&pk->revkey[pk->numrevkeys++], - &sig->revkey[i], - sizeof (struct revocation_key)); + for (i = 0; i < sig->numrevkeys; i++, pk->numrevkeys++) + { + pk->revkey[pk->numrevkeys].class + = sig->revkey[i].class; + pk->revkey[pk->numrevkeys].algid + = sig->revkey[i].algid; + pk->revkey[pk->numrevkeys].fprlen + = sig->revkey[i].fprlen; + memcpy (pk->revkey[pk->numrevkeys].fpr, + sig->revkey[i].fpr, sig->revkey[i].fprlen); + memset (pk->revkey[pk->numrevkeys].fpr + + sig->revkey[i].fprlen, + 0, + sizeof (sig->revkey[i].fpr) + - sig->revkey[i].fprlen); + } } if (sig->timestamp >= sigdate) @@ -3364,7 +3372,7 @@ merge_selfsigs (ctrl_t ctrl, kbnode_t keyblock) * * 1. No requested usage and no primary key requested * Examples for this case are that we have a keyID to be used - * for decrytion or verification. + * for decryption or verification. * 2. No usage but primary key requested * This is the case for all functions which work on an * entire keyblock, e.g. for editing or listing @@ -2009,6 +2009,8 @@ parse_list_options(char *str) N_("show expiration dates during signature listings")}, {"show-sig-subpackets",LIST_SHOW_SIG_SUBPACKETS,NULL, NULL}, + {"show-only-fpr-mbox",LIST_SHOW_ONLY_FPR_MBOX, NULL, + NULL}, {NULL,0,NULL,NULL} }; @@ -2146,6 +2148,8 @@ static struct gnupg_compliance_option compliance_options[] = static void set_compliance_option (enum cmd_and_opt_values option) { + opt.flags.rfc4880bis = 0; /* Clear becuase it is initially set. */ + switch (option) { case oRFC4880bis: @@ -2192,7 +2196,10 @@ set_compliance_option (enum cmd_and_opt_values option) break; case oPGP7: opt.compliance = CO_PGP7; break; case oPGP8: opt.compliance = CO_PGP8; break; - case oGnuPG: opt.compliance = CO_GNUPG; break; + case oGnuPG: + opt.compliance = CO_GNUPG; + opt.flags.rfc4880bis = 1; + break; case oDE_VS: set_compliance_option (oOpenPGP); @@ -2439,6 +2446,8 @@ main (int argc, char **argv) opt.passphrase_repeat = 1; opt.emit_version = 0; opt.weak_digests = NULL; + opt.compliance = CO_GNUPG; + opt.flags.rfc4880bis = 1; /* Check whether we have a config file on the command line. */ orig_argc = argc; @@ -3130,7 +3139,7 @@ main (int argc, char **argv) break; case oSender: { - char *mbox = mailbox_from_userid (pargs.r.ret_str); + char *mbox = mailbox_from_userid (pargs.r.ret_str, 0); if (!mbox) log_error (_("\"%s\" is not a proper mail address\n"), pargs.r.ret_str); @@ -3554,7 +3563,7 @@ main (int argc, char **argv) case oAutoKeyLocate: if (default_akl) { - /* This is the first time --aito-key-locate is seen. + /* This is the first time --auto-key-locate is seen. * We need to reset the default akl. */ default_akl = 0; release_akl(); @@ -3728,7 +3737,7 @@ main (int argc, char **argv) log_info(_("WARNING: program may create a core file!\n")); if (opt.flags.rfc4880bis) - log_info ("WARNING: using experimental features from RFC4880bis!\n"); + log_info ("Note: RFC4880bis features are enabled.\n"); else { opt.mimemode = 0; /* This will use text mode instead. */ @@ -3891,7 +3900,7 @@ main (int argc, char **argv) keygen_set_std_prefs(pers_compress_list,PREFTYPE_ZIP)) log_error(_("invalid personal compress preferences\n")); - /* Check chunk size. Please fix also the man page if you chnage + /* Check chunk size. Please fix also the man page if you change * the default. The limits are given by the specs. */ if (!opt.chunk_size) opt.chunk_size = 27; /* Default to the suggested max of 128 MiB. */ @@ -4875,9 +4884,9 @@ main (int argc, char **argv) while( endless || count ) { byte *p; - /* Wee need a multiple of 3, so that in case of + /* We need a multiple of 3, so that in case of armored output we get a correct string. No - linefolding is done, as it is best to levae this to + linefolding is done, as it is best to leave this to other tools */ size_t n = !endless && count < 99? count : 99; @@ -5073,8 +5082,6 @@ main (int argc, char **argv) if (! (desc.mode == KEYDB_SEARCH_MODE_SHORT_KID || desc.mode == KEYDB_SEARCH_MODE_LONG_KID - || desc.mode == KEYDB_SEARCH_MODE_FPR16 - || desc.mode == KEYDB_SEARCH_MODE_FPR20 || desc.mode == KEYDB_SEARCH_MODE_FPR || desc.mode == KEYDB_SEARCH_MODE_KEYGRIP)) { diff --git a/g10/gpgcompose.c b/g10/gpgcompose.c index b3f7ecdce..e882fa8e3 100644 --- a/g10/gpgcompose.c +++ b/g10/gpgcompose.c @@ -25,6 +25,7 @@ #include "keydb.h" #include "main.h" #include "options.h" +#include "call-agent.h" static int do_debug; #define debug(fmt, ...) \ @@ -2248,9 +2249,12 @@ sk_esk (const char *option, int argc, char *argv[], void *cookie) log_assert (sizeof (si.salt) == sizeof (ske->s2k.salt)); memcpy (ske->s2k.salt, si.salt, sizeof (ske->s2k.salt)); if (! si.s2k_is_session_key) - /* 0 means get the default. */ - ske->s2k.count = encode_s2k_iterations (si.iterations); - + { + if (!si.iterations) + ske->s2k.count = encode_s2k_iterations (agent_get_s2k_count ()); + else + ske->s2k.count = encode_s2k_iterations (si.iterations); + } /* Derive the symmetric key that is either the session key or the key used to encrypt the session key. */ @@ -2281,16 +2285,27 @@ sk_esk (const char *option, int argc, char *argv[], void *cookie) /* Encrypt the session key using the s2k specifier. */ { DEK *sesdekp = &sesdek; + void *enckey; + size_t enckeylen; /* Now encrypt the session key (or rather, the algorithm used to - encrypt the SKESK plus the session key) using ENCKEY. */ - err = encrypt_seskey (&s2kdek, 0, &sesdekp, - (void**)&ske->seskey, (size_t *)&ske->seskeylen); + encrypt the SKESK plus the session key) using S2KDEK. */ + err = encrypt_seskey (&s2kdek, 0, &sesdekp, &enckey, &enckeylen); + if (err) log_fatal ("encrypt_seskey failed: %s\n", gpg_strerror (err)); + if (enckeylen - 1 > sesdek.keylen) + log_fatal ("key size is too big: %zu\n", enckeylen); + else + { + ske->seskeylen = (byte)enckeylen; + memcpy (ske->seskey, enckey, enckeylen); + } + /* Save the session key for later. */ session_key = sesdek; + xfree (enckey); } pkt.pkttype = PKT_SYMKEY_ENC; @@ -3060,10 +3075,11 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, } void -show_basic_key_info (ctrl_t ctrl, KBNODE keyblock) +show_basic_key_info (ctrl_t ctrl, KBNODE keyblock, int made_from_sec) { (void)ctrl; - (void) keyblock; + (void)keyblock; + (void)made_from_sec; } int diff --git a/g10/import.c b/g10/import.c index 73f795cd9..c2a1dd033 100644 --- a/g10/import.c +++ b/g10/import.c @@ -1,6 +1,6 @@ /* import.c - import a key into our key storage. * Copyright (C) 1998-2007, 2010-2011 Free Software Foundation, Inc. - * Copyright (C) 2014, 2016, 2017 Werner Koch + * Copyright (C) 2014, 2016, 2017, 2019 Werner Koch * * This file is part of GnuPG. * @@ -75,6 +75,8 @@ struct import_stats_s #define NODE_DELETION_MARK 4 /* A node flag used to temporary mark a node. */ #define NODE_FLAG_A 8 +/* A flag used by transfer_secret_keys. */ +#define NODE_TRANSFER_SECKEY 16 /* An object and a global instance to store selectors created from @@ -109,11 +111,16 @@ static gpg_error_t import_one (ctrl_t ctrl, unsigned char **fpr, size_t *fpr_len, unsigned int options, int from_sk, int silent, import_screener_t screener, void *screener_arg, - int origin, const char *url); -static int import_secret_one (ctrl_t ctrl, kbnode_t keyblock, + int origin, const char *url, int *r_valid); +static gpg_error_t import_matching_seckeys ( + ctrl_t ctrl, kbnode_t seckeys, + const byte *mainfpr, size_t mainfprlen, + struct import_stats_s *stats, int batch); +static gpg_error_t import_secret_one (ctrl_t ctrl, kbnode_t keyblock, struct import_stats_s *stats, int batch, unsigned int options, int for_migration, - import_screener_t screener, void *screener_arg); + import_screener_t screener, void *screener_arg, + kbnode_t *r_secattic); static int import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options, struct import_stats_s *stats); static int chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, @@ -121,6 +128,7 @@ static int chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, static int delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, unsigned int options); static int any_uid_left (kbnode_t keyblock); +static int remove_all_uids (kbnode_t *keyblock); static int merge_blocks (ctrl_t ctrl, unsigned int options, kbnode_t keyblock_orig, kbnode_t keyblock, u32 *keyid, @@ -181,6 +189,9 @@ parse_import_options(char *str,unsigned int *options,int noisy) {"import-minimal",IMPORT_MINIMAL|IMPORT_CLEAN,NULL, N_("remove as much as possible from key after import")}, + {"import-drop-uids", IMPORT_DROP_UIDS, NULL, + N_("Do not import user id or attribute packets")}, + {"import-export", IMPORT_EXPORT, NULL, N_("run import filters and export key immediately")}, @@ -562,6 +573,7 @@ import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats, kbnode_t keyblock = NULL; /* Need to initialize because gcc can't grasp the return semantics of read_block. */ + kbnode_t secattic = NULL; /* Kludge for PGP desktop percularity */ int rc = 0; int v3keys; @@ -582,18 +594,63 @@ import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats, { stats->v3keys += v3keys; if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY) - rc = import_one (ctrl, keyblock, - stats, fpr, fpr_len, options, 0, 0, - screener, screener_arg, origin, url); + { + rc = import_one (ctrl, keyblock, + stats, fpr, fpr_len, options, 0, 0, + screener, screener_arg, origin, url, NULL); + if (secattic) + { + byte tmpfpr[MAX_FINGERPRINT_LEN]; + size_t tmpfprlen; + + if (!rc && !(opt.dry_run || (options & IMPORT_DRY_RUN))) + { + /* Kudge for PGP desktop - see below. */ + fingerprint_from_pk (keyblock->pkt->pkt.public_key, + tmpfpr, &tmpfprlen); + rc = import_matching_seckeys (ctrl, secattic, + tmpfpr, tmpfprlen, + stats, opt.batch); + } + release_kbnode (secattic); + secattic = NULL; + } + } else if (keyblock->pkt->pkttype == PKT_SECRET_KEY) - rc = import_secret_one (ctrl, keyblock, stats, - opt.batch, options, 0, - screener, screener_arg); + { + release_kbnode (secattic); + secattic = NULL; + rc = import_secret_one (ctrl, keyblock, stats, + opt.batch, options, 0, + screener, screener_arg, &secattic); + keyblock = NULL; /* Ownership was transferred. */ + if (secattic) + { + if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY) + rc = 0; /* Try import after the next pubkey. */ + + /* The attic is a workaround for the peculiar PGP + * Desktop method of exporting a secret key: The + * exported file is the concatenation of two armored + * keyblocks; first the private one and then the public + * one. The strange thing is that the secret one has no + * binding signatures at all and thus we have not + * imported it. The attic stores that secret keys and + * we try to import it once after the very next public + * keyblock. */ + } + } else if (keyblock->pkt->pkttype == PKT_SIGNATURE && IS_KEY_REV (keyblock->pkt->pkt.signature) ) - rc = import_revoke_cert (ctrl, keyblock, options, stats); + { + release_kbnode (secattic); + secattic = NULL; + rc = import_revoke_cert (ctrl, keyblock, options, stats); + } else { + release_kbnode (secattic); + secattic = NULL; log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype); } release_kbnode (keyblock); @@ -619,6 +676,7 @@ import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats, else if (rc && gpg_err_code (rc) != GPG_ERR_INV_KEYRING) log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (rc)); + release_kbnode (secattic); return rc; } @@ -655,8 +713,11 @@ import_old_secring (ctrl_t ctrl, const char *fname) while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys))) { if (keyblock->pkt->pkttype == PKT_SECRET_KEY) - err = import_secret_one (ctrl, keyblock, stats, 1, 0, 1, - NULL, NULL); + { + err = import_secret_one (ctrl, keyblock, stats, 1, 0, 1, + NULL, NULL, NULL); + keyblock = NULL; /* Ownership was transferred. */ + } release_kbnode (keyblock); if (err) break; @@ -924,6 +985,8 @@ read_block( IOBUF a, int with_meta, add_kbnode (root, new_kbnode (pkt)); pkt = xmalloc (sizeof *pkt); } + else + free_packet (pkt, &parsectx); init_packet(pkt); break; } @@ -1078,23 +1141,14 @@ print_import_ok (PKT_public_key *pk, unsigned int reason) static void print_import_check (PKT_public_key * pk, PKT_user_id * id) { - char * buf; - byte fpr[24]; + byte hexfpr[2*MAX_FINGERPRINT_LEN+1]; u32 keyid[2]; - size_t i, n; - size_t pos = 0; - buf = xmalloc (17+41+id->len+32); keyid_from_pk (pk, keyid); - sprintf (buf, "%08X%08X ", keyid[0], keyid[1]); - pos = 17; - fingerprint_from_pk (pk, fpr, &n); - for (i = 0; i < n; i++, pos += 2) - sprintf (buf+pos, "%02X", fpr[i]); - strcat (buf, " "); - strcat (buf, id->name); - write_status_text (STATUS_IMPORT_CHECK, buf); - xfree (buf); + hexfingerprint (pk, hexfpr, sizeof hexfpr); + write_status_printf (STATUS_IMPORT_CHECK, "%08X%08X %s %s", + keyid[0], keyid[1], hexfpr, id->name); + } @@ -1258,7 +1312,7 @@ impex_filter_getval (void *cookie, const char *propname) { if (!uid->mbox) { - uid->mbox = mailbox_from_userid (uid->name); + uid->mbox = mailbox_from_userid (uid->name, 0); } result = uid->mbox; } @@ -1669,7 +1723,10 @@ update_key_origin (kbnode_t keyblock, u32 curtime, int origin, const char *url) * the internal errorcount, so that invalid input can be detected by * programs which called gpg. If SILENT is no messages are printed - * even most error messages are suppressed. ORIGIN is the origin of - * the key (0 for unknown) and URL the corresponding URL. + * the key (0 for unknown) and URL the corresponding URL. FROM_SK + * indicates that the key has been made from a secret key. If R_SAVED + * is not NULL a boolean will be stored indicating whether the keyblock + * has valid parts. */ static gpg_error_t import_one (ctrl_t ctrl, @@ -1677,7 +1734,7 @@ import_one (ctrl_t ctrl, unsigned char **fpr, size_t *fpr_len, unsigned int options, int from_sk, int silent, import_screener_t screener, void *screener_arg, - int origin, const char *url) + int origin, const char *url, int *r_valid) { gpg_error_t err = 0; PKT_public_key *pk; @@ -1696,6 +1753,9 @@ import_one (ctrl_t ctrl, int any_filter = 0; KEYDB_HANDLE hd = NULL; + if (r_valid) + *r_valid = 0; + /* If show-only is active we don't won't any extra output. */ if ((options & (IMPORT_SHOW | IMPORT_DRY_RUN))) silent = 1; @@ -1713,9 +1773,11 @@ import_one (ctrl_t ctrl, keyid_from_pk( pk, keyid ); uidnode = find_next_kbnode( keyblock, PKT_USER_ID ); - if (opt.verbose && !opt.interactive && !silent) + if (opt.verbose && !opt.interactive && !silent && !from_sk) { - log_info( "pub %s/%s %s ", + /* Note that we do not print this info in FROM_SK mode + * because import_secret_one already printed that. */ + log_info ("pub %s/%s %s ", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk(pk), datestr_from_pk(pk) ); if (uidnode) @@ -1726,7 +1788,9 @@ import_one (ctrl_t ctrl, } - if (!uidnode ) + /* Unless import-drop-uids has been requested we don't allow import + * of a key without UIDs. */ + if (!uidnode && !(options & IMPORT_DROP_UIDS)) { if (!silent) log_error( _("key %s: no user ID\n"), keystr_from_pk(pk)); @@ -1746,14 +1810,18 @@ import_one (ctrl_t ctrl, print_import_check (pk, uidnode->pkt->pkt.user_id); merge_keys_and_selfsig (ctrl, keyblock); tty_printf ("\n"); - show_basic_key_info (ctrl, keyblock); + show_basic_key_info (ctrl, keyblock, from_sk); tty_printf ("\n"); if (!cpr_get_answer_is_yes ("import.okay", "Do you want to import this key? (y/N) ")) return 0; } - collapse_uids(&keyblock); + /* Remove or collapse the user ids. */ + if ((options & IMPORT_DROP_UIDS)) + remove_all_uids (&keyblock); + else + collapse_uids (&keyblock); /* Clean the key that we're about to import, to cut down on things that we have to clean later. This has no practical impact on the @@ -1800,7 +1868,10 @@ import_one (ctrl_t ctrl, } } - if (!delete_inv_parts (ctrl, keyblock, keyid, options ) ) + /* Delete invalid parts and without the drop option bail out if + * there are no user ids. */ + if (!delete_inv_parts (ctrl, keyblock, keyid, options) + && !(options & IMPORT_DROP_UIDS) ) { if (!silent) { @@ -1840,6 +1911,10 @@ import_one (ctrl_t ctrl, return 0; } + /* The keyblock is valid and ready for real import. */ + if (r_valid) + *r_valid = 1; + /* Show the key in the form it is merged or inserted. We skip this * if "import-export" is also active without --armor or the output * file has explicily been given. */ @@ -2163,12 +2238,15 @@ import_one (ctrl_t ctrl, /* Transfer all the secret keys in SEC_KEYBLOCK to the gpg-agent. The - function prints diagnostics and returns an error code. If BATCH is - true the secret keys are stored by gpg-agent in the transfer format - (i.e. no re-protection and aksing for passphrases). */ + * function prints diagnostics and returns an error code. If BATCH is + * true the secret keys are stored by gpg-agent in the transfer format + * (i.e. no re-protection and aksing for passphrases). If ONLY_MARKED + * is set, only those nodes with flag NODE_TRANSFER_SECKEY are + * processed. */ gpg_error_t transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, - kbnode_t sec_keyblock, int batch, int force) + kbnode_t sec_keyblock, int batch, int force, + int only_marked) { gpg_error_t err = 0; void *kek = NULL; @@ -2209,12 +2287,16 @@ transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, xfree (kek); kek = NULL; + /* Note: We need to use walk_kbnode so that we skip nodes which are + * marked as deleted. */ main_pk = NULL; while ((node = walk_kbnode (sec_keyblock, &ctx, 0))) { if (node->pkt->pkttype != PKT_SECRET_KEY && node->pkt->pkttype != PKT_SECRET_SUBKEY) continue; + if (only_marked && !(node->flag & NODE_TRANSFER_SECKEY)) + continue; pk = node->pkt->pkt.public_key; if (!main_pk) main_pk = pk; @@ -2454,14 +2536,21 @@ transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, /* Walk a secret keyblock and produce a public keyblock out of it. - Returns a new node or NULL on error. */ + * Returns a new node or NULL on error. Modifies the tag field of the + * nodes. */ static kbnode_t sec_to_pub_keyblock (kbnode_t sec_keyblock) { kbnode_t pub_keyblock = NULL; kbnode_t ctx = NULL; kbnode_t secnode, pubnode; + unsigned int tag = 0; + + /* Set a tag to all nodes. */ + for (secnode = sec_keyblock; secnode; secnode = secnode->next) + secnode->tag = ++tag; + /* Copy. */ while ((secnode = walk_kbnode (sec_keyblock, &ctx, 0))) { if (secnode->pkt->pkttype == PKT_SECRET_KEY @@ -2491,6 +2580,7 @@ sec_to_pub_keyblock (kbnode_t sec_keyblock) { pubnode = clone_kbnode (secnode); } + pubnode->tag = secnode->tag; if (!pub_keyblock) pub_keyblock = pubnode; @@ -2501,25 +2591,223 @@ sec_to_pub_keyblock (kbnode_t sec_keyblock) return pub_keyblock; } -/**************** - * Ditto for secret keys. Handling is simpler than for public keys. - * We allow secret key importing only when allow is true, this is so - * that a secret key can not be imported accidentally and thereby tampering - * with the trust calculation. + +/* Delete all notes in the keyblock at R_KEYBLOCK which are not in + * PUB_KEYBLOCK. Modifies the tags of both keyblock's nodes. */ +static gpg_error_t +resync_sec_with_pub_keyblock (kbnode_t *r_keyblock, kbnode_t pub_keyblock, + kbnode_t *r_removedsecs) +{ + kbnode_t sec_keyblock = *r_keyblock; + kbnode_t node, prevnode; + unsigned int *taglist; + unsigned int ntaglist, n; + kbnode_t attic = NULL; + kbnode_t *attic_head = &attic; + + /* Collect all tags in an array for faster searching. */ + for (ntaglist = 0, node = pub_keyblock; node; node = node->next) + ntaglist++; + taglist = xtrycalloc (ntaglist, sizeof *taglist); + if (!taglist) + return gpg_error_from_syserror (); + for (ntaglist = 0, node = pub_keyblock; node; node = node->next) + taglist[ntaglist++] = node->tag; + + /* Walks over the secret keyblock and delete all nodes which are not + * in the tag list. Those nodes have been deleted in the + * pub_keyblock. Sequential search is a bit lazy and could be + * optimized by sorting and bsearch; however secret keyrings are + * short and there are easier ways to DoS the import. */ + again: + for (prevnode=NULL, node=sec_keyblock; node; prevnode=node, node=node->next) + { + for (n=0; n < ntaglist; n++) + if (taglist[n] == node->tag) + break; + if (n == ntaglist) /* Not in public keyblock. */ + { + if (node->pkt->pkttype == PKT_SECRET_KEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + { + if (!prevnode) + sec_keyblock = node->next; + else + prevnode->next = node->next; + node->next = NULL; + *attic_head = node; + attic_head = &node->next; + goto again; /* That's lame; I know. */ + } + else + delete_kbnode (node); + } + } + + xfree (taglist); + + /* Commit the as deleted marked nodes and return the possibly + * modified keyblock and a list of removed secret key nodes. */ + commit_kbnode (&sec_keyblock); + *r_keyblock = sec_keyblock; + *r_removedsecs = attic; + return 0; +} + + +/* Helper for import_secret_one. */ +static gpg_error_t +do_transfer (ctrl_t ctrl, kbnode_t keyblock, PKT_public_key *pk, + struct import_stats_s *stats, int batch, int only_marked) + +{ + gpg_error_t err; + struct import_stats_s subkey_stats = {0}; + + err = transfer_secret_keys (ctrl, &subkey_stats, keyblock, + batch, 0, only_marked); + if (gpg_err_code (err) == GPG_ERR_NOT_PROCESSED) + { + /* TRANSLATORS: For a smartcard, each private key on host has a + * reference (stub) to a smartcard and actual private key data + * is stored on the card. A single smartcard can have up to + * three private key data. Importing private key stub is always + * skipped in 2.1, and it returns GPG_ERR_NOT_PROCESSED. + * Instead, user should be suggested to run 'gpg --card-status', + * then, references to a card will be automatically created + * again. */ + log_info (_("To migrate '%s', with each smartcard, " + "run: %s\n"), "secring.gpg", "gpg --card-status"); + err = 0; + } + + if (!err) + { + int status = 16; + + if (!opt.quiet) + log_info (_("key %s: secret key imported\n"), keystr_from_pk (pk)); + if (subkey_stats.secret_imported) + { + status |= 1; + stats->secret_imported += 1; + } + if (subkey_stats.secret_dups) + stats->secret_dups += 1; + + if (is_status_enabled ()) + print_import_ok (pk, status); + } + + return err; +} + + +/* If the secret keys (main or subkey) in SECKEYS have a corresponding + * public key in the public key described by (FPR,FPRLEN) import these + * parts. */ -static int +static gpg_error_t +import_matching_seckeys (ctrl_t ctrl, kbnode_t seckeys, + const byte *mainfpr, size_t mainfprlen, + struct import_stats_s *stats, int batch) +{ + gpg_error_t err; + kbnode_t pub_keyblock = NULL; + kbnode_t node; + struct { byte fpr[MAX_FINGERPRINT_LEN]; size_t fprlen; } *fprlist = NULL; + size_t n, nfprlist; + byte fpr[MAX_FINGERPRINT_LEN]; + size_t fprlen; + PKT_public_key *pk; + + /* Get the entire public key block from our keystore and put all its + * fingerprints into an array. */ + err = get_pubkey_byfprint (ctrl, NULL, &pub_keyblock, mainfpr, mainfprlen); + if (err) + goto leave; + log_assert (pub_keyblock && pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY); + pk = pub_keyblock->pkt->pkt.public_key; + + for (nfprlist = 0, node = pub_keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + nfprlist++; + log_assert (nfprlist); + fprlist = xtrycalloc (nfprlist, sizeof *fprlist); + if (!fprlist) + { + err = gpg_error_from_syserror (); + goto leave; + } + for (n = 0, node = pub_keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + fingerprint_from_pk (node->pkt->pkt.public_key, + fprlist[n].fpr, &fprlist[n].fprlen); + n++; + } + log_assert (n == nfprlist); + + /* for (n=0; n < nfprlist; n++) */ + /* log_printhex (fprlist[n].fpr, fprlist[n].fprlen, "pubkey %zu:", n); */ + + /* Mark all secret keys which have a matching public key part in + * PUB_KEYBLOCK. */ + for (node = seckeys; node; node = node->next) + { + if (node->pkt->pkttype != PKT_SECRET_KEY + && node->pkt->pkttype != PKT_SECRET_SUBKEY) + continue; /* Should not happen. */ + fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen); + node->flag &= ~NODE_TRANSFER_SECKEY; + for (n=0; n < nfprlist; n++) + if (fprlist[n].fprlen == fprlen && !memcmp (fprlist[n].fpr,fpr,fprlen)) + { + node->flag |= NODE_TRANSFER_SECKEY; + /* log_debug ("found matching seckey\n"); */ + break; + } + } + + /* Transfer all marked keys. */ + err = do_transfer (ctrl, seckeys, pk, stats, batch, 1); + + leave: + xfree (fprlist); + release_kbnode (pub_keyblock); + return err; +} + + +/* Import function for a single secret keyblock. Handling is simpler + * than for public keys. We allow secret key importing only when + * allow is true, this is so that a secret key can not be imported + * accidentally and thereby tampering with the trust calculation. + * + * Ownership of KEYBLOCK is transferred to this function! + * + * If R_SECATTIC is not null the last special sec_keyblock is stored + * there. + */ +static gpg_error_t import_secret_one (ctrl_t ctrl, kbnode_t keyblock, - struct import_stats_s *stats, int batch, unsigned int options, - int for_migration, - import_screener_t screener, void *screener_arg) + struct import_stats_s *stats, int batch, + unsigned int options, int for_migration, + import_screener_t screener, void *screener_arg, + kbnode_t *r_secattic) { PKT_public_key *pk; struct seckey_info *ski; kbnode_t node, uidnode; u32 keyid[2]; - int rc = 0; + gpg_error_t err = 0; int nr_prev; kbnode_t pub_keyblock; + kbnode_t attic = NULL; + byte fpr[MAX_FINGERPRINT_LEN]; + size_t fprlen; char pkstrbuf[PUBKEY_STRING_SIZE]; /* Get the key and print some info about it */ @@ -2529,6 +2817,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, pk = node->pkt->pkt.public_key; + fingerprint_from_pk (pk, fpr, &fprlen); keyid_from_pk (pk, keyid); uidnode = find_next_kbnode (keyblock, PKT_USER_ID); @@ -2536,12 +2825,13 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, { log_error (_("secret key %s: %s\n"), keystr_from_pk (pk), _("rejected by import screener")); + release_kbnode (keyblock); return 0; } if (opt.verbose && !for_migration) { - log_info ("sec %s/%s %s ", + log_info ("sec %s/%s %s ", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk (pk), datestr_from_pk (pk)); if (uidnode) @@ -2555,6 +2845,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, { if (!for_migration) log_error (_("importing secret keys not allowed\n")); + release_kbnode (keyblock); return 0; } @@ -2562,6 +2853,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, { if (!for_migration) log_error( _("key %s: no user ID\n"), keystr_from_pk (pk)); + release_kbnode (keyblock); return 0; } @@ -2570,6 +2862,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, { /* Actually an internal error. */ log_error ("key %s: secret key info missing\n", keystr_from_pk (pk)); + release_kbnode (keyblock); return 0; } @@ -2580,6 +2873,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, if (!for_migration) log_error (_("key %s: secret key with invalid cipher %d" " - skipped\n"), keystr_from_pk (pk), ski->algo); + release_kbnode (keyblock); return 0; } @@ -2590,6 +2884,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, to put a secret key into the keyring and the user might later be tricked into signing stuff with that key. */ log_error (_("importing secret keys not allowed\n")); + release_kbnode (keyblock); return 0; } #endif @@ -2601,20 +2896,62 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, /* Make a public key out of the key. */ pub_keyblock = sec_to_pub_keyblock (keyblock); if (!pub_keyblock) - log_error ("key %s: failed to create public key from secret key\n", - keystr_from_pk (pk)); + { + err = gpg_error_from_syserror (); + log_error ("key %s: failed to create public key from secret key\n", + keystr_from_pk (pk)); + } else { + int valid; + /* Note that this outputs an IMPORT_OK status message for the public key block, and below we will output another one for the secret keys. FIXME? */ import_one (ctrl, pub_keyblock, stats, NULL, NULL, options, 1, for_migration, - screener, screener_arg, 0, NULL); + screener, screener_arg, 0, NULL, &valid); + + /* The secret keyblock may not have nodes which are deleted in + * the public keyblock. Otherwise we would import just the + * secret key without having the public key. That would be + * surprising and clutters out private-keys-v1.d. */ + err = resync_sec_with_pub_keyblock (&keyblock, pub_keyblock, &attic); + if (err) + goto leave; + + if (!valid) + { + /* If the block was not valid the primary key is left in the + * original keyblock because we require that for the first + * node. Move it to ATTIC. */ + if (keyblock && keyblock->pkt->pkttype == PKT_SECRET_KEY) + { + node = keyblock; + keyblock = node->next; + node->next = NULL; + if (attic) + { + node->next = attic; + attic = node; + } + else + attic = node; + } + + /* Try to import the secret key iff we have a public key. */ + if (attic && !(opt.dry_run || (options & IMPORT_DRY_RUN))) + err = import_matching_seckeys (ctrl, attic, fpr, fprlen, + stats, batch); + else + err = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } - /* Fixme: We should check for an invalid keyblock and - cancel the secret key import in this case. */ - release_kbnode (pub_keyblock); + /* log_debug ("attic is:\n"); */ + /* dump_kbnode (attic); */ + + /* Proceed with the valid parts of PUBKEYBLOCK. */ /* At least we cancel the secret key import when the public key import was skipped due to MERGE_ONLY option and a new @@ -2622,63 +2959,40 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock, if (!(opt.dry_run || (options & IMPORT_DRY_RUN)) && stats->skipped_new_keys <= nr_prev) { - /* Read the keyblock again to get the effects of a merge. */ - /* Fixme: we should do this based on the fingerprint or - even better let import_one return the merged - keyblock. */ - node = get_pubkeyblock (ctrl, keyid); - if (!node) - log_error ("key %s: failed to re-lookup public key\n", - keystr_from_pk (pk)); + /* Read the keyblock again to get the effects of a merge for + * the public key. */ + err = get_pubkey_byfprint (ctrl, NULL, &node, fpr, fprlen); + if (err || !node) + log_error ("key %s: failed to re-lookup public key: %s\n", + keystr_from_pk (pk), gpg_strerror (err)); else { - gpg_error_t err; - - /* transfer_secret_keys collects subkey stats. */ - struct import_stats_s subkey_stats = {0}; + err = do_transfer (ctrl, keyblock, pk, stats, batch, 0); + if (!err) + check_prefs (ctrl, node); + release_kbnode (node); - err = transfer_secret_keys (ctrl, &subkey_stats, keyblock, - batch, 0); - if (gpg_err_code (err) == GPG_ERR_NOT_PROCESSED) + if (!err && attic) { - /* TRANSLATORS: For smartcard, each private key on - host has a reference (stub) to a smartcard and - actual private key data is stored on the card. A - single smartcard can have up to three private key - data. Importing private key stub is always - skipped in 2.1, and it returns - GPG_ERR_NOT_PROCESSED. Instead, user should be - suggested to run 'gpg --card-status', then, - references to a card will be automatically - created again. */ - log_info (_("To migrate '%s', with each smartcard, " - "run: %s\n"), "secring.gpg", "gpg --card-status"); - err = 0; + /* Try to import invalid subkeys. This can be the + * case if the primary secret key was imported due + * to --allow-non-selfsigned-uid. */ + err = import_matching_seckeys (ctrl, attic, fpr, fprlen, + stats, batch); } - if (!err) - { - int status = 16; - if (!opt.quiet) - log_info (_("key %s: secret key imported\n"), - keystr_from_pk (pk)); - if (subkey_stats.secret_imported) - { - status |= 1; - stats->secret_imported += 1; - } - if (subkey_stats.secret_dups) - stats->secret_dups += 1; - if (is_status_enabled ()) - print_import_ok (pk, status); - check_prefs (ctrl, node); - } - release_kbnode (node); } } } - return rc; + leave: + release_kbnode (keyblock); + release_kbnode (pub_keyblock); + if (r_secattic) + *r_secattic = attic; + else + release_kbnode (attic); + return err; } @@ -2942,9 +3256,7 @@ import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options, size_t an; fingerprint_from_pk (pk, afp, &an); - while (an < MAX_FINGERPRINT_LEN) - afp[an++] = 0; - rc = keydb_search_fpr (hd, afp); + rc = keydb_search_fpr (hd, afp, an); } if (rc) { @@ -3058,7 +3370,7 @@ chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, int *non_self) kbnode_t bsnode = NULL; /* Subkey binding signature node. */ u32 bsdate = 0; /* Timestamp of that node. */ kbnode_t rsnode = NULL; /* Subkey recocation signature node. */ - u32 rsdate = 0; /* Timestamp of tha node. */ + u32 rsdate = 0; /* Timestamp of that node. */ PKT_signature *sig; int rc; kbnode_t n; @@ -3415,14 +3727,51 @@ any_uid_left (kbnode_t keyblock) -/**************** +/* Delete all user ids from KEYBLOCK. + * Returns: True if the keyblock has changed. */ +static int +remove_all_uids (kbnode_t *keyblock) +{ + kbnode_t node; + int any = 0; + + for (node = *keyblock; node; node = node->next) + { + if (is_deleted_kbnode (node)) + continue; + + if (node->pkt->pkttype != PKT_USER_ID) + continue; + + /* We are at the first user id. Delete everything up to the + * first subkey. */ + for (; node; node = node->next) + { + if (is_deleted_kbnode (node)) + continue; + + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + break; + delete_kbnode (node); + any = 1; + } + break; /* All done. */ + } + + commit_kbnode (keyblock); + return any; +} + + +/* * It may happen that the imported keyblock has duplicated user IDs. * We check this here and collapse those user IDs together with their * sigs into one. * Returns: True if the keyblock has changed. */ int -collapse_uids( kbnode_t *keyblock ) +collapse_uids (kbnode_t *keyblock) { kbnode_t uid1; int any=0; @@ -3562,7 +3911,7 @@ revocation_present (ctrl_t ctrl, kbnode_t keyblock) u32 keyid[2]; keyid_from_fingerprint (ctrl, sig->revkey[idx].fpr, - MAX_FINGERPRINT_LEN, keyid); + sig->revkey[idx].fprlen, keyid); for(inode=keyblock->next;inode;inode=inode->next) { @@ -3582,7 +3931,7 @@ revocation_present (ctrl_t ctrl, kbnode_t keyblock) err = get_pubkey_byfprint_fast (NULL, sig->revkey[idx].fpr, - MAX_FINGERPRINT_LEN); + sig->revkey[idx].fprlen); if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY || gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY) { @@ -3598,13 +3947,13 @@ revocation_present (ctrl_t ctrl, kbnode_t keyblock) tempkeystr,keystr(keyid)); keyserver_import_fprint (ctrl, sig->revkey[idx].fpr, - MAX_FINGERPRINT_LEN, + sig->revkey[idx].fprlen, opt.keyserver, 0); /* Do we have it now? */ err = get_pubkey_byfprint_fast (NULL, sig->revkey[idx].fpr, - MAX_FINGERPRINT_LEN); + sig->revkey[idx].fprlen); } if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY diff --git a/g10/kbnode.c b/g10/kbnode.c index 64def0509..93035e8f6 100644 --- a/g10/kbnode.c +++ b/g10/kbnode.c @@ -68,8 +68,8 @@ alloc_node (void) n->next = NULL; n->pkt = NULL; n->flag = 0; + n->tag = 0; n->private_flag=0; - n->recno = 0; return n; } diff --git a/g10/key-clean.c b/g10/key-clean.c index f66a0dbb4..d701a6665 100644 --- a/g10/key-clean.c +++ b/g10/key-clean.c @@ -500,7 +500,7 @@ clean_one_subkey_dupsigs (ctrl_t ctrl, kbnode_t subkeynode) log_debug ("\tchecking subkey %08lX for dupsigs\n", (ulong) keyid_from_pk (pk, NULL)); - /* First check that the choosen flag has been set. Note that we + /* First check that the chosen flag has been set. Note that we * only look at plain signatures so to keep all revocation * signatures which may carry important information. */ for (node = subkeynode->next; @@ -519,7 +519,7 @@ clean_one_subkey_dupsigs (ctrl_t ctrl, kbnode_t subkeynode) } if (!any_choosen) - return 0; /* Ooops no choosen flag set - we can't decide. */ + return 0; /* Ooops no chosen flag set - we can't decide. */ for (node = subkeynode->next; node && !(node->pkt->pkttype == PKT_PUBLIC_SUBKEY diff --git a/g10/key-clean.h b/g10/key-clean.h index a0fb76950..c4f164928 100644 --- a/g10/key-clean.h +++ b/g10/key-clean.h @@ -23,7 +23,7 @@ #include "gpg.h" -/* No explict cleaning. */ +/* No explicit cleaning. */ #define KEY_CLEAN_NONE 0 /* Remove only invalid subkeys (ie. missing key-bindings) */ #define KEY_CLEAN_INVALID 1 diff --git a/g10/keydb.c b/g10/keydb.c index 03fadfd54..8c067e1df 100644 --- a/g10/keydb.c +++ b/g10/keydb.c @@ -81,6 +81,7 @@ enum keyblock_cache_states { struct keyblock_cache { enum keyblock_cache_states state; byte fpr[MAX_FINGERPRINT_LEN]; + byte fprlen; iobuf_t iobuf; /* Image of the keyblock. */ int pk_no; int uid_no; @@ -558,17 +559,9 @@ keydb_search_desc_dump (struct keydb_search_desc *desc) case KEYDB_SEARCH_MODE_LONG_KID: return xasprintf ("LONG_KID: '%s'", format_keyid (desc->u.kid, KF_LONG, b, sizeof (b))); - case KEYDB_SEARCH_MODE_FPR16: - bin2hex (desc->u.fpr, 16, fpr); - return xasprintf ("FPR16: '%s'", - format_hexfingerprint (fpr, b, sizeof (b))); - case KEYDB_SEARCH_MODE_FPR20: - bin2hex (desc->u.fpr, 20, fpr); - return xasprintf ("FPR20: '%s'", - format_hexfingerprint (fpr, b, sizeof (b))); case KEYDB_SEARCH_MODE_FPR: - bin2hex (desc->u.fpr, 20, fpr); - return xasprintf ("FPR: '%s'", + bin2hex (desc->u.fpr, desc->fprlen, fpr); + return xasprintf ("FPR%02d: '%s'", desc->fprlen, format_hexfingerprint (fpr, b, sizeof (b))); case KEYDB_SEARCH_MODE_ISSUER: return xasprintf ("ISSUER: '%s'", desc->u.name); @@ -1529,8 +1522,11 @@ keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb) memset (&desc, 0, sizeof (desc)); fingerprint_from_pk (pk, desc.u.fpr, &len); - if (len == 20) - desc.mode = KEYDB_SEARCH_MODE_FPR20; + if (len == 20 || len == 32) + { + desc.mode = KEYDB_SEARCH_MODE_FPR; + desc.fprlen = len; + } else log_bug ("%s: Unsupported key length: %zu\n", __func__, len); @@ -1862,6 +1858,7 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, int was_reset = hd->is_reset; /* If an entry is already in the cache, then don't add it again. */ int already_in_cache = 0; + int fprlen; if (descindex) *descindex = 0; /* Make sure it is always set on return. */ @@ -1902,12 +1899,17 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, /* NB: If one of the exact search modes below is used in a loop to walk over all keys (with the same fingerprint) the caching must have been disabled for the handle. */ + if (desc[0].mode == KEYDB_SEARCH_MODE_FPR) + fprlen = desc[0].fprlen; + else + fprlen = 0; + if (!hd->no_caching && ndesc == 1 - && (desc[0].mode == KEYDB_SEARCH_MODE_FPR20 - || desc[0].mode == KEYDB_SEARCH_MODE_FPR) - && hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED - && !memcmp (hd->keyblock_cache.fpr, desc[0].u.fpr, 20) + && fprlen + && hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED + && hd->keyblock_cache.fprlen == fprlen + && !memcmp (hd->keyblock_cache.fpr, desc[0].u.fpr, fprlen) /* Make sure the current file position occurs before the cached result to avoid an infinite loop. */ && (hd->current < hd->keyblock_cache.resource @@ -1922,8 +1924,7 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, hd->current = hd->keyblock_cache.resource; /* HD->KEYBLOCK_CACHE.OFFSET is the last byte in the record. Seek just beyond that. */ - keybox_seek (hd->active[hd->current].u.kb, - hd->keyblock_cache.offset + 1); + keybox_seek (hd->active[hd->current].u.kb, hd->keyblock_cache.offset + 1); keydb_stats.found_cached++; return 0; } @@ -1986,8 +1987,8 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, keyblock_cache_clear (hd); if (!hd->no_caching && !rc - && ndesc == 1 && (desc[0].mode == KEYDB_SEARCH_MODE_FPR20 - || desc[0].mode == KEYDB_SEARCH_MODE_FPR) + && ndesc == 1 + && fprlen && hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX) { hd->keyblock_cache.state = KEYBLOCK_CACHE_PREPARED; @@ -1997,11 +1998,14 @@ keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, within the record. */ hd->keyblock_cache.offset = keybox_offset (hd->active[hd->current].u.kb) - 1; - memcpy (hd->keyblock_cache.fpr, desc[0].u.fpr, 20); + memcpy (hd->keyblock_cache.fpr, desc[0].u.fpr, fprlen); + hd->keyblock_cache.fprlen = fprlen; } if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND - && ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID && was_reset + && ndesc == 1 + && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID + && was_reset && !already_in_cache) kid_not_found_insert (desc[0].u.kid); @@ -2078,12 +2082,13 @@ keydb_search_kid (KEYDB_HANDLE hd, u32 *kid) * off. If you want to search the whole database, then you need to * first call keydb_search_reset(). */ gpg_error_t -keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr) +keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr, size_t fprlen) { KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FPR; - memcpy (desc.u.fpr, fpr, MAX_FINGERPRINT_LEN); + memcpy (desc.u.fpr, fpr, fprlen); + desc.fprlen = fprlen; return keydb_search (hd, &desc, 1, NULL); } diff --git a/g10/keydb.h b/g10/keydb.h index 1def2bb81..7cdfe9bbf 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -52,12 +52,13 @@ typedef struct getkey_ctx_s *getkey_ctx_t; * This structure is also used to bind arbitrary packets together. */ -struct kbnode_struct { - KBNODE next; - PACKET *pkt; - int flag; - int private_flag; - ulong recno; /* used while updating the trustdb */ +struct kbnode_struct +{ + kbnode_t next; + PACKET *pkt; + int flag; /* Local use during keyblock processing (not cloned).*/ + unsigned int tag; /* Ditto. */ + int private_flag; }; #define is_deleted_kbnode(a) ((a)->private_flag & 1) @@ -244,9 +245,9 @@ gpg_error_t keydb_search_next (KEYDB_HANDLE hd); key id. */ gpg_error_t keydb_search_kid (KEYDB_HANDLE hd, u32 *kid); -/* This is a convenience function for searching for keys with a long - (20 byte) fingerprint. */ -gpg_error_t keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr); +/* This is a convenience function for searching for keys by + * fingerprint. */ +gpg_error_t keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr, size_t fprlen); /*-- pkclist.c --*/ @@ -276,7 +277,6 @@ gpg_error_t build_sk_list (ctrl_t ctrl, strlist_t locusr, SK_LIST *ret_sk_list, unsigned use); /*-- passphrase.h --*/ -unsigned char encode_s2k_iterations (int iterations); int have_static_passphrase(void); const char *get_static_passphrase (void); void set_passphrase_from_string(const char *pass); diff --git a/g10/keyedit.c b/g10/keyedit.c index 9716ed9d6..c28a565b1 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -1894,7 +1894,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, node = new_kbnode (pkt); /* Transfer it to gpg-agent which handles secret keys. */ - err = transfer_secret_keys (ctrl, NULL, node, 1, 1); + err = transfer_secret_keys (ctrl, NULL, node, 1, 1, 0); /* Treat the pkt as a public key. */ pkt->pkttype = PKT_PUBLIC_KEY; @@ -2564,9 +2564,7 @@ find_by_primary_fpr (ctrl_t ctrl, const char *fpr, *r_kdbhd = NULL; if (classify_user_id (fpr, &desc, 1) - || !(desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR16 - || desc.mode == KEYDB_SEARCH_MODE_FPR20)) + || desc.mode != KEYDB_SEARCH_MODE_FPR) { log_error (_("\"%s\" is not a fingerprint\n"), fpr); err = gpg_error (GPG_ERR_INV_NAME); @@ -2581,19 +2579,9 @@ find_by_primary_fpr (ctrl_t ctrl, const char *fpr, /* Check that the primary fingerprint has been given. */ fingerprint_from_pk (keyblock->pkt->pkt.public_key, fprbin, &fprlen); - if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR16 - && !memcmp (fprbin, desc.u.fpr, 16)) - ; - else if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR - && !memcmp (fprbin, desc.u.fpr, 16) - && !desc.u.fpr[16] - && !desc.u.fpr[17] - && !desc.u.fpr[18] - && !desc.u.fpr[19]) - ; - else if (fprlen == 20 && (desc.mode == KEYDB_SEARCH_MODE_FPR20 - || desc.mode == KEYDB_SEARCH_MODE_FPR) - && !memcmp (fprbin, desc.u.fpr, 20)) + if (desc.mode == KEYDB_SEARCH_MODE_FPR + && fprlen == desc.fprlen + && !memcmp (fprbin, desc.u.fpr, fprlen)) ; else { @@ -2917,8 +2905,7 @@ keyedit_quick_set_expire (ctrl_t ctrl, const char *fpr, const char *expirestr, /* Parse the fingerprint. */ if (classify_user_id (subkeyfprs[idx], &desc, 1) - || !(desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR20)) + || desc.mode != KEYDB_SEARCH_MODE_FPR) { log_error (_("\"%s\" is not a proper fingerprint\n"), subkeyfprs[idx] ); @@ -3524,7 +3511,7 @@ show_key_with_all_names (ctrl_t ctrl, estream_t fp, algo = gcry_pk_algo_name (pk->revkey[i].algid); keyid_from_fingerprint (ctrl, pk->revkey[i].fpr, - MAX_FINGERPRINT_LEN, r_keyid); + pk->revkey[i].fprlen, r_keyid); user = get_user_id_string_native (ctrl, r_keyid); tty_fprintf (fp, @@ -3690,13 +3677,14 @@ show_key_with_all_names (ctrl_t ctrl, estream_t fp, /* Display basic key information. This function is suitable to show - information on the key without any dependencies on the trustdb or - any other internal GnuPG stuff. KEYBLOCK may either be a public or - a secret key. This function may be called with KEYBLOCK containing - secret keys and thus the printing of "pub" vs. "sec" does only - depend on the packet type and not by checking with gpg-agent. */ + * information on the key without any dependencies on the trustdb or + * any other internal GnuPG stuff. KEYBLOCK may either be a public or + * a secret key. This function may be called with KEYBLOCK containing + * secret keys and thus the printing of "pub" vs. "sec" does only + * depend on the packet type and not by checking with gpg-agent. If + * PRINT_SEC ist set "sec" is printed instead of "pub". */ void -show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock) +show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock, int print_sec) { KBNODE node; int i; @@ -3709,13 +3697,17 @@ show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock) || node->pkt->pkttype == PKT_SECRET_KEY) { PKT_public_key *pk = node->pkt->pkt.public_key; + const char *tag; + + if (node->pkt->pkttype == PKT_SECRET_KEY || print_sec) + tag = "sec"; + else + tag = "pub"; /* Note, we use the same format string as in other show functions to make the translation job easier. */ tty_printf ("%s %s/%s ", - node->pkt->pkttype == PKT_PUBLIC_KEY ? "pub" : - node->pkt->pkttype == PKT_PUBLIC_SUBKEY ? "sub" : - node->pkt->pkttype == PKT_SECRET_KEY ? "sec" :"ssb", + tag, pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk (pk)); tty_printf (_("created: %s"), datestr_from_pk (pk)); @@ -4309,13 +4301,14 @@ menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive) xfree (answer); fingerprint_from_pk (revoker_pk, revkey.fpr, &fprlen); - if (fprlen != 20) + if (fprlen != 20 && fprlen != 32) { log_error (_("cannot appoint a PGP 2.x style key as a " "designated revoker\n")); continue; } + revkey.fprlen = fprlen; revkey.class = 0x80; if (sensitive) revkey.class |= 0x40; diff --git a/g10/keyedit.h b/g10/keyedit.h index d1f453a6f..af5e99664 100644 --- a/g10/keyedit.h +++ b/g10/keyedit.h @@ -50,7 +50,7 @@ void keyedit_quick_set_expire (ctrl_t ctrl, char **subkeyfprs); void keyedit_quick_set_primary (ctrl_t ctrl, const char *username, const char *primaryuid); -void show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock); +void show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock, int print_sec); int keyedit_print_one_sig (ctrl_t ctrl, estream_t fp, int rc, kbnode_t keyblock, kbnode_t node, int *inv_sigs, int *no_key, diff --git a/g10/keygen.c b/g10/keygen.c index 817704040..64fefd231 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -1,6 +1,6 @@ /* keygen.c - Generate a key pair * Copyright (C) 1998-2007, 2009-2011 Free Software Foundation, Inc. - * Copyright (C) 2014, 2015, 2016 Werner Koch + * Copyright (C) 2014, 2015, 2016, 2017, 2018 Werner Koch * * This file is part of GnuPG. * @@ -59,6 +59,7 @@ const char *default_expiration_interval = "2y"; /* Flag bits used during key generation. */ #define KEYGEN_FLAG_NO_PROTECTION 1 #define KEYGEN_FLAG_TRANSIENT_KEY 2 +#define KEYGEN_FLAG_CREATE_V5_KEY 4 /* Maximum number of supported algorithm preferences. */ #define MAX_PREFS 30 @@ -90,7 +91,9 @@ enum para_name { pHANDLE, pKEYSERVER, pKEYGRIP, - pSUBKEYGRIP + pSUBKEYGRIP, + pVERSION, /* Desired version of the key packet. */ + pSUBVERSION, /* Ditto for the subpacket. */ }; struct para_data_s { @@ -148,13 +151,13 @@ static gpg_error_t parse_algo_usage_expire (ctrl_t ctrl, int for_subkey, const char *expirestr, int *r_algo, unsigned int *r_usage, u32 *r_expire, unsigned int *r_nbits, - const char **r_curve); + const char **r_curve, int *r_version); static void do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, struct output_control_s *outctrl, int card ); static int write_keyblock (iobuf_t out, kbnode_t node); static gpg_error_t gen_card_key (int keyno, int algo, int is_primary, kbnode_t pub_root, u32 *timestamp, - u32 expireval); + u32 expireval, int keygen_flags); static unsigned int get_keysize_range (int algo, unsigned int *min, unsigned int *max); @@ -450,7 +453,7 @@ keygen_set_std_prefs (const char *string,int personal) } /* In case we have no compress algo at all, declare that - we prefer no compresssion. */ + we prefer no compression. */ if (!any_compress) strcat(dummy_string,"Z0 "); @@ -761,6 +764,48 @@ add_feature_aead (PKT_signature *sig, int enabled) static void +add_feature_v5 (PKT_signature *sig, int enabled) +{ + const byte *s; + size_t n; + int i; + char *buf; + + s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n ); + if (s && n && ((enabled && (s[0] & 0x04)) || (!enabled && !(s[0] & 0x04)))) + return; /* Already set or cleared */ + + if (!s || !n) + { /* Create a new one */ + n = 1; + buf = xmalloc_clear (n); + } + else + { + buf = xmalloc (n); + memcpy (buf, s, n); + } + + if (enabled) + buf[0] |= 0x04; /* v5 key supported */ + else + buf[0] &= ~0x04; + + /* Are there any bits set? */ + for (i=0; i < n; i++) + if (buf[i]) + break; + + if (i == n) + delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES); + else + build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n); + + xfree (buf); +} + + +static void add_keyserver_modify (PKT_signature *sig,int enabled) { const byte *s; @@ -848,6 +893,7 @@ keygen_upd_std_prefs (PKT_signature *sig, void *opaque) /* Make sure that the MDC feature flag is set if needed. */ add_feature_mdc (sig,mdc_available); add_feature_aead (sig, aead_available); + add_feature_v5 (sig, opt.flags.rfc4880bis); add_keyserver_modify (sig,ks_modify); keygen_add_keyserver_url(sig,NULL); @@ -943,11 +989,13 @@ keygen_add_revkey (PKT_signature *sig, void *opaque) struct revocation_key *revkey = opaque; byte buf[2+MAX_FINGERPRINT_LEN]; + log_assert (revkey->fprlen <= MAX_FINGERPRINT_LEN); buf[0] = revkey->class; buf[1] = revkey->algid; - memcpy (&buf[2], revkey->fpr, MAX_FINGERPRINT_LEN); + memcpy (buf + 2, revkey->fpr, revkey->fprlen); + memset (buf + 2 + revkey->fprlen, 0, sizeof (revkey->fpr) - revkey->fprlen); - build_sig_subpkt (sig, SIGSUBPKT_REV_KEY, buf, 2+MAX_FINGERPRINT_LEN); + build_sig_subpkt (sig, SIGSUBPKT_REV_KEY, buf, 2+revkey->fprlen); /* All sigs with revocation keys set are nonrevocable. */ sig->flags.revocable = 0; @@ -1368,7 +1416,7 @@ key_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp, static int do_create_from_keygrip (ctrl_t ctrl, int algo, const char *hexkeygrip, kbnode_t pub_root, u32 timestamp, u32 expireval, - int is_subkey) + int is_subkey, int keygen_flags) { int err; PACKET *pkt; @@ -1415,7 +1463,7 @@ do_create_from_keygrip (ctrl_t ctrl, int algo, const char *hexkeygrip, } pk->timestamp = timestamp; - pk->version = 4; + pk->version = (keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4; if (expireval) pk->expiredate = pk->timestamp + expireval; pk->pubkey_algo = algo; @@ -1482,7 +1530,7 @@ common_gen (const char *keyparms, int algo, const char *algoelem, } pk->timestamp = timestamp; - pk->version = 4; + pk->version = (keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4; if (expireval) pk->expiredate = pk->timestamp + expireval; pk->pubkey_algo = algo; @@ -2926,7 +2974,7 @@ ask_user_id (int mode, int full, KBNODE keyblock) /* Basic key generation. Here we divert to the actual generation routines based on the requested algorithm. */ static int -do_create (int algo, unsigned int nbits, const char *curve, KBNODE pub_root, +do_create (int algo, unsigned int nbits, const char *curve, kbnode_t pub_root, u32 timestamp, u32 expiredate, int is_subkey, int keygen_flags, const char *passphrase, char **cache_nonce_addr, char **passwd_nonce_addr) @@ -3005,12 +3053,14 @@ generate_user_id (KBNODE keyblock, const char *uidstr) * for any parameter. FOR_SUBKEY shall be true if this is used as a * subkey. If CLEAR_CERT is set a default CERT usage will be cleared; * this is useful if for example the default algorithm is used for a - * subkey. */ + * subkey. If R_KEYVERSION is not NULL it will receive the version of + * the key; this is currently 4 but can be changed with the flag "v5" + * to create a v5 key. */ static gpg_error_t parse_key_parameter_part (char *string, int for_subkey, int clear_cert, int *r_algo, unsigned int *r_size, unsigned int *r_keyuse, - char const **r_curve) + char const **r_curve, int *r_keyversion) { char *flags; int algo; @@ -3019,6 +3069,7 @@ parse_key_parameter_part (char *string, int for_subkey, int clear_cert, int ecdh_or_ecdsa = 0; unsigned int size; int keyuse; + int keyversion = 4; int i; const char *s; @@ -3117,6 +3168,13 @@ parse_key_parameter_part (char *string, int for_subkey, int clear_cert, return gpg_error (GPG_ERR_INV_FLAG); } } + else if (!ascii_strcasecmp (s, "v5")) + { + if (opt.flags.rfc4880bis) + keyversion = 5; + } + else if (!ascii_strcasecmp (s, "v4")) + keyversion = 4; else { xfree (tokens); @@ -3192,10 +3250,13 @@ parse_key_parameter_part (char *string, int for_subkey, int clear_cert, *r_keyuse = keyuse; if (r_curve) *r_curve = curve; + if (r_keyversion) + *r_keyversion = keyversion; return 0; } + /* Parse and return the standard key generation parameter. * The string is expected to be in this format: * @@ -3226,6 +3287,7 @@ parse_key_parameter_part (char *string, int for_subkey, int clear_cert, * ecdsa := Use algorithm ECDSA. * eddsa := Use algorithm EdDSA. * ecdh := Use algorithm ECDH. + * v5 := Create version 5 key (requires option --rfc4880bis) * * There are several defaults and fallbacks depending on the * algorithm. PART can be used to select which part of STRING is @@ -3244,9 +3306,11 @@ parse_key_parameter_string (const char *string, int part, int *r_algo, unsigned int *r_size, unsigned int *r_keyuse, char const **r_curve, + int *r_version, int *r_subalgo, unsigned int *r_subsize, - unsigned *r_subkeyuse, - char const **r_subcurve) + unsigned int *r_subkeyuse, + char const **r_subcurve, + int *r_subversion) { gpg_error_t err = 0; char *primary, *secondary; @@ -3259,6 +3323,8 @@ parse_key_parameter_string (const char *string, int part, *r_keyuse = 0; if (r_curve) *r_curve = NULL; + if (r_version) + *r_version = 4; if (r_subalgo) *r_subalgo = 0; if (r_subsize) @@ -3267,6 +3333,8 @@ parse_key_parameter_string (const char *string, int part, *r_subkeyuse = 0; if (r_subcurve) *r_subcurve = NULL; + if (r_subversion) + *r_subversion = 4; if (!string || !*string || !ascii_strcasecmp (string, "default") || !strcmp (string, "-")) @@ -3281,11 +3349,11 @@ parse_key_parameter_string (const char *string, int part, *secondary++ = 0; if (part == -1 || part == 0) { - err = parse_key_parameter_part (primary, 0, 0, r_algo, r_size, - r_keyuse, r_curve); + err = parse_key_parameter_part (primary, 0, 0, r_algo, r_size, + r_keyuse, r_curve, r_version); if (!err && part == -1) err = parse_key_parameter_part (secondary, 1, 0, r_subalgo, r_subsize, - r_subkeyuse, r_subcurve); + r_subkeyuse, r_subcurve, r_subversion); } else if (part == 1) { @@ -3298,14 +3366,17 @@ parse_key_parameter_string (const char *string, int part, if (secondary) { err = parse_key_parameter_part (secondary, 1, 0, - r_algo, r_size, r_keyuse, r_curve); + r_algo, r_size, r_keyuse, r_curve, + r_version); if (!err && suggested_use && r_keyuse && !(suggested_use & *r_keyuse)) err = parse_key_parameter_part (primary, 1, 1 /*(clear cert)*/, - r_algo, r_size, r_keyuse, r_curve); + r_algo, r_size, r_keyuse, r_curve, + r_version); } else err = parse_key_parameter_part (primary, 1, 0, - r_algo, r_size, r_keyuse, r_curve); + r_algo, r_size, r_keyuse, r_curve, + r_version); } xfree (primary); @@ -3393,9 +3464,8 @@ get_parameter_algo( struct para_data_s *para, enum para_name key, * compatibility with the batch key generation. It would be * better to make full use of parse_key_parameter_string. */ parse_key_parameter_string (NULL, 0, 0, - &i, NULL, NULL, NULL, - NULL, NULL, NULL, NULL); - + &i, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL); if (r_default) *r_default = 1; } @@ -3526,6 +3596,8 @@ parse_revocation_key (const char *fname, revkey.fpr[i]=c; } + if (i != 20 && i != 32) + goto fail; /* skip to the tag */ while(*pn && *pn!='s' && *pn!='S') @@ -3538,7 +3610,7 @@ parse_revocation_key (const char *fname, return 0; - fail: + fail: log_error("%s:%d: invalid revocation key\n", fname, r->lnr ); return -1; /* error */ } @@ -3806,6 +3878,8 @@ read_parameter_file (ctrl_t ctrl, const char *fname ) { "Keygrip", pKEYGRIP }, { "Key-Grip", pKEYGRIP }, { "Subkey-grip", pSUBKEYGRIP }, + { "Key-Version", pVERSION }, + { "Subkey-Version", pSUBVERSION }, { NULL, 0 } }; IOBUF fp; @@ -3950,12 +4024,19 @@ read_parameter_file (ctrl_t ctrl, const char *fname ) break; } } - r = xmalloc_clear( sizeof *r + strlen( value ) ); - r->lnr = lnr; - r->key = keywords[i].key; - strcpy( r->u.value, value ); - r->next = para; - para = r; + + if (!opt.flags.rfc4880bis && (keywords[i].key == pVERSION + || keywords[i].key == pSUBVERSION)) + ; /* Ignore version unless --rfc4880bis is active. */ + else + { + r = xmalloc_clear( sizeof *r + strlen( value ) ); + r->lnr = lnr; + r->key = keywords[i].key; + strcpy( r->u.value, value ); + r->next = para; + para = r; + } } if( err ) log_error("%s:%d: %s\n", fname, lnr, err ); @@ -3990,7 +4071,8 @@ read_parameter_file (ctrl_t ctrl, const char *fname ) /* Helper for quick_generate_keypair. */ static struct para_data_s * quickgen_set_para (struct para_data_s *para, int for_subkey, - int algo, int nbits, const char *curve, unsigned int use) + int algo, int nbits, const char *curve, unsigned int use, + int version) { struct para_data_s *r; @@ -4029,6 +4111,15 @@ quickgen_set_para (struct para_data_s *para, int for_subkey, para = r; } + if (opt.flags.rfc4880bis) + { + r = xmalloc_clear (sizeof *r + 20); + r->key = for_subkey? pSUBVERSION : pVERSION; + snprintf (r->u.value, 20, "%d", version); + r->next = para; + para = r; + } + return para; } @@ -4121,25 +4212,26 @@ quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr, || !strcmp (usagestr, "-"))) { /* Use default key parameters. */ - int algo, subalgo; + int algo, subalgo, version, subversion; unsigned int size, subsize; unsigned int keyuse, subkeyuse; const char *curve, *subcurve; err = parse_key_parameter_string (algostr, -1, 0, - &algo, &size, &keyuse, &curve, + &algo, &size, &keyuse, &curve, &version, &subalgo, &subsize, &subkeyuse, - &subcurve); + &subcurve, &subversion); if (err) { log_error (_("Key generation failed: %s\n"), gpg_strerror (err)); goto leave; } - para = quickgen_set_para (para, 0, algo, size, curve, keyuse); + para = quickgen_set_para (para, 0, algo, size, curve, keyuse, version); if (subalgo) para = quickgen_set_para (para, 1, - subalgo, subsize, subcurve, subkeyuse); + subalgo, subsize, subcurve, subkeyuse, + subversion); if (*expirestr) { @@ -4162,21 +4254,22 @@ quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr, else { /* Extended unattended mode. Creates only the primary key. */ - int algo; + int algo, version; unsigned int use; u32 expire; unsigned int nbits; const char *curve; err = parse_algo_usage_expire (ctrl, 0, algostr, usagestr, expirestr, - &algo, &use, &expire, &nbits, &curve); + &algo, &use, &expire, &nbits, &curve, + &version); if (err) { log_error (_("Key generation failed: %s\n"), gpg_strerror (err) ); goto leave; } - para = quickgen_set_para (para, 0, algo, nbits, curve, use); + para = quickgen_set_para (para, 0, algo, nbits, curve, use, version); r = xmalloc_clear (sizeof *r + 20); r->key = pKEYEXPIRE; r->u.expire = expire; @@ -4490,7 +4583,7 @@ generate_keypair (ctrl_t ctrl, int full, const char *fname, } else /* Default key generation. */ { - int subalgo; + int subalgo, version, subversion; unsigned int size, subsize; unsigned int keyuse, subkeyuse; const char *curve, *subcurve; @@ -4505,18 +4598,19 @@ generate_keypair (ctrl_t ctrl, int full, const char *fname, , "--full-generate-key" ); err = parse_key_parameter_string (NULL, -1, 0, - &algo, &size, &keyuse, &curve, + &algo, &size, &keyuse, &curve, &version, &subalgo, &subsize, - &subkeyuse, &subcurve); + &subkeyuse, &subcurve, &subversion); if (err) { log_error (_("Key generation failed: %s\n"), gpg_strerror (err)); return; } - para = quickgen_set_para (para, 0, algo, size, curve, keyuse); + para = quickgen_set_para (para, 0, algo, size, curve, keyuse, version); if (subalgo) para = quickgen_set_para (para, 1, - subalgo, subsize, subcurve, subkeyuse); + subalgo, subsize, subcurve, subkeyuse, + subversion); } @@ -4736,6 +4830,7 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, int algo; u32 expire; const char *key_from_hexgrip = NULL; + unsigned int keygen_flags; if (outctrl->dryrun) { @@ -4804,9 +4899,14 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, algo = get_parameter_algo( para, pKEYTYPE, NULL ); expire = get_parameter_u32( para, pKEYEXPIRE ); key_from_hexgrip = get_parameter_value (para, pKEYGRIP); + + keygen_flags = outctrl->keygen_flags; + if (get_parameter_uint (para, pVERSION) == 5) + keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY; + if (key_from_hexgrip) err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip, - pub_root, timestamp, expire, 0); + pub_root, timestamp, expire, 0, keygen_flags); else if (!card) err = do_create (algo, get_parameter_uint( para, pKEYLENGTH ), @@ -4814,13 +4914,13 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, pub_root, timestamp, expire, 0, - outctrl->keygen_flags, + keygen_flags, get_parameter_passphrase (para), &cache_nonce, NULL); else err = gen_card_key (1, algo, 1, pub_root, ×tamp, - expire); + expire, keygen_flags); /* Get the pointer to the generated public key packet. */ if (!err) @@ -4859,7 +4959,7 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, if (!err && card && get_parameter (para, pAUTHKEYTYPE)) { err = gen_card_key (3, get_parameter_algo( para, pAUTHKEYTYPE, NULL ), - 0, pub_root, ×tamp, expire); + 0, pub_root, ×tamp, expire, keygen_flags); if (!err) err = write_keybinding (ctrl, pub_root, pri_psk, NULL, PUBKEY_USAGE_AUTH, timestamp, cache_nonce); @@ -4871,11 +4971,16 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, s = NULL; key_from_hexgrip = get_parameter_value (para, pSUBKEYGRIP); + + keygen_flags = outctrl->keygen_flags; + if (get_parameter_uint (para, pSUBVERSION) == 5) + keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY; + if (key_from_hexgrip) err = do_create_from_keygrip (ctrl, subkey_algo, key_from_hexgrip, pub_root, timestamp, get_parameter_u32 (para, pSUBKEYEXPIRE), - 1); + 1, keygen_flags); else if (!card || (s = get_parameter_value (para, pCARDBACKUPKEY))) { err = do_create (subkey_algo, @@ -4884,7 +4989,7 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, pub_root, timestamp, get_parameter_u32 (para, pSUBKEYEXPIRE), 1, - s ? KEYGEN_FLAG_NO_PROTECTION : outctrl->keygen_flags, + s? KEYGEN_FLAG_NO_PROTECTION : keygen_flags, get_parameter_passphrase (para), &cache_nonce, NULL); /* Get the pointer to the generated public subkey packet. */ @@ -4904,7 +5009,8 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, } else { - err = gen_card_key (2, subkey_algo, 0, pub_root, ×tamp, expire); + err = gen_card_key (2, subkey_algo, 0, pub_root, ×tamp, expire, + keygen_flags); } if (!err) @@ -5028,13 +5134,15 @@ parse_algo_usage_expire (ctrl_t ctrl, int for_subkey, const char *algostr, const char *usagestr, const char *expirestr, int *r_algo, unsigned int *r_usage, u32 *r_expire, - unsigned int *r_nbits, const char **r_curve) + unsigned int *r_nbits, const char **r_curve, + int *r_version) { gpg_error_t err; int algo; unsigned int use, nbits; u32 expire; int wantuse; + int version = 4; const char *curve = NULL; *r_curve = NULL; @@ -5052,8 +5160,8 @@ parse_algo_usage_expire (ctrl_t ctrl, int for_subkey, err = parse_key_parameter_string (algostr, for_subkey? 1 : 0, usagestr? parse_usagestr (usagestr):0, - &algo, &nbits, &use, &curve, - NULL, NULL, NULL, NULL); + &algo, &nbits, &use, &curve, &version, + NULL, NULL, NULL, NULL, NULL); if (err) return err; @@ -5091,6 +5199,7 @@ parse_algo_usage_expire (ctrl_t ctrl, int for_subkey, *r_usage = use; *r_expire = expire; *r_nbits = nbits; + *r_version = version; return 0; } @@ -5118,6 +5227,7 @@ generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr, char *serialno = NULL; char *cache_nonce = NULL; char *passwd_nonce = NULL; + int keygen_flags = 0; interactive = (!algostr || !usagestr || !expirestr); @@ -5199,10 +5309,16 @@ generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr, } else /* Unattended mode. */ { + int version; + err = parse_algo_usage_expire (ctrl, 1, algostr, usagestr, expirestr, - &algo, &use, &expire, &nbits, &curve); + &algo, &use, &expire, &nbits, &curve, + &version); if (err) goto leave; + + if (version == 5) + keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY; } /* Verify the passphrase now so that we get a cache item for the @@ -5225,7 +5341,8 @@ generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr, if (key_from_hexgrip) { err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip, - keyblock, cur_time, expire, 1); + keyblock, cur_time, expire, 1, + keygen_flags); } else { @@ -5241,7 +5358,7 @@ generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr, passwd = NULL; err = do_create (algo, nbits, curve, - keyblock, cur_time, expire, 1, 0, + keyblock, cur_time, expire, 1, keygen_flags, passwd, &cache_nonce, &passwd_nonce); } if (err) @@ -5289,6 +5406,7 @@ generate_card_subkeypair (ctrl_t ctrl, kbnode_t pub_keyblock, PKT_public_key *sub_pk = NULL; int algo; struct agent_card_info_s info; + int keygen_flags = 0; /* FIXME!!! */ log_assert (keyno >= 1 && keyno <= 3); @@ -5359,7 +5477,8 @@ generate_card_subkeypair (ctrl_t ctrl, kbnode_t pub_keyblock, /* Note, that depending on the backend, the card key generation may update CUR_TIME. */ - err = gen_card_key (keyno, algo, 0, pub_keyblock, &cur_time, expire); + err = gen_card_key (keyno, algo, 0, pub_keyblock, &cur_time, expire, + keygen_flags); /* Get the pointer to the generated public subkey packet. */ if (!err) { @@ -5405,10 +5524,11 @@ write_keyblock( IOBUF out, KBNODE node ) } -/* Note that timestamp is an in/out arg. */ +/* Note that timestamp is an in/out arg. + * FIXME: Does not yet support v5 keys. */ static gpg_error_t gen_card_key (int keyno, int algo, int is_primary, kbnode_t pub_root, - u32 *timestamp, u32 expireval) + u32 *timestamp, u32 expireval, int keygen_flags) { #ifdef ENABLE_CARD_SUPPORT gpg_error_t err; @@ -5482,7 +5602,7 @@ gen_card_key (int keyno, int algo, int is_primary, kbnode_t pub_root, } pk->timestamp = *timestamp; - pk->version = 4; + pk->version = (keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4; if (expireval) pk->expiredate = pk->timestamp + expireval; pk->pubkey_algo = algo; diff --git a/g10/keyid.c b/g10/keyid.c index a9034ee46..aa77b47e2 100644 --- a/g10/keyid.c +++ b/g10/keyid.c @@ -136,19 +136,21 @@ pubkey_string (PKT_public_key *pk, char *buffer, size_t bufsize) } -/* Hash a public key. This function is useful for v4 fingerprints and - for v3 or v4 key signing. */ +/* Hash a public key. This function is useful for v4 and v5 + * fingerprints and for v3 or v4 key signing. */ void hash_public_key (gcry_md_hd_t md, PKT_public_key *pk) { - unsigned int n = 6; + unsigned int n; unsigned int nn[PUBKEY_MAX_NPKEY]; byte *pp[PUBKEY_MAX_NPKEY]; int i; unsigned int nbits; size_t nbytes; int npkey = pubkey_get_npkey (pk->pubkey_algo); + int is_v5 = pk->version == 5; + n = is_v5? 10 : 6; /* FIXME: We can avoid the extra malloc by calling only the first mpi_print here which computes the required length and calling the real mpi_print only at the end. The speed advantage would only be @@ -201,12 +203,22 @@ hash_public_key (gcry_md_hd_t md, PKT_public_key *pk) } } - gcry_md_putc ( md, 0x99 ); /* ctb */ - /* What does it mean if n is greater than 0xFFFF ? */ - gcry_md_putc ( md, n >> 8 ); /* 2 byte length header */ - gcry_md_putc ( md, n ); - gcry_md_putc ( md, pk->version ); - + if (is_v5) + { + gcry_md_putc ( md, 0x9a ); /* ctb */ + gcry_md_putc ( md, n >> 24 ); /* 4 byte length header */ + gcry_md_putc ( md, n >> 16 ); + gcry_md_putc ( md, n >> 8 ); + gcry_md_putc ( md, n ); + gcry_md_putc ( md, pk->version ); + } + else + { + gcry_md_putc ( md, 0x99 ); /* ctb */ + gcry_md_putc ( md, n >> 8 ); /* 2 byte length header */ + gcry_md_putc ( md, n ); + gcry_md_putc ( md, pk->version ); + } gcry_md_putc ( md, pk->timestamp >> 24 ); gcry_md_putc ( md, pk->timestamp >> 16 ); gcry_md_putc ( md, pk->timestamp >> 8 ); @@ -214,6 +226,15 @@ hash_public_key (gcry_md_hd_t md, PKT_public_key *pk) gcry_md_putc ( md, pk->pubkey_algo ); + if (is_v5) + { + n -= 10; + gcry_md_putc ( md, n >> 24 ); + gcry_md_putc ( md, n >> 16 ); + gcry_md_putc ( md, n >> 8 ); + gcry_md_putc ( md, n ); + } + if(npkey==0 && pk->pkey[0] && gcry_mpi_get_flag (pk->pkey[0], GCRYMPI_FLAG_OPAQUE)) { @@ -237,10 +258,10 @@ do_fingerprint_md( PKT_public_key *pk ) { gcry_md_hd_t md; - if (gcry_md_open (&md, DIGEST_ALGO_SHA1, 0)) + if (gcry_md_open (&md, pk->version == 5 ? GCRY_MD_SHA256 : GCRY_MD_SHA1, 0)) BUG (); - hash_public_key(md,pk); - gcry_md_final( md ); + hash_public_key (md,pk); + gcry_md_final (md); return md; } @@ -472,18 +493,27 @@ keystr_from_desc(KEYDB_SEARCH_DESC *desc) case KEYDB_SEARCH_MODE_SHORT_KID: return keystr(desc->u.kid); - case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: { u32 keyid[2]; - keyid[0] = buf32_to_u32 (desc->u.fpr+12); - keyid[1] = buf32_to_u32 (desc->u.fpr+16); + if (desc->fprlen == 32) + { + keyid[0] = buf32_to_u32 (desc->u.fpr); + keyid[1] = buf32_to_u32 (desc->u.fpr+4); + } + else if (desc->fprlen == 20) + { + keyid[0] = buf32_to_u32 (desc->u.fpr+12); + keyid[1] = buf32_to_u32 (desc->u.fpr+16); + } + else if (desc->fprlen == 16) + return "?v3 fpr?"; + else /* oops */ + return "?vx fpr?"; return keystr(keyid); } - case KEYDB_SEARCH_MODE_FPR16: - return "?v3 fpr?"; - default: BUG(); } @@ -491,13 +521,12 @@ keystr_from_desc(KEYDB_SEARCH_DESC *desc) /* - * Get the keyid from the public key and put it into keyid - * if this is not NULL. Return the 32 low bits of the keyid. + * Get the keyid from the public key PK and store it at KEYID unless + * this is NULL. Returns the 32 bit short keyid. */ u32 keyid_from_pk (PKT_public_key *pk, u32 *keyid) { - u32 lowbits; u32 dummy_keyid[2]; if (!keyid) @@ -507,7 +536,6 @@ keyid_from_pk (PKT_public_key *pk, u32 *keyid) { keyid[0] = pk->keyid[0]; keyid[1] = pk->keyid[1]; - lowbits = keyid[1]; } else { @@ -518,24 +546,32 @@ keyid_from_pk (PKT_public_key *pk, u32 *keyid) if(md) { dp = gcry_md_read ( md, 0 ); - keyid[0] = buf32_to_u32 (dp+12); - keyid[1] = buf32_to_u32 (dp+16); - lowbits = keyid[1]; + if (pk->version == 5) + { + keyid[0] = buf32_to_u32 (dp); + keyid[1] = buf32_to_u32 (dp+4); + } + else + { + keyid[0] = buf32_to_u32 (dp+12); + keyid[1] = buf32_to_u32 (dp+16); + } gcry_md_close (md); pk->keyid[0] = keyid[0]; pk->keyid[1] = keyid[1]; } else - pk->keyid[0]=pk->keyid[1]=keyid[0]=keyid[1]=lowbits=0xFFFFFFFF; + pk->keyid[0] = pk->keyid[1] = keyid[0]= keyid[1] = 0xFFFFFFFF; } - return lowbits; + return keyid[1]; /*FIXME:shortkeyid ist different for v5*/ } /* - * Get the keyid from the fingerprint. This function is simple for most - * keys, but has to do a keylookup for old stayle keys. + * Get the keyid from the fingerprint. This function is simple for + * most keys, but has to do a key lookup for old v3 keys where the + * keyid is not part of the fingerprint. */ u32 keyid_from_fingerprint (ctrl_t ctrl, const byte *fprint, @@ -546,7 +582,7 @@ keyid_from_fingerprint (ctrl_t ctrl, const byte *fprint, if( !keyid ) keyid = dummy_keyid; - if (fprint_len != 20) + if (fprint_len != 20 && fprint_len != 32) { /* This is special as we have to lookup the key first. */ PKT_public_key pk; @@ -556,7 +592,8 @@ keyid_from_fingerprint (ctrl_t ctrl, const byte *fprint, rc = get_pubkey_byfprint (ctrl, &pk, NULL, fprint, fprint_len); if( rc ) { - log_error("Oops: keyid_from_fingerprint: no pubkey\n"); + log_printhex (fprint, fprint_len, + "Oops: keyid_from_fingerprint: no pubkey; fpr:"); keyid[0] = 0; keyid[1] = 0; } @@ -566,8 +603,16 @@ keyid_from_fingerprint (ctrl_t ctrl, const byte *fprint, else { const byte *dp = fprint; - keyid[0] = buf32_to_u32 (dp+12); - keyid[1] = buf32_to_u32 (dp+16); + if (fprint_len == 20) /* v4 key */ + { + keyid[0] = buf32_to_u32 (dp+12); + keyid[1] = buf32_to_u32 (dp+16); + } + else /* v5 key */ + { + keyid[0] = buf32_to_u32 (dp); + keyid[1] = buf32_to_u32 (dp+4); + } } return keyid[1]; @@ -582,7 +627,7 @@ keyid_from_sig (PKT_signature *sig, u32 *keyid) keyid[0] = sig->keyid[0]; keyid[1] = sig->keyid[1]; } - return sig->keyid[1]; + return sig->keyid[1]; /*FIXME:shortkeyid*/ } @@ -772,15 +817,23 @@ fingerprint_from_pk (PKT_public_key *pk, byte *array, size_t *ret_len) size_t len; gcry_md_hd_t md; - md = do_fingerprint_md(pk); - dp = gcry_md_read( md, 0 ); + md = do_fingerprint_md (pk); + dp = gcry_md_read (md, 0); len = gcry_md_get_algo_dlen (gcry_md_get_algo (md)); - log_assert( len <= MAX_FINGERPRINT_LEN ); + log_assert (len <= MAX_FINGERPRINT_LEN); if (!array) array = xmalloc ( len ); memcpy (array, dp, len ); - pk->keyid[0] = buf32_to_u32 (dp+12); - pk->keyid[1] = buf32_to_u32 (dp+16); + if (pk->version == 5) + { + pk->keyid[0] = buf32_to_u32 (dp); + pk->keyid[1] = buf32_to_u32 (dp+4); + } + else + { + pk->keyid[0] = buf32_to_u32 (dp+12); + pk->keyid[1] = buf32_to_u32 (dp+16); + } gcry_md_close( md); if (ret_len) @@ -975,7 +1028,12 @@ keygrip_from_pk (PKT_public_key *pk, unsigned char *array) if (!gcry_pk_get_keygrip (s_pkey, array)) { - log_info ("error computing keygrip\n"); + char *hexfpr; + + hexfpr = hexfingerprint (pk, NULL, 0); + log_info ("error computing keygrip (fpr=%s)\n", hexfpr); + xfree (hexfpr); + memset (array, 0, 20); err = gpg_error (GPG_ERR_GENERAL); } diff --git a/g10/keylist.c b/g10/keylist.c index 8b7da76ee..8d5b2e0b9 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -212,7 +212,7 @@ print_pubkey_info (ctrl_t ctrl, estream_t fp, PKT_public_key *pk) else p = get_user_id_native (ctrl, keyid); - if (fp) + if (!fp) tty_printf ("\n"); tty_fprintf (fp, "%s %s/%s %s %s\n", pk->flags.primary? "pub":"sub", @@ -541,7 +541,7 @@ list_all (ctrl_t ctrl, int secret, int mark_secret) ; /* Secret key listing requested but this isn't one. */ else { - if (!opt.with_colons) + if (!opt.with_colons && !(opt.list_options & LIST_SHOW_ONLY_FPR_MBOX)) { resname = keydb_get_resource_name (hd); if (lastresname != resname) @@ -611,6 +611,7 @@ list_one (ctrl_t ctrl, strlist_t names, int secret, int mark_secret) { log_error ("error reading key: %s\n", gpg_strerror (rc)); getkey_end (ctrl, ctx); + write_status_error ("keylist.getkey", rc); return; } @@ -1020,7 +1021,7 @@ list_keyblock_print (ctrl_t ctrl, kbnode_t keyblock, int secret, int fpr, char *mbox, *hash, *p; char hashbuf[32]; - mbox = mailbox_from_userid (uid->name); + mbox = mailbox_from_userid (uid->name, 0); if (mbox && (p = strchr (mbox, '@'))) { *p++ = 0; @@ -1254,6 +1255,57 @@ list_keyblock_print (ctrl_t ctrl, kbnode_t keyblock, int secret, int fpr, xfree (hexgrip); } + +/* Do a simple key listing printing only the fingerprint and the mail + * address of valid keys. */ +static void +list_keyblock_simple (ctrl_t ctrl, kbnode_t keyblock) +{ + gpg_err_code_t ec; + kbnode_t kbctx; + kbnode_t node; + char hexfpr[2*MAX_FINGERPRINT_LEN+1]; + char *mbox; + + (void)ctrl; + + node = find_kbnode (keyblock, PKT_PUBLIC_KEY); + if (!node) + { + log_error ("Oops; key lost!\n"); + dump_kbnode (keyblock); + return; + } + hexfingerprint (node->pkt->pkt.public_key, hexfpr, sizeof hexfpr); + + for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));) + { + if (node->pkt->pkttype == PKT_USER_ID) + { + PKT_user_id *uid = node->pkt->pkt.user_id; + + if (uid->attrib_data) + continue; + + if (uid->flags.expired || uid->flags.revoked) + continue; + + mbox = mailbox_from_userid (uid->name, 0); + if (!mbox) + { + ec = gpg_err_code_from_syserror (); + if (ec != GPG_ERR_EINVAL) + log_error ("error getting mailbox from user-id: %s\n", + gpg_strerror (ec)); + continue; + } + es_fprintf (es_stdout, "%s %s\n", hexfpr, mbox); + xfree (mbox); + } + } +} + + void print_revokers (estream_t fp, PKT_public_key * pk) { @@ -1270,7 +1322,7 @@ print_revokers (estream_t fp, PKT_public_key * pk) es_fprintf (fp, "rvk:::%d::::::", pk->revkey[i].algid); p = pk->revkey[i].fpr; - for (j = 0; j < 20; j++, p++) + for (j = 0; j < pk->revkey[i].fprlen; j++, p++) es_fprintf (fp, "%02X", *p); es_fprintf (fp, ":%02x%s:\n", pk->revkey[i].class, @@ -1807,6 +1859,12 @@ list_keyblock (ctrl_t ctrl, if (opt.with_colons) list_keyblock_colon (ctrl, keyblock, secret, has_secret); + else if ((opt.list_options & LIST_SHOW_ONLY_FPR_MBOX)) + { + if (!listctx->no_validity) + check_trustdb_stale (ctrl); + list_keyblock_simple (ctrl, keyblock); + } else list_keyblock_print (ctrl, keyblock, secret, fpr, listctx); @@ -2044,10 +2102,18 @@ print_key_line (ctrl_t ctrl, estream_t fp, PKT_public_key *pk, int secret) tty_fprintf (fp, "/%s", keystr_from_pk (pk)); tty_fprintf (fp, " %s", datestr_from_pk (pk)); - if ((opt.list_options & LIST_SHOW_USAGE)) + if (pk->flags.primary + && !(openpgp_pk_algo_usage (pk->pubkey_algo) + & (PUBKEY_USAGE_CERT| PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH))) + { + /* A primary key which is really not capable to sign. */ + tty_fprintf (fp, " [INVALID_ALGO]"); + } + else if ((opt.list_options & LIST_SHOW_USAGE)) { tty_fprintf (fp, " [%s]", usagestr_from_pk (pk, 0)); } + if (pk->flags.revoked) { tty_fprintf (fp, " ["); diff --git a/g10/keyring.c b/g10/keyring.c index 25ef50747..21791a6ac 100644 --- a/g10/keyring.c +++ b/g10/keyring.c @@ -995,8 +995,6 @@ keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, case KEYDB_SEARCH_MODE_LONG_KID: need_keyid = 1; break; - case KEYDB_SEARCH_MODE_FPR16: - case KEYDB_SEARCH_MODE_FPR20: case KEYDB_SEARCH_MODE_FPR: need_fpr = 1; break; @@ -1134,11 +1132,12 @@ keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, pk = pkt.pkt.public_key; ++pk_no; - if (need_fpr) { - fingerprint_from_pk (pk, afp, &an); - while (an < 20) /* fill up to 20 bytes */ - afp[an++] = 0; - } + if (need_fpr) + { + fingerprint_from_pk (pk, afp, &an); + while (an < 32) /* fill up to 32 bytes */ + afp[an++] = 0; + } if (need_keyid) keyid_from_pk (pk, aki); @@ -1180,13 +1179,9 @@ keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, && desc[n].u.kid[1] == aki[1]) goto found; break; - case KEYDB_SEARCH_MODE_FPR16: - if (pk && !memcmp (desc[n].u.fpr, afp, 16)) - goto found; - break; - case KEYDB_SEARCH_MODE_FPR20: case KEYDB_SEARCH_MODE_FPR: - if (pk && !memcmp (desc[n].u.fpr, afp, 20)) + if (pk && desc[n].fprlen >= 16 && desc[n].fprlen <= 32 + && !memcmp (desc[n].u.fpr, afp, desc[n].fprlen)) goto found; break; case KEYDB_SEARCH_MODE_FIRST: diff --git a/g10/keyserver.c b/g10/keyserver.c index a8c222d3f..66900f7a9 100644 --- a/g10/keyserver.c +++ b/g10/keyserver.c @@ -470,7 +470,6 @@ parse_preferred_keyserver(PKT_signature *sig) static void print_keyrec (ctrl_t ctrl, int number,struct keyrec *keyrec) { - int i; iobuf_writebyte(keyrec->uidbuf,0); iobuf_flush_temp(keyrec->uidbuf); @@ -509,20 +508,11 @@ print_keyrec (ctrl_t ctrl, int number,struct keyrec *keyrec) es_printf ("key %s",keystr(keyrec->desc.u.kid)); break; - /* If it gave us a PGP 2.x fingerprint, not much we can do - beyond displaying it. */ - case KEYDB_SEARCH_MODE_FPR16: - es_printf ("key "); - for(i=0;i<16;i++) - es_printf ("%02X",keyrec->desc.u.fpr[i]); - break; - - /* If we get a modern fingerprint, we have the most - flexibility. */ - case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: { u32 kid[2]; - keyid_from_fingerprint (ctrl, keyrec->desc.u.fpr,20,kid); + keyid_from_fingerprint (ctrl, keyrec->desc.u.fpr, keyrec->desc.fprlen, + kid); es_printf("key %s",keystr(kid)); } break; @@ -613,8 +603,7 @@ parse_keyrec(char *keystring) err = classify_user_id (tok, &work->desc, 1); if (err || (work->desc.mode != KEYDB_SEARCH_MODE_SHORT_KID && work->desc.mode != KEYDB_SEARCH_MODE_LONG_KID - && work->desc.mode != KEYDB_SEARCH_MODE_FPR16 - && work->desc.mode != KEYDB_SEARCH_MODE_FPR20)) + && work->desc.mode != KEYDB_SEARCH_MODE_FPR)) { work->desc.mode=KEYDB_SEARCH_MODE_NONE; return ret; @@ -995,8 +984,7 @@ keyserver_export (ctrl_t ctrl, strlist_t users) err = classify_user_id (users->d, &desc, 1); if (err || (desc.mode != KEYDB_SEARCH_MODE_SHORT_KID && desc.mode != KEYDB_SEARCH_MODE_LONG_KID - && desc.mode != KEYDB_SEARCH_MODE_FPR16 - && desc.mode != KEYDB_SEARCH_MODE_FPR20)) + && desc.mode != KEYDB_SEARCH_MODE_FPR)) { log_error(_("\"%s\" not a key ID: skipping\n"),users->d); continue; @@ -1065,14 +1053,9 @@ keyserver_retrieval_screener (kbnode_t keyblock, void *opaque) /* Compare requested and returned fingerprints if available. */ for (n = 0; n < ndesc; n++) { - if (desc[n].mode == KEYDB_SEARCH_MODE_FPR20) - { - if (fpr_len == 20 && !memcmp (fpr, desc[n].u.fpr, 20)) - return 0; - } - else if (desc[n].mode == KEYDB_SEARCH_MODE_FPR16) + if (desc[n].mode == KEYDB_SEARCH_MODE_FPR) { - if (fpr_len == 16 && !memcmp (fpr, desc[n].u.fpr, 16)) + if (fpr_len == desc[n].fprlen && !memcmp (fpr, desc[n].u.fpr, 32)) return 0; } else if (desc[n].mode == KEYDB_SEARCH_MODE_LONG_KID) @@ -1110,8 +1093,7 @@ keyserver_import (ctrl_t ctrl, strlist_t users) err = classify_user_id (users->d, &desc[count], 1); if (err || (desc[count].mode != KEYDB_SEARCH_MODE_SHORT_KID && desc[count].mode != KEYDB_SEARCH_MODE_LONG_KID - && desc[count].mode != KEYDB_SEARCH_MODE_FPR16 - && desc[count].mode != KEYDB_SEARCH_MODE_FPR20)) + && desc[count].mode != KEYDB_SEARCH_MODE_FPR)) { log_error (_("\"%s\" not a key ID: skipping\n"), users->d); continue; @@ -1167,14 +1149,13 @@ keyserver_import_fprint (ctrl_t ctrl, const byte *fprint,size_t fprint_len, memset(&desc,0,sizeof(desc)); - if(fprint_len==16) - desc.mode=KEYDB_SEARCH_MODE_FPR16; - else if(fprint_len==20) - desc.mode=KEYDB_SEARCH_MODE_FPR20; + if (fprint_len == 16 || fprint_len == 20 || fprint_len == 32) + desc.mode = KEYDB_SEARCH_MODE_FPR; else return -1; memcpy(desc.u.fpr,fprint,fprint_len); + desc.fprlen = fprint_len; /* TODO: Warn here if the fingerprint we got doesn't match the one we asked for? */ @@ -1291,20 +1272,21 @@ keyidlist (ctrl_t ctrl, strlist_t users, KEYDB_SEARCH_DESC **klist, This is because it's easy to calculate any sort of keyid from a v4 fingerprint, but not a v3 fingerprint. */ - if(node->pkt->pkt.public_key->version<4) + if (node->pkt->pkt.public_key->version < 4) { (*klist)[*count].mode=KEYDB_SEARCH_MODE_LONG_KID; keyid_from_pk(node->pkt->pkt.public_key, (*klist)[*count].u.kid); } else - { - size_t dummy; + { + size_t fprlen; - (*klist)[*count].mode=KEYDB_SEARCH_MODE_FPR20; - fingerprint_from_pk(node->pkt->pkt.public_key, - (*klist)[*count].u.fpr,&dummy); - } + fingerprint_from_pk (node->pkt->pkt.public_key, + (*klist)[*count].u.fpr, &fprlen); + (*klist)[*count].mode = KEYDB_SEARCH_MODE_FPR; + (*klist)[*count].fprlen = fprlen; + } /* This is a little hackish, using the skipfncvalue as a void* pointer to the keyserver spec, but we don't need @@ -1528,7 +1510,7 @@ keyserver_search (ctrl_t ctrl, strlist_t tokens) err = gpg_dirmngr_ks_search (ctrl, searchstr, search_line_handler, &parm); - if (parm.not_found) + if (parm.not_found || gpg_err_code (err) == GPG_ERR_NO_DATA) { if (parm.searchstr_disp) log_info (_("key \"%s\" not found on keyserver\n"), @@ -1539,6 +1521,8 @@ keyserver_search (ctrl_t ctrl, strlist_t tokens) if (gpg_err_code (err) == GPG_ERR_NO_KEYSERVER) log_error (_("no keyserver known (use option --keyserver)\n")); + else if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = gpg_error (GPG_ERR_NOT_FOUND); else if (err) log_error ("error searching keyserver: %s\n", gpg_strerror (err)); @@ -1618,10 +1602,9 @@ keyserver_get_chunk (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc, { int quiet = 0; - if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR20 - || desc[idx].mode == KEYDB_SEARCH_MODE_FPR16) + if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR) { - n = 1+2+2*20; + n = 1+2+2*desc[idx].fprlen; if (idx && linelen + n > MAX_KS_GET_LINELEN) break; /* Declare end of this chunk. */ linelen += n; @@ -1632,11 +1615,9 @@ keyserver_get_chunk (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc, else { strcpy (pattern[npat], "0x"); - bin2hex (desc[idx].u.fpr, - desc[idx].mode == KEYDB_SEARCH_MODE_FPR20? 20 : 16, - pattern[npat]+2); + bin2hex (desc[idx].u.fpr, desc[idx].fprlen, pattern[npat]+2); npat++; - if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR20) + if (desc[idx].fprlen == 20 || desc[idx].fprlen == 32) npat_fpr++; } } @@ -1715,7 +1696,7 @@ keyserver_get_chunk (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc, } } - /* Remember now many of search items were considered. Note that + /* Remember how many of the search items were considered. Note that this is different from NPAT. */ *r_ndesc_used = idx; @@ -2053,7 +2034,7 @@ keyserver_import_wkd (ctrl_t ctrl, const char *name, int quick, /* We want to work on the mbox. That is what dirmngr will do anyway * and we need the mbox for the import filter anyway. */ - mbox = mailbox_from_userid (name); + mbox = mailbox_from_userid (name, 0); if (!mbox) { err = gpg_error_from_syserror (); diff --git a/g10/main.h b/g10/main.h index 768f7cfb8..34a932b16 100644 --- a/g10/main.h +++ b/g10/main.h @@ -41,7 +41,9 @@ # define DEFAULT_CIPHER_ALGO CIPHER_ALGO_3DES #endif -#if GCRYPT_VERSION_NUMBER < 0x019000 +/* We will start using OCB mode by default only if the yet to be + * released libgcrypt 1.9 is used. */ +#if GCRYPT_VERSION_NUMBER < 0x010900 # define DEFAULT_AEAD_ALGO AEAD_ALGO_OCB #else # define DEFAULT_AEAD_ALGO AEAD_ALGO_EAX @@ -392,7 +394,8 @@ struct impex_filter_parm_s const char *impex_filter_getval (void *cookie, const char *propname); gpg_error_t transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, - kbnode_t sec_keyblock, int batch, int force); + kbnode_t sec_keyblock, int batch, int force, + int only_marked); int collapse_uids( KBNODE *keyblock ); @@ -505,8 +508,6 @@ gpg_error_t card_generate_subkey (ctrl_t ctrl, kbnode_t pub_keyblock); int card_store_subkey (KBNODE node, int use); #endif -#define S2K_DECODE_COUNT(_val) ((16ul + ((_val) & 15)) << (((_val) >> 4) + 6)) - /*-- migrate.c --*/ void migrate_secring (ctrl_t ctrl); diff --git a/g10/mainproc.c b/g10/mainproc.c index 6ec15894d..7acf67b1e 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -86,6 +86,7 @@ struct mainproc_context int trustletter; /* Temporary usage in list_node. */ ulong symkeys; /* Number of symmetrically encrypted session keys. */ struct pubkey_enc_list *pkenc_list; /* List of encryption packets. */ + int seen_pkt_encrypted_aead; /* PKT_ENCRYPTED_AEAD packet seen. */ struct { unsigned int sig_seen:1; /* Set to true if a signature packet has been seen. */ @@ -137,6 +138,7 @@ release_list( CTX c ) c->any.data = 0; c->any.uncompress_failed = 0; c->last_was_session_key = 0; + c->seen_pkt_encrypted_aead = 0; xfree (c->dek); c->dek = NULL; } @@ -479,6 +481,7 @@ proc_pubkey_enc (CTX c, PACKET *pkt) x->keyid[0] = enc->keyid[0]; x->keyid[1] = enc->keyid[1]; x->pubkey_algo = enc->pubkey_algo; + x->result = -1; x->data[0] = x->data[1] = NULL; if (enc->data[0]) { @@ -536,6 +539,9 @@ proc_encrypted (CTX c, PACKET *pkt) int result = 0; int early_plaintext = literals_seen; + if (pkt->pkttype == PKT_ENCRYPTED_AEAD) + c->seen_pkt_encrypted_aead = 1; + if (early_plaintext) { log_info (_("WARNING: multiple plaintexts seen\n")); @@ -572,22 +578,21 @@ proc_encrypted (CTX c, PACKET *pkt) { c->dek = xmalloc_secure_clear (sizeof *c->dek); result = get_session_key (c->ctrl, c->pkenc_list, c->dek); - if (result == GPG_ERR_NO_SECKEY) - { - if (is_status_enabled ()) - { - struct pubkey_enc_list *list; - - for (list = c->pkenc_list; list; list = list->next) - { - char buf[20]; - snprintf (buf, sizeof buf, "%08lX%08lX", - (ulong)list->keyid[0], (ulong)list->keyid[1]); - write_status_text (STATUS_NO_SECKEY, buf); - } - } + if (is_status_enabled ()) + { + struct pubkey_enc_list *list; + + for (list = c->pkenc_list; list; list = list->next) + if (list->result == GPG_ERR_NO_SECKEY) + { + char buf[20]; + snprintf (buf, sizeof buf, "%08lX%08lX", + (ulong)list->keyid[0], (ulong)list->keyid[1]); + write_status_text (STATUS_NO_SECKEY, buf); + } } - else if (result) + + if (result) { log_info (_("public key decryption failed: %s\n"), gpg_strerror (result)); @@ -704,7 +709,6 @@ proc_encrypted (CTX c, PACKET *pkt) } - if (!result) result = decrypt_data (c->ctrl, c, pkt->pkt.encrypted, c->dek ); @@ -804,14 +808,31 @@ proc_encrypted (CTX c, PACKET *pkt) } +static int +have_seen_pkt_encrypted_aead( CTX c ) +{ + CTX cc; + + for (cc = c; cc; cc = cc->anchor) + { + if (cc->seen_pkt_encrypted_aead) + return 1; + } + + return 0; +} + + static void proc_plaintext( CTX c, PACKET *pkt ) { PKT_plaintext *pt = pkt->pkt.plaintext; int any, clearsig, rc; kbnode_t n; + unsigned char *extrahash; + size_t extrahashlen; - /* This is a literal data packet. Bumb a counter for later checks. */ + /* This is a literal data packet. Bump a counter for later checks. */ literals_seen++; if (pt->namelen == 8 && !memcmp( pt->name, "_CONSOLE", 8)) @@ -843,7 +864,10 @@ proc_plaintext( CTX c, PACKET *pkt ) /* The onepass signature case. */ if (n->pkt->pkt.onepass_sig->digest_algo) { - gcry_md_enable (c->mfx.md, n->pkt->pkt.onepass_sig->digest_algo); + if (!opt.skip_verify) + gcry_md_enable (c->mfx.md, + n->pkt->pkt.onepass_sig->digest_algo); + any = 1; } } @@ -861,7 +885,8 @@ proc_plaintext( CTX c, PACKET *pkt ) * documents. */ clearsig = (*data == 0x01); for (data++, datalen--; datalen; datalen--, data++) - gcry_md_enable (c->mfx.md, *data); + if (!opt.skip_verify) + gcry_md_enable (c->mfx.md, *data); any = 1; break; /* Stop here as one-pass signature packets are not expected. */ @@ -869,12 +894,13 @@ proc_plaintext( CTX c, PACKET *pkt ) else if (n->pkt->pkttype == PKT_SIGNATURE) { /* The SIG+LITERAL case that PGP used to use. */ - gcry_md_enable ( c->mfx.md, n->pkt->pkt.signature->digest_algo ); + if (!opt.skip_verify) + gcry_md_enable (c->mfx.md, n->pkt->pkt.signature->digest_algo); any = 1; } } - if (!any && !opt.skip_verify) + if (!any && !opt.skip_verify && !have_seen_pkt_encrypted_aead(c)) { /* This is for the old GPG LITERAL+SIG case. It's not legal according to 2440, so hopefully it won't come up that often. @@ -920,12 +946,37 @@ proc_plaintext( CTX c, PACKET *pkt ) if (rc) log_error ("handle plaintext failed: %s\n", gpg_strerror (rc)); + /* We add a marker control packet instead of the plaintext packet. + * This is so that we can later detect invalid packet sequences. + * The apcket is further used to convey extra data from the + * plaintext packet to the signature verification. */ + extrahash = xtrymalloc (6 + pt->namelen); + if (!extrahash) + { + /* No way to return an error. */ + rc = gpg_error_from_syserror (); + log_error ("malloc failed in %s: %s\n", __func__, gpg_strerror (rc)); + extrahashlen = 0; + } + else + { + extrahash[0] = pt->mode; + extrahash[1] = pt->namelen; + if (pt->namelen) + memcpy (extrahash+2, pt->name, pt->namelen); + extrahashlen = 2 + pt->namelen; + extrahash[extrahashlen++] = pt->timestamp >> 24; + extrahash[extrahashlen++] = pt->timestamp >> 16; + extrahash[extrahashlen++] = pt->timestamp >> 8; + extrahash[extrahashlen++] = pt->timestamp ; + } + free_packet (pkt, NULL); c->last_was_session_key = 0; - /* We add a marker control packet instead of the plaintext packet. - * This is so that we can later detect invalid packet sequences. */ - n = new_kbnode (create_gpg_control (CTRLPKT_PLAINTEXT_MARK, NULL, 0)); + n = new_kbnode (create_gpg_control (CTRLPKT_PLAINTEXT_MARK, + extrahash, extrahashlen)); + xfree (extrahash); if (c->list) add_kbnode (c->list, n); else @@ -995,7 +1046,8 @@ proc_compressed (CTX c, PACKET *pkt) * found. Returns: 0 = valid signature or an error code */ static int -do_check_sig (CTX c, kbnode_t node, int *is_selfsig, +do_check_sig (CTX c, kbnode_t node, const void *extrahash, size_t extrahashlen, + int *is_selfsig, int *is_expkey, int *is_revkey, PKT_public_key **r_pk) { PKT_signature *sig; @@ -1081,14 +1133,16 @@ do_check_sig (CTX c, kbnode_t node, int *is_selfsig, /* We only get here if we are checking the signature of a binary (0x00) or text document (0x01). */ - rc = check_signature2 (c->ctrl, sig, md, NULL, is_expkey, is_revkey, r_pk); + rc = check_signature2 (c->ctrl, sig, md, extrahash, extrahashlen, + NULL, is_expkey, is_revkey, r_pk); if (! rc) md_good = md; else if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE && md2) { PKT_public_key *pk2; - rc = check_signature2 (c->ctrl, sig, md2, NULL, is_expkey, is_revkey, + rc = check_signature2 (c->ctrl, sig, md2, extrahash, extrahashlen, + NULL, is_expkey, is_revkey, r_pk? &pk2 : NULL); if (!rc) { @@ -1251,7 +1305,7 @@ list_node (CTX c, kbnode_t node) if (opt.check_sigs) { fflush (stdout); - rc2 = do_check_sig (c, node, &is_selfsig, NULL, NULL, NULL); + rc2 = do_check_sig (c, node, NULL, 0, &is_selfsig, NULL, NULL, NULL); switch (gpg_err_code (rc2)) { case 0: sigrc = '!'; break; @@ -1714,7 +1768,7 @@ akl_has_wkd_method (void) } -/* Return the ISSUER fingerprint buffer and its lenbgth at R_LEN. +/* Return the ISSUER fingerprint buffer and its length at R_LEN. * Returns NULL if not available. The returned buffer is valid as * long as SIG is not modified. */ const byte * @@ -1724,7 +1778,7 @@ issuer_fpr_raw (PKT_signature *sig, size_t *r_len) size_t n; p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_ISSUER_FPR, &n); - if (p && n == 21 && p[0] == 4) + if (p && ((n == 21 && p[0] == 4) || (n == 33 && p[0] == 5))) { *r_len = n - 1; return p+1; @@ -1787,6 +1841,8 @@ check_sig_and_print (CTX c, kbnode_t node) char *issuer_fpr = NULL; PKT_public_key *pk = NULL; /* The public key for the signature or NULL. */ int tried_ks_by_fpr; + const void *extrahash = NULL; + size_t extrahashlen = 0; if (opt.skip_verify) { @@ -1844,6 +1900,8 @@ check_sig_and_print (CTX c, kbnode_t node) { if (n->next) goto ambiguous; /* We only allow one P packet. */ + extrahash = n->pkt->pkt.gpg_control->data; + extrahashlen = n->pkt->pkt.gpg_control->datalen; } else goto ambiguous; @@ -1858,6 +1916,9 @@ check_sig_and_print (CTX c, kbnode_t node) && (n->pkt->pkt.gpg_control->control == CTRLPKT_PLAINTEXT_MARK))) goto ambiguous; + extrahash = n->pkt->pkt.gpg_control->data; + extrahashlen = n->pkt->pkt.gpg_control->datalen; + for (n_sig=0, n = n->next; n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next) n_sig++; @@ -1888,6 +1949,8 @@ check_sig_and_print (CTX c, kbnode_t node) && (n->pkt->pkt.gpg_control->control == CTRLPKT_PLAINTEXT_MARK))) goto ambiguous; + extrahash = n->pkt->pkt.gpg_control->data; + extrahashlen = n->pkt->pkt.gpg_control->datalen; for (n_sig=0, n = n->next; n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next) n_sig++; @@ -1933,7 +1996,8 @@ check_sig_and_print (CTX c, kbnode_t node) if (sig->signers_uid) log_info (_(" issuer \"%s\"\n"), sig->signers_uid); - rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, extrahash, extrahashlen, + NULL, &is_expkey, &is_revkey, &pk); /* If the key isn't found, check for a preferred keyserver. */ if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY && sig->flags.pref_ks) @@ -1968,8 +2032,8 @@ check_sig_and_print (CTX c, kbnode_t node) res = keyserver_import_keyid (c->ctrl, sig->keyid,spec, 1); glo_ctrl.in_auto_key_retrieve--; if (!res) - rc = do_check_sig (c, node, NULL, - &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, extrahash, extrahashlen, + NULL, &is_expkey, &is_revkey, &pk); free_keyserver_spec (spec); if (!rc) @@ -2004,7 +2068,8 @@ check_sig_and_print (CTX c, kbnode_t node) glo_ctrl.in_auto_key_retrieve--; free_keyserver_spec (spec); if (!res) - rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, extrahash, extrahashlen, + NULL, &is_expkey, &is_revkey, &pk); } } } @@ -2026,7 +2091,7 @@ check_sig_and_print (CTX c, kbnode_t node) p = issuer_fpr_raw (sig, &n); if (p) { - /* v4 packet with a SHA-1 fingerprint. */ + /* v4 or v5 packet with a SHA-1/256 fingerprint. */ free_public_key (pk); pk = NULL; glo_ctrl.in_auto_key_retrieve++; @@ -2034,7 +2099,8 @@ check_sig_and_print (CTX c, kbnode_t node) tried_ks_by_fpr = 1; glo_ctrl.in_auto_key_retrieve--; if (!res) - rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, extrahash, extrahashlen, + NULL, &is_expkey, &is_revkey, &pk); } } @@ -2056,7 +2122,8 @@ check_sig_and_print (CTX c, kbnode_t node) /* Fixme: If the fingerprint is embedded in the signature, * compare it to the fingerprint of the returned key. */ if (!res) - rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, extrahash, extrahashlen, + NULL, &is_expkey, &is_revkey, &pk); } /* If the above methods did't work, our next try is to use a @@ -2074,7 +2141,8 @@ check_sig_and_print (CTX c, kbnode_t node) res = keyserver_import_keyid (c->ctrl, sig->keyid, opt.keyserver, 1); glo_ctrl.in_auto_key_retrieve--; if (!res) - rc = do_check_sig (c, node, NULL, &is_expkey, &is_revkey, &pk); + rc = do_check_sig (c, node, extrahash, extrahashlen, + NULL, &is_expkey, &is_revkey, &pk); } if (!rc || gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE) diff --git a/g10/misc.c b/g10/misc.c index d7a3ee3f2..0541d2b77 100644 --- a/g10/misc.c +++ b/g10/misc.c @@ -513,6 +513,7 @@ map_pk_gcry_to_openpgp (enum gcry_pk_algos algo) { switch (algo) { + case GCRY_PK_EDDSA: return PUBKEY_ALGO_EDDSA; case GCRY_PK_ECDSA: return PUBKEY_ALGO_ECDSA; case GCRY_PK_ECDH: return PUBKEY_ALGO_ECDH; default: return algo < 110 ? (pubkey_algo_t)algo : 0; @@ -549,7 +550,7 @@ openpgp_cipher_blocklen (cipher_algo_t algo) /**************** * Wrapper around the libgcrypt function with additional checks on - * the OpenPGP contraints for the algo ID. + * the OpenPGP constraints for the algo ID. */ int openpgp_cipher_test_algo (cipher_algo_t algo) @@ -712,7 +713,7 @@ openpgp_pk_test_algo2 (pubkey_algo_t algo, unsigned int use) #endif case PUBKEY_ALGO_ELGAMAL: - /* Dont't allow type 20 keys unless in rfc2440 mode. */ + /* Don't allow type 20 keys unless in rfc2440 mode. */ if (RFC2440) ga = GCRY_PK_ELG; break; @@ -723,6 +724,13 @@ openpgp_pk_test_algo2 (pubkey_algo_t algo, unsigned int use) if (!ga) return gpg_error (GPG_ERR_PUBKEY_ALGO); + /* Elgamal in OpenPGP used to support signing and Libgcrypt still + * does. However, we removed the signing capability from gpg ages + * ago. This function should reflect this so that errors are thrown + * early and not only when we try to sign using Elgamal. */ + if (ga == GCRY_PK_ELG && (use & (PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG))) + return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + /* Now check whether Libgcrypt has support for the algorithm. */ return gcry_pk_algo_info (ga, GCRYCTL_TEST_ALGO, NULL, &use_buf); } @@ -1521,6 +1529,8 @@ optlen(const char *s) return strlen(s); } + +/* Note: This function returns true on success. */ int parse_options(char *str,unsigned int *options, struct parse_options *opts,int noisy) diff --git a/g10/options.h b/g10/options.h index 7defbda76..8adf09f08 100644 --- a/g10/options.h +++ b/g10/options.h @@ -360,6 +360,7 @@ EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode; #define IMPORT_RESTORE (1<<10) #define IMPORT_REPAIR_KEYS (1<<11) #define IMPORT_DRY_RUN (1<<12) +#define IMPORT_DROP_UIDS (1<<13) #define EXPORT_LOCAL_SIGS (1<<0) #define EXPORT_ATTRIBUTES (1<<1) @@ -370,6 +371,7 @@ EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode; #define EXPORT_PKA_FORMAT (1<<6) #define EXPORT_DANE_FORMAT (1<<7) #define EXPORT_BACKUP (1<<10) +#define EXPORT_DROP_UIDS (1<<13) #define LIST_SHOW_PHOTOS (1<<0) #define LIST_SHOW_POLICY_URLS (1<<1) @@ -384,6 +386,7 @@ EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode; #define LIST_SHOW_SIG_EXPIRE (1<<9) #define LIST_SHOW_SIG_SUBPACKETS (1<<10) #define LIST_SHOW_USAGE (1<<11) +#define LIST_SHOW_ONLY_FPR_MBOX (1<<12) #define VERIFY_SHOW_PHOTOS (1<<0) #define VERIFY_SHOW_POLICY_URLS (1<<1) diff --git a/g10/packet.h b/g10/packet.h index 6e1438be6..41dd1a95a 100644 --- a/g10/packet.h +++ b/g10/packet.h @@ -34,11 +34,11 @@ #define DEBUG_PARSE_PACKET 1 -/* Constants to allocate static MPI arrays. */ -#define PUBKEY_MAX_NPKEY 5 -#define PUBKEY_MAX_NSKEY 7 -#define PUBKEY_MAX_NSIG 2 -#define PUBKEY_MAX_NENC 2 +/* Constants to allocate static MPI arrays. */ +#define PUBKEY_MAX_NPKEY OPENPGP_MAX_NPKEY +#define PUBKEY_MAX_NSKEY OPENPGP_MAX_NSKEY +#define PUBKEY_MAX_NSIG OPENPGP_MAX_NSIG +#define PUBKEY_MAX_NENC OPENPGP_MAX_NENC /* Usage flags */ #define PUBKEY_USAGE_SIG GCRY_PK_USAGE_SIGN /* Good for signatures. */ @@ -137,6 +137,7 @@ struct pubkey_enc_list struct pubkey_enc_list *next; u32 keyid[2]; int pubkey_algo; + int result; gcry_mpi_t data[PUBKEY_MAX_NENC]; }; @@ -180,6 +181,8 @@ struct revocation_key { byte class; /* The public-key algorithm ID. */ byte algid; + /* The length of the fingerprint. */ + byte fprlen; /* The fingerprint of the authorized key. */ byte fpr[MAX_FINGERPRINT_LEN]; }; @@ -244,7 +247,7 @@ typedef struct const byte *trust_regexp; struct revocation_key *revkey; int numrevkeys; - int help_counter; /* Used internally bu some fucntions. */ + int help_counter; /* Used internally bu some functions. */ pka_info_t *pka_info; /* Malloced PKA data or NULL if not available. See also flags.pka_tried. */ char *signers_uid; /* Malloced value of the SIGNERS_UID @@ -850,7 +853,7 @@ PACKET *create_gpg_control ( ctrlpkttype_t type, /*-- build-packet.c --*/ int build_packet (iobuf_t out, PACKET *pkt); gpg_error_t build_packet_and_meta (iobuf_t out, PACKET *pkt); -gpg_error_t gpg_mpi_write (iobuf_t out, gcry_mpi_t a); +gpg_error_t gpg_mpi_write (iobuf_t out, gcry_mpi_t a, unsigned int *t_nwritten); gpg_error_t gpg_mpi_write_nohdr (iobuf_t out, gcry_mpi_t a); u32 calc_packet_length( PACKET *pkt ); void build_sig_subpkt( PKT_signature *sig, sigsubpkttype_t type, @@ -897,6 +900,7 @@ int check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest); * it and verifying the signature. */ gpg_error_t check_signature2 (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest, + const void *extrahash, size_t extrahashlen, u32 *r_expiredate, int *r_expired, int *r_revoked, PKT_public_key **r_pk); diff --git a/g10/parse-packet.c b/g10/parse-packet.c index 92c65294a..5b4b1c900 100644 --- a/g10/parse-packet.c +++ b/g10/parse-packet.c @@ -1644,7 +1644,7 @@ parse_one_sig_subpkt (const byte * buffer, size_t n, int type) if (n < 8) break; return 0; - case SIGSUBPKT_ISSUER_FPR: /* issuer key ID */ + case SIGSUBPKT_ISSUER_FPR: /* issuer key fingerprint */ if (n < 21) break; return 0; @@ -1905,21 +1905,23 @@ parse_revkeys (PKT_signature * sig) while ((revkey = enum_sig_subpkt (sig->hashed, SIGSUBPKT_REV_KEY, &len, &seq, NULL))) { - if (/* The only valid length is 22 bytes. See RFC 4880 - 5.2.3.15. */ - len == 22 - /* 0x80 bit must be set on the class. */ + /* Consider only valid packets. They must have a length of + * either 2+20 or 2+32 octets and bit 7 of the class octet must + * be set. */ + if ((len == 22 || len == 34) && (revkey[0] & 0x80)) { sig->revkey = xrealloc (sig->revkey, sizeof (struct revocation_key) * (sig->numrevkeys + 1)); - /* Copy the individual fields. */ sig->revkey[sig->numrevkeys].class = revkey[0]; sig->revkey[sig->numrevkeys].algid = revkey[1]; - memcpy (sig->revkey[sig->numrevkeys].fpr, &revkey[2], 20); - + len -= 2; + sig->revkey[sig->numrevkeys].fprlen = len; + memcpy (sig->revkey[sig->numrevkeys].fpr, revkey+2, len); + memset (sig->revkey[sig->numrevkeys].fpr+len, 0, + sizeof (sig->revkey[sig->numrevkeys].fpr) - len); sig->numrevkeys++; } } @@ -1932,7 +1934,7 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, { int md5_len = 0; unsigned n; - int is_v4 = 0; + int is_v4or5 = 0; int rc = 0; int i, ndata; @@ -1945,8 +1947,8 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, } sig->version = iobuf_get_noeof (inp); pktlen--; - if (sig->version == 4) - is_v4 = 1; + if (sig->version == 4 || sig->version == 5) + is_v4or5 = 1; else if (sig->version != 2 && sig->version != 3) { log_error ("packet(%d) with unknown version %d\n", @@ -1957,7 +1959,7 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, goto leave; } - if (!is_v4) + if (!is_v4or5) { if (pktlen == 0) goto underflow; @@ -1968,7 +1970,7 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, goto underflow; sig->sig_class = iobuf_get_noeof (inp); pktlen--; - if (!is_v4) + if (!is_v4or5) { if (pktlen < 12) goto underflow; @@ -1987,7 +1989,7 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, pktlen--; sig->flags.exportable = 1; sig->flags.revocable = 1; - if (is_v4) /* Read subpackets. */ + if (is_v4or5) /* Read subpackets. */ { if (pktlen < 2) goto underflow; @@ -2058,7 +2060,7 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, sig->digest_start[1] = iobuf_get_noeof (inp); pktlen--; - if (is_v4 && sig->pubkey_algo) /* Extract required information. */ + if (is_v4or5 && sig->pubkey_algo) /* Extract required information. */ { const byte *p; size_t len; @@ -2076,10 +2078,23 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, && opt.verbose) log_info ("signature packet without timestamp\n"); - p = parse_sig_subpkt2 (sig, SIGSUBPKT_ISSUER); - if (p) - { - sig->keyid[0] = buf32_to_u32 (p); + /* Set the key id. We first try the issuer fingerprint and if + * it is a v4 signature the fallback to the issuer. Note that + * only the issuer packet is also searched in the unhashed area. */ + p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_ISSUER_FPR, &len); + if (p && len == 21 && p[0] == 4) + { + sig->keyid[0] = buf32_to_u32 (p + 1 + 12); + sig->keyid[1] = buf32_to_u32 (p + 1 + 16); + } + else if (p && len == 33 && p[0] == 5) + { + sig->keyid[0] = buf32_to_u32 (p + 1 ); + sig->keyid[1] = buf32_to_u32 (p + 1 + 4); + } + else if ((p = parse_sig_subpkt2 (sig, SIGSUBPKT_ISSUER))) + { + sig->keyid[0] = buf32_to_u32 (p); sig->keyid[1] = buf32_to_u32 (p + 4); } else if (!(sig->pubkey_algo >= 100 && sig->pubkey_algo <= 110) @@ -2159,7 +2174,7 @@ parse_signature (IOBUF inp, int pkttype, unsigned long pktlen, (ulong) sig->keyid[0], (ulong) sig->keyid[1], sig->version, (ulong) sig->timestamp, md5_len, sig->sig_class, sig->digest_algo, sig->digest_start[0], sig->digest_start[1]); - if (is_v4) + if (is_v4or5) { parse_sig_subpkt (sig->hashed, SIGSUBPKT_LIST_HASHED, NULL); parse_sig_subpkt (sig->unhashed, SIGSUBPKT_LIST_UNHASHED, NULL); @@ -2285,6 +2300,9 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, int npkey, nskey; u32 keyid[2]; PKT_public_key *pk; + int is_v5; + unsigned int pkbytes; /* For v5 keys: Number of bytes in the public + * key material. For v4 keys: 0. */ (void) hdr; @@ -2316,12 +2334,13 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, return 0; } else if (version == 4) - { - /* The only supported version. Use an older gpg - version (i.e. gpg 1.4) to parse v3 packets. */ - } + is_v5 = 0; + else if (version == 5) + is_v5 = 1; else if (version == 2 || version == 3) { + /* Not anymore supported since 2.1. Use an older gpg version + * (i.e. gpg 1.4) to parse v3 packets. */ if (opt.verbose > 1) log_info ("packet(%d) with obsolete version %d\n", pkttype, version); if (list_mode) @@ -2339,7 +2358,7 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, goto leave; } - if (pktlen < 11) + if (pktlen < (is_v5? 15:11)) { log_error ("packet(%d) too short\n", pkttype); if (list_mode) @@ -2351,7 +2370,7 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, { log_error ("packet(%d) too large\n", pkttype); if (list_mode) - es_fputs (":key packet: [too larget]\n", listfp); + es_fputs (":key packet: [too large]\n", listfp); err = gpg_error (GPG_ERR_INV_PACKET); goto leave; } @@ -2362,14 +2381,28 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, max_expiredate = 0; algorithm = iobuf_get_noeof (inp); pktlen--; + if (is_v5) + { + pkbytes = read_32 (inp); + pktlen -= 4; + } + else + pkbytes = 0; + if (list_mode) - es_fprintf (listfp, ":%s key packet:\n" - "\tversion %d, algo %d, created %lu, expires %lu\n", - pkttype == PKT_PUBLIC_KEY ? "public" : - pkttype == PKT_SECRET_KEY ? "secret" : - pkttype == PKT_PUBLIC_SUBKEY ? "public sub" : - pkttype == PKT_SECRET_SUBKEY ? "secret sub" : "??", - version, algorithm, timestamp, expiredate); + { + es_fprintf (listfp, ":%s key packet:\n" + "\tversion %d, algo %d, created %lu, expires %lu", + pkttype == PKT_PUBLIC_KEY ? "public" : + pkttype == PKT_SECRET_KEY ? "secret" : + pkttype == PKT_PUBLIC_SUBKEY ? "public sub" : + pkttype == PKT_SECRET_SUBKEY ? "secret sub" : "??", + version, algorithm, timestamp, expiredate); + if (is_v5) + es_fprintf (listfp, ", pkbytes %u\n", pkbytes); + else + es_fprintf (listfp, "\n"); + } pk->timestamp = timestamp; pk->expiredate = expiredate; @@ -2444,6 +2477,7 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, struct seckey_info *ski; byte temp[16]; size_t snlen = 0; + unsigned int skbytes; if (pktlen < 1) { @@ -2460,23 +2494,42 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, ski->algo = iobuf_get_noeof (inp); pktlen--; + + if (is_v5) + { + unsigned int protcount = 0; + + /* Read the one octet count of the following key-protection + * material. Only required in case of unknown values. */ + if (!pktlen) + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + protcount = iobuf_get_noeof (inp); + pktlen--; + if (list_mode) + es_fprintf (listfp, "\tprotbytes: %u\n", protcount); + } + if (ski->algo) { ski->is_protected = 1; ski->s2k.count = 0; if (ski->algo == 254 || ski->algo == 255) { - if (pktlen < 3) + if (pktlen < 3) { err = gpg_error (GPG_ERR_INV_PACKET); goto leave; } - ski->sha1chk = (ski->algo == 254); + + ski->sha1chk = (ski->algo == 254); ski->algo = iobuf_get_noeof (inp); pktlen--; /* Note that a ski->algo > 110 is illegal, but I'm not - erroring on it here as otherwise there would be no - way to delete such a key. */ + * erroring out here as otherwise there would be no way + * to delete such a key. */ ski->s2k.mode = iobuf_get_noeof (inp); pktlen--; ski->s2k.hash_algo = iobuf_get_noeof (inp); @@ -2502,10 +2555,8 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, } /* Read the salt. */ - switch (ski->s2k.mode) + if (ski->s2k.mode == 3 || ski->s2k.mode == 1) { - case 1: - case 3: for (i = 0; i < 8 && pktlen; i++, pktlen--) temp[i] = iobuf_get_noeof (inp); if (i < 8) @@ -2514,7 +2565,6 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, goto leave; } memcpy (ski->s2k.salt, temp, 8); - break; } /* Check the mode. */ @@ -2614,7 +2664,9 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, * ski->ivlen = cipher_get_blocksize (ski->algo); * won't work. The only solution I see is to hardwire it. * NOTE: if you change the ivlen above 16, don't forget to - * enlarge temp. */ + * enlarge temp. + * FIXME: For v5 keys we can deduce this info! + */ ski->ivlen = openpgp_cipher_blocklen (ski->algo); log_assert (ski->ivlen <= sizeof (temp)); @@ -2642,6 +2694,20 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, memcpy (ski->iv, temp, ski->ivlen); } + /* Skip count of secret key material. */ + if (is_v5) + { + if (pktlen < 4) + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + skbytes = read_32 (inp); + pktlen -= 4; + if (list_mode) + es_fprintf (listfp, "\tskbytes: %u\n", skbytes); + } + /* It does not make sense to read it into secure memory. * If the user is so careless, not to protect his secret key, * we can assume, that he operates an open system :=(. @@ -2664,13 +2730,14 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, /* Ugly: The length is encrypted too, so we read all stuff * up to the end of the packet into the first SKEY - * element. */ + * element. + * FIXME: We can do better for v5 keys. */ pk->pkey[npkey] = gcry_mpi_set_opaque (NULL, read_rest (inp, pktlen), pktlen * 8); /* Mark that MPI as protected - we need this information for - importing a key. The OPAQUE flag can't be used because - we also store public EdDSA values in opaque MPIs. */ + * importing a key. The OPAQUE flag can't be used because + * we also store public EdDSA values in opaque MPIs. */ if (pk->pkey[npkey]) gcry_mpi_set_flag (pk->pkey[npkey], GCRYMPI_FLAG_USER1); pktlen = 0; @@ -3180,7 +3247,7 @@ parse_plaintext (IOBUF inp, int pkttype, unsigned long pktlen, pt->name[i] = c; } /* Fill up NAME so that a check with valgrind won't complain about - * reading from uninitalized memory. This case may be triggred by + * reading from uninitialized memory. This case may be triggred by * corrupted packets. */ for (; i < namelen; i++) pt->name[i] = 0; @@ -3507,11 +3574,13 @@ create_gpg_control (ctrlpkttype_t type, const byte * data, size_t datalen) PACKET *packet; byte *p; + if (!data) + datalen = 0; + packet = xmalloc (sizeof *packet); init_packet (packet); packet->pkttype = PKT_GPG_CONTROL; - packet->pkt.gpg_control = xmalloc (sizeof *packet->pkt.gpg_control - + datalen - 1); + packet->pkt.gpg_control = xmalloc (sizeof *packet->pkt.gpg_control + datalen); packet->pkt.gpg_control->control = type; packet->pkt.gpg_control->datalen = datalen; p = packet->pkt.gpg_control->data; diff --git a/g10/passphrase.c b/g10/passphrase.c index 10574ec6a..99a2c0dc2 100644 --- a/g10/passphrase.c +++ b/g10/passphrase.c @@ -48,57 +48,6 @@ static char *next_pw = NULL; static char *last_pw = NULL; - -/* Pack an s2k iteration count into the form specified in 2440. If - we're in between valid values, round up. With value 0 return the - old default. */ -unsigned char -encode_s2k_iterations (int iterations) -{ - gpg_error_t err; - unsigned char c=0; - unsigned char result; - unsigned int count; - - if (!iterations) - { - unsigned long mycnt; - - /* Ask the gpg-agent for a useful iteration count. */ - err = agent_get_s2k_count (&mycnt); - if (err || mycnt < 65536) - { - /* Don't print an error if an older agent is used. */ - if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER) - log_error (_("problem with the agent: %s\n"), gpg_strerror (err)); - /* Default to 65536 which we used up to 2.0.13. */ - return 96; - } - else if (mycnt >= 65011712) - return 255; /* Largest possible value. */ - else - return encode_s2k_iterations ((int)mycnt); - } - - if (iterations <= 1024) - return 0; /* Command line arg compatibility. */ - - if (iterations >= 65011712) - return 255; - - /* Need count to be in the range 16-31 */ - for (count=iterations>>6; count>=32; count>>=1) - c++; - - result = (c<<4)|(count-16); - - if (S2K_DECODE_COUNT(result) < iterations) - result++; - - return result; -} - - int have_static_passphrase() { @@ -106,6 +55,7 @@ have_static_passphrase() && (opt.batch || opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)); } + /* Return a static passphrase. The returned value is only valid as long as no other passphrase related function is called. NULL may be returned if no passphrase has been set; better use @@ -342,7 +292,7 @@ passphrase_to_dek (int cipher_algo, STRING2KEY *s2k, call out to gpg-agent and that should not be done during option processing in main(). */ if (!opt.s2k_count) - opt.s2k_count = encode_s2k_iterations (0); + opt.s2k_count = encode_s2k_iterations (agent_get_s2k_count ()); s2k->count = opt.s2k_count; } } diff --git a/g10/pubkey-enc.c b/g10/pubkey-enc.c index 32b1ed08b..055c39b8f 100644 --- a/g10/pubkey-enc.c +++ b/g10/pubkey-enc.c @@ -110,6 +110,16 @@ get_session_key (ctrl_t ctrl, struct pubkey_enc_list *list, DEK *dek) continue; } + /* FIXME: The list needs to be sorted so that we try the keys in + * an appropriate order. For example: + * - On-disk keys w/o protection + * - On-disk keys with a cached passphrase + * - On-card keys of an active card + * - On-disk keys with protection + * - On-card keys from cards which are not plugged it. Here a + * cancel-all button should stop asking for other cards. + * Without any anonymous keys the sorting can be skipped. + */ for (k = list; k; k = k->next) { if (!(k->pubkey_algo == PUBKEY_ALGO_ELGAMAL_E @@ -122,6 +132,8 @@ get_session_key (ctrl_t ctrl, struct pubkey_enc_list *list, DEK *dek) if (openpgp_pk_test_algo2 (k->pubkey_algo, PUBKEY_USAGE_ENC)) continue; + k->result = GPG_ERR_NO_SECKEY; + if (sk->pubkey_algo != k->pubkey_algo) continue; @@ -129,6 +141,9 @@ get_session_key (ctrl_t ctrl, struct pubkey_enc_list *list, DEK *dek) if (!k->keyid[0] && !k->keyid[1]) { + if (opt.skip_hidden_recipients) + continue; + if (!opt.quiet) log_info (_("anonymous recipient; trying secret key %s ...\n"), keystr (keyid)); @@ -142,6 +157,7 @@ get_session_key (ctrl_t ctrl, struct pubkey_enc_list *list, DEK *dek) rc = get_it (ctrl, k, dek, sk, keyid); if (!rc) { + k->result = 0; if (!opt.quiet && !k->keyid[0] && !k->keyid[1]) log_info (_("okay, we are the anonymous recipient.\n")); search_for_secret_keys = 0; diff --git a/g10/revoke.c b/g10/revoke.c index b778684b0..e8ce3544c 100644 --- a/g10/revoke.c +++ b/g10/revoke.c @@ -277,12 +277,12 @@ gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr) fingerprint_from_pk (list->pk, fpr, &fprlen); - /* Don't get involved with keys that don't have 160 - bit fingerprints */ - if(fprlen!=20) + /* Don't get involved with keys that don't have a v4 + * or v5 fingerprint */ + if (fprlen != 20 && fprlen != 32) continue; - if(memcmp(fpr,pk->revkey[i].fpr,20)==0) + if (!memcmp(fpr,pk->revkey[i].fpr, fprlen)) break; } @@ -295,7 +295,7 @@ gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr) { pk2 = xmalloc_clear (sizeof *pk2); rc = get_pubkey_byfprint (ctrl, pk2, NULL, - pk->revkey[i].fpr, MAX_FINGERPRINT_LEN); + pk->revkey[i].fpr, pk->revkey[i].fprlen); } /* We have the revocation key. */ @@ -388,15 +388,18 @@ gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr) for(j=0;j<signode->pkt->pkt.signature->numrevkeys;j++) { - if(pk->revkey[i].class== - signode->pkt->pkt.signature->revkey[j].class && - pk->revkey[i].algid== - signode->pkt->pkt.signature->revkey[j].algid && - memcmp(pk->revkey[i].fpr, - signode->pkt->pkt.signature->revkey[j].fpr, - MAX_FINGERPRINT_LEN)==0) + if (pk->revkey[i].class + == signode->pkt->pkt.signature->revkey[j].class + && pk->revkey[i].algid + == signode->pkt->pkt.signature->revkey[j].algid + && pk->revkey[i].fprlen + == signode->pkt->pkt.signature->revkey[j].fprlen + && !memcmp + (pk->revkey[i].fpr, + signode->pkt->pkt.signature->revkey[j].fpr, + pk->revkey[i].fprlen)) { - revkey=signode->pkt->pkt.signature; + revkey = signode->pkt->pkt.signature; break; } } diff --git a/g10/seskey.c b/g10/seskey.c index 15490179d..fb71ad5cd 100644 --- a/g10/seskey.c +++ b/g10/seskey.c @@ -95,7 +95,7 @@ encode_session_key (int openpgp_pk_algo, DEK *dek, unsigned int nbits) output be a multiple of 8 bytes. */ if (openpgp_pk_algo == PUBKEY_ALGO_ECDH) { - /* Pad to 8 byte granulatiry; the padding byte is the number of + /* Pad to 8 byte granularity; the padding byte is the number of * padded bytes. * * A DEK(k bytes) CSUM(2 bytes) 0x 0x 0x 0x ... 0x @@ -143,7 +143,7 @@ encode_session_key (int openpgp_pk_algo, DEK *dek, unsigned int nbits) * * 0 2 RND(i bytes) 0 A DEK(k bytes) CSUM(2 bytes) * - * (But how can we store the leading 0 - the external representaion + * (But how can we store the leading 0 - the external representation * of MPIs doesn't allow leading zeroes =:-) * * RND are (at least 1) non-zero random bytes. diff --git a/g10/sig-check.c b/g10/sig-check.c index 0ec384347..e7f97de65 100644 --- a/g10/sig-check.c +++ b/g10/sig-check.c @@ -37,11 +37,14 @@ static int check_signature_end (PKT_public_key *pk, PKT_signature *sig, gcry_md_hd_t digest, + const void *extrahash, size_t extrahashlen, int *r_expired, int *r_revoked, PKT_public_key *ret_pk); static int check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, - gcry_md_hd_t digest); + gcry_md_hd_t digest, + const void *extrahash, + size_t extrahashlen); /* Statistics for signature verification. */ @@ -69,7 +72,7 @@ sig_check_dump_stats (void) int check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest) { - return check_signature2 (ctrl, sig, digest, NULL, NULL, NULL, NULL); + return check_signature2 (ctrl, sig, digest, NULL, 0, NULL, NULL, NULL, NULL); } @@ -95,6 +98,9 @@ check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest) * signature data from the version number through the hashed subpacket * data (inclusive) is hashed.") * + * EXTRAHASH and EXTRAHASHLEN is additional data which is hashed with + * v5 signatures. They may be NULL to use the default. + * * If R_EXPIREDATE is not NULL, R_EXPIREDATE is set to the key's * expiry. * @@ -112,7 +118,9 @@ check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest) * Returns 0 on success. An error code otherwise. */ gpg_error_t check_signature2 (ctrl_t ctrl, - PKT_signature *sig, gcry_md_hd_t digest, u32 *r_expiredate, + PKT_signature *sig, gcry_md_hd_t digest, + const void *extrahash, size_t extrahashlen, + u32 *r_expiredate, int *r_expired, int *r_revoked, PKT_public_key **r_pk) { int rc=0; @@ -179,7 +187,8 @@ check_signature2 (ctrl_t ctrl, if (r_expiredate) *r_expiredate = pk->expiredate; - rc = check_signature_end (pk, sig, digest, r_expired, r_revoked, NULL); + rc = check_signature_end (pk, sig, digest, extrahash, extrahashlen, + r_expired, r_revoked, NULL); /* Check the backsig. This is a back signature (0x19) from * the subkey on the primary key. The idea here is that it @@ -424,6 +433,7 @@ check_signature_metadata_validity (PKT_public_key *pk, PKT_signature *sig, static int check_signature_end (PKT_public_key *pk, PKT_signature *sig, gcry_md_hd_t digest, + const void *extrahash, size_t extrahashlen, int *r_expired, int *r_revoked, PKT_public_key *ret_pk) { int rc = 0; @@ -432,7 +442,8 @@ check_signature_end (PKT_public_key *pk, PKT_signature *sig, r_expired, r_revoked))) return rc; - if ((rc = check_signature_end_simple (pk, sig, digest))) + if ((rc = check_signature_end_simple (pk, sig, digest, + extrahash, extrahashlen))) return rc; if (!rc && ret_pk) @@ -447,7 +458,8 @@ check_signature_end (PKT_public_key *pk, PKT_signature *sig, * expiration, revocation, etc. */ static int check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, - gcry_md_hd_t digest) + gcry_md_hd_t digest, + const void *extrahash, size_t extrahashlen) { gcry_mpi_t result = NULL; int rc = 0; @@ -480,7 +492,8 @@ check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, } /* For data signatures check that the key has sign usage. */ - if (IS_SIG (sig) && !(pk->pubkey_usage & PUBKEY_USAGE_SIG)) + if (!IS_BACK_SIG (sig) && IS_SIG (sig) + && !(pk->pubkey_usage & PUBKEY_USAGE_SIG)) { rc = gpg_error (GPG_ERR_WRONG_KEY_USAGE); if (!opt.quiet) @@ -509,8 +522,10 @@ check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, } else { - byte buf[6]; + byte buf[10]; + int i; size_t n; + gcry_md_putc (digest, sig->pubkey_algo); gcry_md_putc (digest, sig->digest_algo); if (sig->hashed) @@ -529,14 +544,44 @@ check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig, gcry_md_putc (digest, 0); n = 6; } - /* add some magic per Section 5.2.4 of RFC 4880. */ - buf[0] = sig->version; - buf[1] = 0xff; - buf[2] = n >> 24; - buf[3] = n >> 16; - buf[4] = n >> 8; - buf[5] = n; - gcry_md_write( digest, buf, 6 ); + /* Hash data from the literal data packet. */ + if (sig->version >= 5 + && (sig->sig_class == 0x00 || sig->sig_class == 0x01)) + { + /* - One octet content format + * - File name (one octet length followed by the name) + * - Four octet timestamp */ + if (extrahash && extrahashlen) + gcry_md_write (digest, extrahash, extrahashlen); + else /* Detached signature. */ + { + memset (buf, 0, 6); + gcry_md_write (digest, buf, 6); + } + } + /* Add some magic per Section 5.2.4 of RFC 4880. */ + i = 0; + buf[i++] = sig->version; + buf[i++] = 0xff; + if (sig->version >= 5) + { +#if SIZEOF_SIZE_T > 4 + buf[i++] = n >> 56; + buf[i++] = n >> 48; + buf[i++] = n >> 40; + buf[i++] = n >> 32; +#else + buf[i++] = 0; + buf[i++] = 0; + buf[i++] = 0; + buf[i++] = 0; +#endif + } + buf[i++] = n >> 24; + buf[i++] = n >> 16; + buf[i++] = n >> 8; + buf[i++] = n; + gcry_md_write (digest, buf, i); } gcry_md_final( digest ); @@ -571,7 +616,7 @@ hash_uid_packet (PKT_user_id *uid, gcry_md_hd_t md, PKT_signature *sig ) { if (uid->attrib_data) { - if (sig->version >=4) + if (sig->version >= 4) { byte buf[5]; buf[0] = 0xd1; /* packet of type 17 */ @@ -585,7 +630,7 @@ hash_uid_packet (PKT_user_id *uid, gcry_md_hd_t md, PKT_signature *sig ) } else { - if (sig->version >=4) + if (sig->version >= 4) { byte buf[5]; buf[0] = 0xb4; /* indicates a userid packet */ @@ -706,8 +751,8 @@ check_revocation_keys (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig) /* The revoker's keyid. */ u32 keyid[2]; - keyid_from_fingerprint (ctrl, pk->revkey[i].fpr, - MAX_FINGERPRINT_LEN, keyid); + keyid_from_fingerprint (ctrl, pk->revkey[i].fpr, pk->revkey[i].fprlen, + keyid); if(keyid[0]==sig->keyid[0] && keyid[1]==sig->keyid[1]) /* The signature was generated by a designated revoker. @@ -762,7 +807,7 @@ check_backsig (PKT_public_key *main_pk,PKT_public_key *sub_pk, { hash_public_key(md,main_pk); hash_public_key(md,sub_pk); - rc = check_signature_end (sub_pk, backsig, md, NULL, NULL, NULL); + rc = check_signature_end (sub_pk, backsig, md, NULL, 0, NULL, NULL, NULL); cache_sig_result(backsig,rc); gcry_md_close(md); } @@ -949,28 +994,28 @@ check_signature_over_key_or_uid (ctrl_t ctrl, PKT_public_key *signer, { log_assert (packet->pkttype == PKT_PUBLIC_KEY); hash_public_key (md, packet->pkt.public_key); - rc = check_signature_end_simple (signer, sig, md); + rc = check_signature_end_simple (signer, sig, md, NULL, 0); } else if (IS_BACK_SIG (sig)) { log_assert (packet->pkttype == PKT_PUBLIC_KEY); hash_public_key (md, packet->pkt.public_key); hash_public_key (md, signer); - rc = check_signature_end_simple (signer, sig, md); + rc = check_signature_end_simple (signer, sig, md, NULL, 0); } else if (IS_SUBKEY_SIG (sig) || IS_SUBKEY_REV (sig)) { log_assert (packet->pkttype == PKT_PUBLIC_SUBKEY); hash_public_key (md, pripk); hash_public_key (md, packet->pkt.public_key); - rc = check_signature_end_simple (signer, sig, md); + rc = check_signature_end_simple (signer, sig, md, NULL, 0); } else if (IS_UID_SIG (sig) || IS_UID_REV (sig)) { log_assert (packet->pkttype == PKT_USER_ID); hash_public_key (md, pripk); hash_uid_packet (packet->pkt.user_id, md, sig); - rc = check_signature_end_simple (signer, sig, md); + rc = check_signature_end_simple (signer, sig, md, NULL, 0); } else { diff --git a/g10/sign.c b/g10/sign.c index 581a08f5b..176940bff 100644 --- a/g10/sign.c +++ b/g10/sign.c @@ -49,10 +49,23 @@ #define LF "\n" #endif -static int recipient_digest_algo=0; +/* Hack */ +static int recipient_digest_algo; -/**************** - * Create notations and other stuff. It is assumed that the stings in + +/* A type for the extra data we hash into v5 signature packets. */ +struct pt_extra_hash_data_s +{ + unsigned char mode; + u32 timestamp; + unsigned char namelen; + char name[1]; +}; +typedef struct pt_extra_hash_data_s *pt_extra_hash_data_t; + + +/* + * Create notations and other stuff. It is assumed that the strings in * STRLIST are already checked to contain only printable data and have * a valid NAME=VALUE format. */ @@ -152,7 +165,8 @@ mk_notation_policy_etc (PKT_signature *sig, char *mbox; /* For now we use the uid which was used to locate the key. */ - if (pksk->user_id && (mbox = mailbox_from_userid (pksk->user_id->name))) + if (pksk->user_id + && (mbox = mailbox_from_userid (pksk->user_id->name, 0))) { if (DBG_LOOKUP) log_debug ("setting Signer's UID to '%s'\n", mbox); @@ -214,12 +228,16 @@ hash_uid (gcry_md_hd_t md, int sigversion, const PKT_user_id *uid) /* - * Helper to hash some parts from the signature + * Helper to hash some parts from the signature. EXTRAHASH gives the + * extra data to be hashed into v5 signatures; it may by NULL for + * detached signatures. */ static void -hash_sigversion_to_magic (gcry_md_hd_t md, const PKT_signature *sig) +hash_sigversion_to_magic (gcry_md_hd_t md, const PKT_signature *sig, + pt_extra_hash_data_t extrahash) { - byte buf[6]; + byte buf[10]; + int i; size_t n; gcry_md_putc (md, sig->version); @@ -240,14 +258,49 @@ hash_sigversion_to_magic (gcry_md_hd_t md, const PKT_signature *sig) gcry_md_putc (md, 0); n = 6; } + /* Hash data from the literal data packet. */ + if (sig->version >= 5 && (sig->sig_class == 0x00 || sig->sig_class == 0x01)) + { + /* - One octet content format + * - File name (one octet length followed by the name) + * - Four octet timestamp */ + if (extrahash) + { + buf[0] = extrahash->mode; + buf[1] = extrahash->namelen; + gcry_md_write (md, buf, 2); + if (extrahash->namelen) + gcry_md_write (md, extrahash->name, extrahash->namelen); + buf[0] = extrahash->timestamp >> 24; + buf[1] = extrahash->timestamp >> 16; + buf[2] = extrahash->timestamp >> 8; + buf[3] = extrahash->timestamp; + gcry_md_write (md, buf, 4); + } + else /* Detached signatures */ + { + memset (buf, 0, 6); + gcry_md_write (md, buf, 6); + } + } /* Add some magic. */ - buf[0] = sig->version; - buf[1] = 0xff; - buf[2] = n >> 24; /* (n is only 16 bit, so this is always 0) */ - buf[3] = n >> 16; - buf[4] = n >> 8; - buf[5] = n; - gcry_md_write (md, buf, 6); + i = 0; + buf[i++] = sig->version; + buf[i++] = 0xff; + if (sig->version >= 5) + { + /* Note: We don't hashed any data larger than 2^32 and thus we + * can always use 0 here. See also note below. */ + buf[i++] = 0; + buf[i++] = 0; + buf[i++] = 0; + buf[i++] = 0; + } + buf[i++] = n >> 24; /* (n is only 16 bit, so this is always 0) */ + buf[i++] = n >> 16; + buf[i++] = n >> 8; + buf[i++] = n; + gcry_md_write (md, buf, i); } @@ -574,136 +627,173 @@ print_status_sig_created (PKT_public_key *pk, PKT_signature *sig, int what) * Loop over the secret certificates in SK_LIST and build the one pass * signature packets. OpenPGP says that the data should be bracket by * the onepass-sig and signature-packet; so we build these onepass - * packet here in reverse order + * packet here in reverse order. */ static int write_onepass_sig_packets (SK_LIST sk_list, IOBUF out, int sigclass ) { - int skcount; - SK_LIST sk_rover; + int skcount; + SK_LIST sk_rover; - for (skcount=0, sk_rover=sk_list; sk_rover; sk_rover = sk_rover->next) - skcount++; + for (skcount=0, sk_rover=sk_list; sk_rover; sk_rover = sk_rover->next) + skcount++; - for (; skcount; skcount--) { - PKT_public_key *pk; - PKT_onepass_sig *ops; - PACKET pkt; - int i, rc; + for (; skcount; skcount--) + { + PKT_public_key *pk; + PKT_onepass_sig *ops; + PACKET pkt; + int i, rc; - for (i=0, sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { - if (++i == skcount) - break; - } + for (i=0, sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) + if (++i == skcount) + break; - pk = sk_rover->pk; - ops = xmalloc_clear (sizeof *ops); - ops->sig_class = sigclass; - ops->digest_algo = hash_for (pk); - ops->pubkey_algo = pk->pubkey_algo; - keyid_from_pk (pk, ops->keyid); - ops->last = (skcount == 1); - - init_packet(&pkt); - pkt.pkttype = PKT_ONEPASS_SIG; - pkt.pkt.onepass_sig = ops; - rc = build_packet (out, &pkt); - free_packet (&pkt, NULL); - if (rc) { - log_error ("build onepass_sig packet failed: %s\n", - gpg_strerror (rc)); - return rc; + pk = sk_rover->pk; + ops = xmalloc_clear (sizeof *ops); + ops->sig_class = sigclass; + ops->digest_algo = hash_for (pk); + ops->pubkey_algo = pk->pubkey_algo; + keyid_from_pk (pk, ops->keyid); + ops->last = (skcount == 1); + + init_packet (&pkt); + pkt.pkttype = PKT_ONEPASS_SIG; + pkt.pkt.onepass_sig = ops; + rc = build_packet (out, &pkt); + free_packet (&pkt, NULL); + if (rc) + { + log_error ("build onepass_sig packet failed: %s\n", + gpg_strerror (rc)); + return rc; } } - return 0; + return 0; } + /* - * Helper to write the plaintext (literal data) packet + * Helper to write the plaintext (literal data) packet. At + * R_EXTRAHASH a malloced object with the with the extra data hashed + * into v5 signatures is stored. */ static int -write_plaintext_packet (IOBUF out, IOBUF inp, const char *fname, int ptmode) +write_plaintext_packet (iobuf_t out, iobuf_t inp, + const char *fname, int ptmode, + pt_extra_hash_data_t *r_extrahash) { - PKT_plaintext *pt = NULL; - u32 filesize; - int rc = 0; + PKT_plaintext *pt = NULL; + u32 filesize; + int rc = 0; - if (!opt.no_literal) - pt=setup_plaintext_name(fname,inp); + if (!opt.no_literal) + pt = setup_plaintext_name (fname, inp); - /* try to calculate the length of the data */ - if ( !iobuf_is_pipe_filename (fname) && *fname ) - { - off_t tmpsize; - int overflow; - - if( !(tmpsize = iobuf_get_filelength(inp, &overflow)) - && !overflow && opt.verbose) - log_info (_("WARNING: '%s' is an empty file\n"), fname); - - /* We can't encode the length of very large files because - OpenPGP uses only 32 bit for file sizes. So if the size of - a file is larger than 2^32 minus some bytes for packet - headers, we switch to partial length encoding. */ - if ( tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536) ) - filesize = tmpsize; - else - filesize = 0; - - /* Because the text_filter modifies the length of the - * data, it is not possible to know the used length - * without a double read of the file - to avoid that - * we simple use partial length packets. */ - if ( ptmode == 't' || ptmode == 'u' || ptmode == 'm') - filesize = 0; - } - else - filesize = opt.set_filesize? opt.set_filesize : 0; /* stdin */ - - if (!opt.no_literal) { - PACKET pkt; - - /* Note that PT has been initialized above in no_literal mode. */ - pt->timestamp = make_timestamp (); - pt->mode = ptmode; - pt->len = filesize; - pt->new_ctb = !pt->len; - pt->buf = inp; - init_packet(&pkt); - pkt.pkttype = PKT_PLAINTEXT; - pkt.pkt.plaintext = pt; - /*cfx.datalen = filesize? calc_packet_length( &pkt ) : 0;*/ - if( (rc = build_packet (out, &pkt)) ) - log_error ("build_packet(PLAINTEXT) failed: %s\n", - gpg_strerror (rc) ); - pt->buf = NULL; - free_packet (&pkt, NULL); + /* Try to calculate the length of the data. */ + if ( !iobuf_is_pipe_filename (fname) && *fname) + { + off_t tmpsize; + int overflow; + + if (!(tmpsize = iobuf_get_filelength (inp, &overflow)) + && !overflow && opt.verbose) + log_info (_("WARNING: '%s' is an empty file\n"), fname); + + /* We can't encode the length of very large files because + * OpenPGP uses only 32 bit for file sizes. So if the size of a + * file is larger than 2^32 minus some bytes for packet headers, + * we switch to partial length encoding. */ + if (tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536)) + filesize = tmpsize; + else + filesize = 0; + + /* Because the text_filter modifies the length of the + * data, it is not possible to know the used length + * without a double read of the file - to avoid that + * we simple use partial length packets. */ + if (ptmode == 't' || ptmode == 'u' || ptmode == 'm') + filesize = 0; } - else { - byte copy_buffer[4096]; - int bytes_copied; - - while ((bytes_copied = iobuf_read(inp, copy_buffer, 4096)) != -1) - if ( (rc=iobuf_write(out, copy_buffer, bytes_copied)) ) { - log_error ("copying input to output failed: %s\n", - gpg_strerror (rc)); - break; - } - wipememory(copy_buffer,4096); /* burn buffer */ + else + filesize = opt.set_filesize? opt.set_filesize : 0; /* stdin */ + + if (!opt.no_literal) + { + PACKET pkt; + + /* Note that PT has been initialized above in no_literal mode. */ + pt->timestamp = make_timestamp (); + pt->mode = ptmode; + pt->len = filesize; + pt->new_ctb = !pt->len; + pt->buf = inp; + init_packet (&pkt); + pkt.pkttype = PKT_PLAINTEXT; + pkt.pkt.plaintext = pt; + /*cfx.datalen = filesize? calc_packet_length( &pkt ) : 0;*/ + if ((rc = build_packet (out, &pkt))) + log_error ("build_packet(PLAINTEXT) failed: %s\n", + gpg_strerror (rc) ); + + *r_extrahash = xtrymalloc (sizeof **r_extrahash + pt->namelen); + if (!*r_extrahash) + rc = gpg_error_from_syserror (); + else + { + (*r_extrahash)->mode = pt->mode; + (*r_extrahash)->timestamp = pt->timestamp; + (*r_extrahash)->namelen = pt->namelen; + /* Note that the last byte of NAME won't be initialized + * because we don't need it. */ + memcpy ((*r_extrahash)->name, pt->name, pt->namelen); + } + pt->buf = NULL; + free_packet (&pkt, NULL); } - /* fixme: it seems that we never freed pt/pkt */ + else + { + byte copy_buffer[4096]; + int bytes_copied; - return rc; + *r_extrahash = xtrymalloc (sizeof **r_extrahash); + if (!*r_extrahash) + { + rc = gpg_error_from_syserror (); + goto leave; + } + /* FIXME: We need to parse INP to get the to be hashed data from + * it. */ + (*r_extrahash)->mode = 0; + (*r_extrahash)->timestamp = 0; + (*r_extrahash)->namelen = 0; + + while ((bytes_copied = iobuf_read (inp, copy_buffer, 4096)) != -1) + if ((rc = iobuf_write (out, copy_buffer, bytes_copied))) + { + log_error ("copying input to output failed: %s\n", + gpg_strerror (rc)); + break; + } + wipememory (copy_buffer, 4096); /* burn buffer */ + } + + leave: + return rc; } + /* - * Write the signatures from the SK_LIST to OUT. HASH must be a non-finalized - * hash which will not be changes here. + * Write the signatures from the SK_LIST to OUT. HASH must be a + * non-finalized hash which will not be changes here. EXTRAHASH is + * either NULL or the extra data tro be hashed into v5 signatures. */ static int write_signature_packets (ctrl_t ctrl, SK_LIST sk_list, IOBUF out, gcry_md_hd_t hash, + pt_extra_hash_data_t extrahash, int sigclass, u32 timestamp, u32 duration, int status_letter, const char *cache_nonce) { @@ -724,11 +814,10 @@ write_signature_packets (ctrl_t ctrl, if (!sig) return gpg_error_from_syserror (); - if (duration || opt.sig_policy_url - || opt.sig_notations || opt.sig_keyserver_url) - sig->version = 4; + if (pk->version >= 5) + sig->version = 5; /* Required for v5 keys. */ else - sig->version = pk->version; + sig->version = 4; /* Required. */ keyid_from_pk (pk, sig->keyid); sig->digest_algo = hash_for (pk); @@ -744,13 +833,9 @@ write_signature_packets (ctrl_t ctrl, if (gcry_md_copy (&md, hash)) BUG (); - if (sig->version >= 4) - { - build_sig_subpkt_from_sig (sig, pk); - mk_notation_policy_etc (sig, NULL, pk); - } - - hash_sigversion_to_magic (md, sig); + build_sig_subpkt_from_sig (sig, pk); + mk_notation_policy_etc (sig, NULL, pk); + hash_sigversion_to_magic (md, sig, extrahash); gcry_md_final (md); rc = do_sign (ctrl, pk, sig, md, hash_for (pk), cache_nonce); @@ -782,7 +867,7 @@ write_signature_packets (ctrl_t ctrl, } -/**************** +/* * Sign the files whose names are in FILENAME. * If DETACHED has the value true, * make a detached signature. If FILENAMES->d is NULL read from stdin @@ -798,62 +883,66 @@ int sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr, int encryptflag, strlist_t remusr, const char *outfile ) { - const char *fname; - armor_filter_context_t *afx; - compress_filter_context_t zfx; - md_filter_context_t mfx; - text_filter_context_t tfx; - progress_filter_context_t *pfx; - encrypt_filter_context_t efx; - IOBUF inp = NULL, out = NULL; - PACKET pkt; - int rc = 0; - PK_LIST pk_list = NULL; - SK_LIST sk_list = NULL; - SK_LIST sk_rover = NULL; - int multifile = 0; - u32 duration=0; - - pfx = new_progress_context (); - afx = new_armor_context (); - memset( &zfx, 0, sizeof zfx); - memset( &mfx, 0, sizeof mfx); - memset( &efx, 0, sizeof efx); - efx.ctrl = ctrl; - init_packet( &pkt ); - - if( filenames ) { - fname = filenames->d; - multifile = !!filenames->next; + const char *fname; + armor_filter_context_t *afx; + compress_filter_context_t zfx; + md_filter_context_t mfx; + text_filter_context_t tfx; + progress_filter_context_t *pfx; + encrypt_filter_context_t efx; + iobuf_t inp = NULL; + iobuf_t out = NULL; + PACKET pkt; + int rc = 0; + PK_LIST pk_list = NULL; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + int multifile = 0; + u32 duration=0; + pt_extra_hash_data_t extrahash = NULL; + + pfx = new_progress_context (); + afx = new_armor_context (); + memset (&zfx, 0, sizeof zfx); + memset (&mfx, 0, sizeof mfx); + memset (&efx, 0, sizeof efx); + efx.ctrl = ctrl; + init_packet (&pkt); + + if (filenames) + { + fname = filenames->d; + multifile = !!filenames->next; } - else - fname = NULL; + else + fname = NULL; - if( fname && filenames->next && (!detached || encryptflag) ) - log_bug("multiple files can only be detached signed"); + if (fname && filenames->next && (!detached || encryptflag)) + log_bug ("multiple files can only be detached signed"); - if(encryptflag==2 - && (rc=setup_symkey(&efx.symkey_s2k,&efx.symkey_dek))) - goto leave; + if (encryptflag == 2 + && (rc = setup_symkey (&efx.symkey_s2k, &efx.symkey_dek))) + goto leave; - if (opt.ask_sig_expire && !opt.batch) - duration = ask_expire_interval(1,opt.def_sig_expire); - else - duration = parse_expire_string(opt.def_sig_expire); + if (opt.ask_sig_expire && !opt.batch) + duration = ask_expire_interval(1,opt.def_sig_expire); + else + duration = parse_expire_string(opt.def_sig_expire); - /* Note: In the old non-agent version the following call used to - unprotect the secret key. This is now done on demand by the agent. */ - if( (rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG )) ) - goto leave; + /* Note: In the old non-agent version the following call used to + * unprotect the secret key. This is now done on demand by the agent. */ + if ((rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG ))) + goto leave; - if (encryptflag - && (rc=build_pk_list (ctrl, remusr, &pk_list))) - goto leave; + if (encryptflag + && (rc = build_pk_list (ctrl, remusr, &pk_list))) + goto leave; - /* prepare iobufs */ - if( multifile ) /* have list of filenames */ - inp = NULL; /* we do it later */ - else { + /* Prepare iobufs. */ + if (multifile) /* have list of filenames */ + inp = NULL; /* we do it later */ + else + { inp = iobuf_open(fname); if (inp && is_secured_file (iobuf_get_fd (inp))) { @@ -861,407 +950,437 @@ sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr, inp = NULL; gpg_err_set_errno (EPERM); } - if( !inp ) + if (!inp) { rc = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), fname? fname: "[stdin]", - strerror(errno) ); + strerror (errno)); goto leave; } - handle_progress (pfx, inp, fname); + handle_progress (pfx, inp, fname); } - if( outfile ) { - if (is_secured_filename ( outfile )) { - out = NULL; - gpg_err_set_errno (EPERM); + if (outfile) + { + if (is_secured_filename (outfile)) + { + out = NULL; + gpg_err_set_errno (EPERM); } - else - out = iobuf_create (outfile, 0); - if( !out ) - { - rc = gpg_error_from_syserror (); - log_error(_("can't create '%s': %s\n"), outfile, strerror(errno) ); - goto leave; - } - else if( opt.verbose ) - log_info(_("writing to '%s'\n"), outfile ); + else + out = iobuf_create (outfile, 0); + if (!out) + { + rc = gpg_error_from_syserror (); + log_error (_("can't create '%s': %s\n"), outfile, gpg_strerror (rc)); + goto leave; + } + else if (opt.verbose) + log_info (_("writing to '%s'\n"), outfile); + } + else if ((rc = open_outfile (-1, fname, + opt.armor? 1 : detached? 2 : 0, 0, &out))) + { + goto leave; } - else if( (rc = open_outfile (-1, fname, - opt.armor? 1: detached? 2:0, 0, &out))) - goto leave; - /* prepare to calculate the MD over the input */ - if( opt.textmode && !outfile && !multifile ) - { - memset( &tfx, 0, sizeof tfx); - iobuf_push_filter( inp, text_filter, &tfx ); - } + /* Prepare to calculate the MD over the input. */ + if (opt.textmode && !outfile && !multifile) + { + memset (&tfx, 0, sizeof tfx); + iobuf_push_filter (inp, text_filter, &tfx); + } - if ( gcry_md_open (&mfx.md, 0, 0) ) - BUG (); - if (DBG_HASHING) - gcry_md_debug (mfx.md, "sign"); - - /* If we're encrypting and signing, it is reasonable to pick the - hash algorithm to use out of the recipient key prefs. This is - best effort only, as in a DSA2 and smartcard world there are - cases where we cannot please everyone with a single hash (DSA2 - wants >160 and smartcards want =160). In the future this could - be more complex with different hashes for each sk, but the - current design requires a single hash for all SKs. */ - if(pk_list) - { - if(opt.def_digest_algo) - { - if(!opt.expert && - select_algo_from_prefs(pk_list,PREFTYPE_HASH, - opt.def_digest_algo, - NULL)!=opt.def_digest_algo) - log_info(_("WARNING: forcing digest algorithm %s (%d)" - " violates recipient preferences\n"), - gcry_md_algo_name (opt.def_digest_algo), - opt.def_digest_algo ); - } - else - { - int algo, smartcard=0; - union pref_hint hint; - - hint.digest_length = 0; - - /* Of course, if the recipient asks for something - unreasonable (like the wrong hash for a DSA key) then - don't do it. Check all sk's - if any are DSA or live - on a smartcard, then the hash has restrictions and we - may not be able to give the recipient what they want. - For DSA, pass a hint for the largest q we have. Note - that this means that a q>160 key will override a q=160 - key and force the use of truncation for the q=160 key. - The alternative would be to ignore the recipient prefs - completely and get a different hash for each DSA key in - hash_for(). The override behavior here is more or less - reasonable as it is under the control of the user which - keys they sign with for a given message and the fact - that the message with multiple signatures won't be - usable on an implementation that doesn't understand - DSA2 anyway. */ - - for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) - { - if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_DSA - || sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA) - { - int temp_hashlen = (gcry_mpi_get_nbits - (sk_rover->pk->pkey[1])); - - if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA) - temp_hashlen = ecdsa_qbits_from_Q (temp_hashlen); - temp_hashlen = (temp_hashlen+7)/8; - - /* Pick a hash that is large enough for our - largest q */ - - if (hint.digest_length<temp_hashlen) - hint.digest_length=temp_hashlen; - } - /* FIXME: need to check gpg-agent for this. */ - /* else if (sk_rover->pk->is_protected */ - /* && sk_rover->pk->protect.s2k.mode == 1002) */ - /* smartcard = 1; */ - } - - /* Current smartcards only do 160-bit hashes. If we have - to have a >160-bit hash, then we can't use the - recipient prefs as we'd need both =160 and >160 at the - same time and recipient prefs currently require a - single hash for all signatures. All this may well have - to change as the cards add algorithms. */ - - if (!smartcard || (smartcard && hint.digest_length==20)) - if ( (algo= - select_algo_from_prefs(pk_list,PREFTYPE_HASH,-1,&hint)) > 0) - recipient_digest_algo=algo; - } - } + if (gcry_md_open (&mfx.md, 0, 0)) + BUG (); + if (DBG_HASHING) + gcry_md_debug (mfx.md, "sign"); + + /* If we're encrypting and signing, it is reasonable to pick the + * hash algorithm to use out of the recipient key prefs. This is + * best effort only, as in a DSA2 and smartcard world there are + * cases where we cannot please everyone with a single hash (DSA2 + * wants >160 and smartcards want =160). In the future this could + * be more complex with different hashes for each sk, but the + * current design requires a single hash for all SKs. */ + if (pk_list) + { + if (opt.def_digest_algo) + { + if (!opt.expert + && select_algo_from_prefs (pk_list,PREFTYPE_HASH, + opt.def_digest_algo, + NULL) != opt.def_digest_algo) + { + log_info (_("WARNING: forcing digest algorithm %s (%d)" + " violates recipient preferences\n"), + gcry_md_algo_name (opt.def_digest_algo), + opt.def_digest_algo); + } + } + else + { + int algo; + int smartcard=0; + union pref_hint hint; + + hint.digest_length = 0; + + /* Of course, if the recipient asks for something + * unreasonable (like the wrong hash for a DSA key) then + * don't do it. Check all sk's - if any are DSA or live + * on a smartcard, then the hash has restrictions and we + * may not be able to give the recipient what they want. + * For DSA, pass a hint for the largest q we have. Note + * that this means that a q>160 key will override a q=160 + * key and force the use of truncation for the q=160 key. + * The alternative would be to ignore the recipient prefs + * completely and get a different hash for each DSA key in + * hash_for(). The override behavior here is more or less + * reasonable as it is under the control of the user which + * keys they sign with for a given message and the fact + * that the message with multiple signatures won't be + * usable on an implementation that doesn't understand + * DSA2 anyway. */ + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) + { + if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_DSA + || sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA) + { + int temp_hashlen = gcry_mpi_get_nbits (sk_rover->pk->pkey[1]); + + if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA) + temp_hashlen = ecdsa_qbits_from_Q (temp_hashlen); + + temp_hashlen = (temp_hashlen+7)/8; + + /* Pick a hash that is large enough for our largest Q */ + if (hint.digest_length < temp_hashlen) + hint.digest_length = temp_hashlen; + } + /* FIXME: need to check gpg-agent for this. */ + /* else if (sk_rover->pk->is_protected */ + /* && sk_rover->pk->protect.s2k.mode == 1002) */ + /* smartcard = 1; */ + } - for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) - gcry_md_enable (mfx.md, hash_for (sk_rover->pk)); + /* Current smartcards only do 160-bit hashes. If we have + * to have a >160-bit hash, then we can't use the + * recipient prefs as we'd need both =160 and >160 at the + * same time and recipient prefs currently require a + * single hash for all signatures. All this may well have + * to change as the cards add algorithms. */ + if ((!smartcard || (smartcard && hint.digest_length==20)) + && ((algo = select_algo_from_prefs (pk_list, PREFTYPE_HASH, + -1, &hint)) > 0)) + { + recipient_digest_algo = algo; + } + } + } + + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) + gcry_md_enable (mfx.md, hash_for (sk_rover->pk)); - if( !multifile ) - iobuf_push_filter( inp, md_filter, &mfx ); + if (!multifile) + iobuf_push_filter (inp, md_filter, &mfx); - if( detached && !encryptflag) - afx->what = 2; + if (detached && !encryptflag) + afx->what = 2; - if( opt.armor && !outfile ) - push_armor_filter (afx, out); + if (opt.armor && !outfile) + push_armor_filter (afx, out); - if( encryptflag ) { - efx.pk_list = pk_list; - /* fixme: set efx.cfx.datalen if known */ - iobuf_push_filter( out, encrypt_filter, &efx ); + if (encryptflag) + { + efx.pk_list = pk_list; + /* fixme: set efx.cfx.datalen if known */ + iobuf_push_filter (out, encrypt_filter, &efx); } - if (opt.compress_algo && !outfile && !detached) - { - int compr_algo=opt.compress_algo; - - /* If not forced by user */ - if(compr_algo==-1) - { - /* If we're not encrypting, then select_algo_from_prefs - will fail and we'll end up with the default. If we are - encrypting, select_algo_from_prefs cannot fail since - there is an assumed preference for uncompressed data. - Still, if it did fail, we'll also end up with the - default. */ - - if((compr_algo= - select_algo_from_prefs(pk_list,PREFTYPE_ZIP,-1,NULL))==-1) - compr_algo=default_compress_algo(); - } - else if(!opt.expert && pk_list - && select_algo_from_prefs(pk_list,PREFTYPE_ZIP, - compr_algo,NULL)!=compr_algo) - log_info(_("WARNING: forcing compression algorithm %s (%d)" - " violates recipient preferences\n"), - compress_algo_to_string(compr_algo),compr_algo); - - /* algo 0 means no compression */ - if( compr_algo ) - push_compress_filter(out,&zfx,compr_algo); - } + if (opt.compress_algo && !outfile && !detached) + { + int compr_algo = opt.compress_algo; - /* Write the one-pass signature packets if needed */ - if (!detached) { - rc = write_onepass_sig_packets (sk_list, out, - opt.textmode && !outfile ? 0x01:0x00); - if (rc) - goto leave; + /* If not forced by user */ + if (compr_algo==-1) + { + /* If we're not encrypting, then select_algo_from_prefs + * will fail and we'll end up with the default. If we are + * encrypting, select_algo_from_prefs cannot fail since + * there is an assumed preference for uncompressed data. + * Still, if it did fail, we'll also end up with the + * default. */ + if ((compr_algo = select_algo_from_prefs (pk_list, PREFTYPE_ZIP, + -1, NULL)) == -1) + { + compr_algo = default_compress_algo(); + } + } + else if (!opt.expert && pk_list + && select_algo_from_prefs (pk_list, PREFTYPE_ZIP, + compr_algo, NULL) != compr_algo) + { + log_info (_("WARNING: forcing compression algorithm %s (%d)" + " violates recipient preferences\n"), + compress_algo_to_string (compr_algo), compr_algo); + } + + /* Algo 0 means no compression. */ + if (compr_algo) + push_compress_filter (out, &zfx, compr_algo); + } + + /* Write the one-pass signature packets if needed */ + if (!detached) + { + rc = write_onepass_sig_packets (sk_list, out, + opt.textmode && !outfile ? 0x01:0x00); + if (rc) + goto leave; } - write_status_begin_signing (mfx.md); - - /* Setup the inner packet. */ - if( detached ) { - if( multifile ) { - strlist_t sl; - - if( opt.verbose ) - log_info(_("signing:") ); - /* must walk reverse trough this list */ - for( sl = strlist_last(filenames); sl; - sl = strlist_prev( filenames, sl ) ) { - inp = iobuf_open(sl->d); - if (inp && is_secured_file (iobuf_get_fd (inp))) - { - iobuf_close (inp); - inp = NULL; - gpg_err_set_errno (EPERM); - } - if( !inp ) - { - rc = gpg_error_from_syserror (); - log_error(_("can't open '%s': %s\n"), - sl->d,strerror(errno)); - goto leave; - } - handle_progress (pfx, inp, sl->d); - if( opt.verbose ) - log_printf (" '%s'", sl->d ); - if(opt.textmode) - { - memset( &tfx, 0, sizeof tfx); - iobuf_push_filter( inp, text_filter, &tfx ); - } - iobuf_push_filter( inp, md_filter, &mfx ); - while( iobuf_get(inp) != -1 ) - ; - iobuf_close(inp); inp = NULL; + write_status_begin_signing (mfx.md); + + /* Setup the inner packet. */ + if (detached) + { + if (multifile) + { + strlist_t sl; + + if (opt.verbose) + log_info (_("signing:") ); + /* Must walk reverse trough this list. */ + for (sl = strlist_last(filenames); + sl; + sl = strlist_prev( filenames, sl)) + { + inp = iobuf_open (sl->d); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + gpg_err_set_errno (EPERM); + } + if (!inp) + { + rc = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), + sl->d, gpg_strerror (rc)); + goto leave; + } + handle_progress (pfx, inp, sl->d); + if (opt.verbose) + log_printf (" '%s'", sl->d ); + if (opt.textmode) + { + memset (&tfx, 0, sizeof tfx); + iobuf_push_filter (inp, text_filter, &tfx); + } + iobuf_push_filter (inp, md_filter, &mfx); + while (iobuf_get (inp) != -1) + ; + iobuf_close (inp); + inp = NULL; } - if( opt.verbose ) - log_printf ("\n"); + if (opt.verbose) + log_printf ("\n"); } - else { - /* read, so that the filter can calculate the digest */ - while( iobuf_get(inp) != -1 ) - ; + else + { + /* Read, so that the filter can calculate the digest. */ + while (iobuf_get(inp) != -1) + ; } } - else { - rc = write_plaintext_packet (out, inp, fname, - opt.textmode && !outfile ? - (opt.mimemode? 'm':'t'):'b'); + else + { + rc = write_plaintext_packet (out, inp, fname, + (opt.textmode && !outfile) ? + (opt.mimemode? 'm' : 't') : 'b', + &extrahash); } - /* catch errors from above */ - if (rc) - goto leave; + /* Catch errors from above. */ + if (rc) + goto leave; - /* write the signatures */ - rc = write_signature_packets (ctrl, sk_list, out, mfx.md, - opt.textmode && !outfile? 0x01 : 0x00, - 0, duration, detached ? 'D':'S', NULL); - if( rc ) - goto leave; + /* Write the signatures. */ + rc = write_signature_packets (ctrl, sk_list, out, mfx.md, extrahash, + opt.textmode && !outfile? 0x01 : 0x00, + 0, duration, detached ? 'D':'S', NULL); + if (rc) + goto leave; - leave: - if( rc ) - iobuf_cancel(out); - else { - iobuf_close(out); - if (encryptflag) - write_status( STATUS_END_ENCRYPTION ); + leave: + if (rc) + iobuf_cancel (out); + else + { + iobuf_close (out); + if (encryptflag) + write_status (STATUS_END_ENCRYPTION); } - iobuf_close(inp); - gcry_md_close ( mfx.md ); - release_sk_list( sk_list ); - release_pk_list( pk_list ); - recipient_digest_algo=0; - release_progress_context (pfx); - release_armor_context (afx); - return rc; + iobuf_close (inp); + gcry_md_close (mfx.md); + release_sk_list (sk_list); + release_pk_list (pk_list); + recipient_digest_algo = 0; + release_progress_context (pfx); + release_armor_context (afx); + xfree (extrahash); + return rc; } - -/**************** - * make a clear signature. note that opt.armor is not needed +/* + * Make a clear signature. Note that opt.armor is not needed. */ int clearsign_file (ctrl_t ctrl, - const char *fname, strlist_t locusr, const char *outfile ) + const char *fname, strlist_t locusr, const char *outfile) { - armor_filter_context_t *afx; - progress_filter_context_t *pfx; - gcry_md_hd_t textmd = NULL; - IOBUF inp = NULL, out = NULL; - PACKET pkt; - int rc = 0; - SK_LIST sk_list = NULL; - SK_LIST sk_rover = NULL; - u32 duration=0; - - pfx = new_progress_context (); - afx = new_armor_context (); - init_packet( &pkt ); - - if (opt.ask_sig_expire && !opt.batch) - duration = ask_expire_interval (1,opt.def_sig_expire); - else - duration = parse_expire_string (opt.def_sig_expire); - - /* Note: In the old non-agent version the following call used to - unprotect the secret key. This is now done on demand by the agent. */ - if( (rc=build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG )) ) - goto leave; - - /* prepare iobufs */ - inp = iobuf_open(fname); - if (inp && is_secured_file (iobuf_get_fd (inp))) - { - iobuf_close (inp); - inp = NULL; - gpg_err_set_errno (EPERM); - } - if( !inp ) { - rc = gpg_error_from_syserror (); - log_error (_("can't open '%s': %s\n"), - fname? fname: "[stdin]", strerror(errno) ); - goto leave; + armor_filter_context_t *afx; + progress_filter_context_t *pfx; + gcry_md_hd_t textmd = NULL; + iobuf_t inp = NULL; + iobuf_t out = NULL; + PACKET pkt; + int rc = 0; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + u32 duration = 0; + + pfx = new_progress_context (); + afx = new_armor_context (); + init_packet( &pkt ); + + if (opt.ask_sig_expire && !opt.batch) + duration = ask_expire_interval (1, opt.def_sig_expire); + else + duration = parse_expire_string (opt.def_sig_expire); + + /* Note: In the old non-agent version the following call used to + * unprotect the secret key. This is now done on demand by the agent. */ + if ((rc=build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG))) + goto leave; + + /* Prepare iobufs. */ + inp = iobuf_open (fname); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + gpg_err_set_errno (EPERM); + } + if (!inp) + { + rc = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), + fname? fname: "[stdin]", gpg_strerror (rc)); + goto leave; } - handle_progress (pfx, inp, fname); + handle_progress (pfx, inp, fname); - if( outfile ) { - if (is_secured_filename (outfile) ) { - outfile = NULL; - gpg_err_set_errno (EPERM); + if (outfile) + { + if (is_secured_filename (outfile)) + { + outfile = NULL; + gpg_err_set_errno (EPERM); } - else - out = iobuf_create (outfile, 0); - if( !out ) - { - rc = gpg_error_from_syserror (); - log_error(_("can't create '%s': %s\n"), outfile, strerror(errno) ); - goto leave; - } - else if( opt.verbose ) - log_info(_("writing to '%s'\n"), outfile ); - } - else if ((rc = open_outfile (-1, fname, 1, 0, &out))) - goto leave; + else + out = iobuf_create (outfile, 0); - iobuf_writestr(out, "-----BEGIN PGP SIGNED MESSAGE-----" LF ); + if (!out) + { + rc = gpg_error_from_syserror (); + log_error (_("can't create '%s': %s\n"), outfile, gpg_strerror (rc)); + goto leave; + } + else if (opt.verbose) + log_info (_("writing to '%s'\n"), outfile); + } + else if ((rc = open_outfile (-1, fname, 1, 0, &out))) { - const char *s; - int any = 0; - byte hashs_seen[256]; - - memset( hashs_seen, 0, sizeof hashs_seen ); - iobuf_writestr(out, "Hash: " ); - for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) { - int i = hash_for (sk_rover->pk); - - if( !hashs_seen[ i & 0xff ] ) { - s = gcry_md_algo_name ( i ); - if( s ) { - hashs_seen[ i & 0xff ] = 1; - if( any ) - iobuf_put(out, ',' ); - iobuf_writestr(out, s ); - any = 1; - } - } - } - log_assert(any); - iobuf_writestr(out, LF ); + goto leave; } - if( opt.not_dash_escaped ) - iobuf_writestr( out, - "NotDashEscaped: You need "GPG_NAME - " to verify this message" LF ); - iobuf_writestr(out, LF ); + iobuf_writestr (out, "-----BEGIN PGP SIGNED MESSAGE-----" LF); - if ( gcry_md_open (&textmd, 0, 0) ) - BUG (); + { + const char *s; + int any = 0; + byte hashs_seen[256]; + + memset (hashs_seen, 0, sizeof hashs_seen); + iobuf_writestr (out, "Hash: " ); for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) - gcry_md_enable (textmd, hash_for(sk_rover->pk)); + { + int i = hash_for (sk_rover->pk); - if ( DBG_HASHING ) - gcry_md_debug ( textmd, "clearsign" ); + if (!hashs_seen[ i & 0xff ]) + { + s = gcry_md_algo_name (i); + if (s) + { + hashs_seen[ i & 0xff ] = 1; + if (any) + iobuf_put (out, ','); + iobuf_writestr (out, s); + any = 1; + } + } + } + log_assert (any); + iobuf_writestr (out, LF); + } + + if (opt.not_dash_escaped) + iobuf_writestr (out, + "NotDashEscaped: You need "GPG_NAME + " to verify this message" LF); + iobuf_writestr (out, LF ); + + if (gcry_md_open (&textmd, 0, 0)) + BUG (); + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) + gcry_md_enable (textmd, hash_for(sk_rover->pk)); - copy_clearsig_text (out, inp, textmd, !opt.not_dash_escaped, - opt.escape_from); - /* fixme: check for read errors */ + if (DBG_HASHING) + gcry_md_debug (textmd, "clearsign"); - /* now write the armor */ - afx->what = 2; - push_armor_filter (afx, out); + copy_clearsig_text (out, inp, textmd, !opt.not_dash_escaped, opt.escape_from); + /* fixme: check for read errors */ - /* Write the signatures. */ - rc = write_signature_packets (ctrl, sk_list, out, textmd, 0x01, 0, - duration, 'C', NULL); - if( rc ) - goto leave; + /* Now write the armor. */ + afx->what = 2; + push_armor_filter (afx, out); + + /* Write the signatures. */ + rc = write_signature_packets (ctrl, sk_list, out, textmd, NULL, 0x01, 0, + duration, 'C', NULL); + if (rc) + goto leave; - leave: - if( rc ) - iobuf_cancel(out); - else - iobuf_close(out); - iobuf_close(inp); - gcry_md_close ( textmd ); - release_sk_list( sk_list ); - release_progress_context (pfx); - release_armor_context (afx); - return rc; + leave: + if (rc) + iobuf_cancel (out); + else + iobuf_close (out); + iobuf_close (inp); + gcry_md_close (textmd); + release_sk_list (sk_list); + release_progress_context (pfx); + release_armor_context (afx); + return rc; } + /* * Sign and conventionally encrypt the given file. * FIXME: Far too much code is duplicated - revamp the whole file. @@ -1269,175 +1388,182 @@ clearsign_file (ctrl_t ctrl, int sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t locusr) { - armor_filter_context_t *afx; - progress_filter_context_t *pfx; - compress_filter_context_t zfx; - md_filter_context_t mfx; - text_filter_context_t tfx; - cipher_filter_context_t cfx; - IOBUF inp = NULL, out = NULL; - PACKET pkt; - STRING2KEY *s2k = NULL; - int rc = 0; - SK_LIST sk_list = NULL; - SK_LIST sk_rover = NULL; - int algo; - u32 duration=0; - int canceled; - - pfx = new_progress_context (); - afx = new_armor_context (); - memset( &zfx, 0, sizeof zfx); - memset( &mfx, 0, sizeof mfx); - memset( &tfx, 0, sizeof tfx); - memset( &cfx, 0, sizeof cfx); - init_packet( &pkt ); - - if (opt.ask_sig_expire && !opt.batch) - duration = ask_expire_interval (1, opt.def_sig_expire); - else - duration = parse_expire_string (opt.def_sig_expire); - - /* Note: In the old non-agent version the following call used to - unprotect the secret key. This is now done on demand by the agent. */ - rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG); - if (rc) - goto leave; - - /* prepare iobufs */ - inp = iobuf_open(fname); - if (inp && is_secured_file (iobuf_get_fd (inp))) - { - iobuf_close (inp); - inp = NULL; - gpg_err_set_errno (EPERM); - } - if( !inp ) { - rc = gpg_error_from_syserror (); - log_error (_("can't open '%s': %s\n"), - fname? fname: "[stdin]", strerror(errno) ); - goto leave; + armor_filter_context_t *afx; + progress_filter_context_t *pfx; + compress_filter_context_t zfx; + md_filter_context_t mfx; + text_filter_context_t tfx; + cipher_filter_context_t cfx; + iobuf_t inp = NULL; + iobuf_t out = NULL; + PACKET pkt; + STRING2KEY *s2k = NULL; + int rc = 0; + SK_LIST sk_list = NULL; + SK_LIST sk_rover = NULL; + int algo; + u32 duration = 0; + int canceled; + pt_extra_hash_data_t extrahash = NULL; + + pfx = new_progress_context (); + afx = new_armor_context (); + memset (&zfx, 0, sizeof zfx); + memset (&mfx, 0, sizeof mfx); + memset (&tfx, 0, sizeof tfx); + memset (&cfx, 0, sizeof cfx); + init_packet (&pkt); + + if (opt.ask_sig_expire && !opt.batch) + duration = ask_expire_interval (1, opt.def_sig_expire); + else + duration = parse_expire_string (opt.def_sig_expire); + + /* Note: In the old non-agent version the following call used to + * unprotect the secret key. This is now done on demand by the agent. */ + rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG); + if (rc) + goto leave; + + /* Prepare iobufs. */ + inp = iobuf_open (fname); + if (inp && is_secured_file (iobuf_get_fd (inp))) + { + iobuf_close (inp); + inp = NULL; + gpg_err_set_errno (EPERM); } - handle_progress (pfx, inp, fname); + if (!inp) + { + rc = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), + fname? fname: "[stdin]", gpg_strerror (rc)); + goto leave; + } + handle_progress (pfx, inp, fname); - /* prepare key */ - s2k = xmalloc_clear( sizeof *s2k ); - s2k->mode = opt.s2k_mode; - s2k->hash_algo = S2K_DIGEST_ALGO; + /* Prepare key. */ + s2k = xmalloc_clear (sizeof *s2k); + s2k->mode = opt.s2k_mode; + s2k->hash_algo = S2K_DIGEST_ALGO; - algo = default_cipher_algo(); - cfx.dek = passphrase_to_dek (algo, s2k, 1, 1, NULL, &canceled); + algo = default_cipher_algo (); + cfx.dek = passphrase_to_dek (algo, s2k, 1, 1, NULL, &canceled); - if (!cfx.dek || !cfx.dek->keylen) { - rc = gpg_error (canceled?GPG_ERR_CANCELED:GPG_ERR_BAD_PASSPHRASE); - log_error(_("error creating passphrase: %s\n"), gpg_strerror (rc) ); - goto leave; + if (!cfx.dek || !cfx.dek->keylen) + { + rc = gpg_error (canceled?GPG_ERR_CANCELED:GPG_ERR_BAD_PASSPHRASE); + log_error (_("error creating passphrase: %s\n"), gpg_strerror (rc)); + goto leave; } - cfx.dek->use_aead = use_aead (NULL, cfx.dek->algo); - if (!cfx.dek->use_aead) - cfx.dek->use_mdc = !!use_mdc (NULL, cfx.dek->algo); - - if (!opt.quiet || !opt.batch) - log_info (_("%s.%s encryption will be used\n"), - openpgp_cipher_algo_name (algo), - cfx.dek->use_aead? openpgp_aead_algo_name (cfx.dek->use_aead) - /**/ : "CFB"); - - /* now create the outfile */ - rc = open_outfile (-1, fname, opt.armor? 1:0, 0, &out); - if (rc) - goto leave; - - /* prepare to calculate the MD over the input */ - if (opt.textmode) - iobuf_push_filter (inp, text_filter, &tfx); - if ( gcry_md_open (&mfx.md, 0, 0) ) - BUG (); - if ( DBG_HASHING ) - gcry_md_debug (mfx.md, "symc-sign"); + cfx.dek->use_aead = use_aead (NULL, cfx.dek->algo); + if (!cfx.dek->use_aead) + cfx.dek->use_mdc = !!use_mdc (NULL, cfx.dek->algo); + + if (!opt.quiet || !opt.batch) + log_info (_("%s.%s encryption will be used\n"), + openpgp_cipher_algo_name (algo), + cfx.dek->use_aead? openpgp_aead_algo_name (cfx.dek->use_aead) + /**/ : "CFB"); + + /* Now create the outfile. */ + rc = open_outfile (-1, fname, opt.armor? 1:0, 0, &out); + if (rc) + goto leave; + + /* Prepare to calculate the MD over the input. */ + if (opt.textmode) + iobuf_push_filter (inp, text_filter, &tfx); + if (gcry_md_open (&mfx.md, 0, 0)) + BUG (); + if (DBG_HASHING) + gcry_md_debug (mfx.md, "symc-sign"); - for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) - gcry_md_enable (mfx.md, hash_for (sk_rover->pk)); + for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) + gcry_md_enable (mfx.md, hash_for (sk_rover->pk)); - iobuf_push_filter (inp, md_filter, &mfx); + iobuf_push_filter (inp, md_filter, &mfx); - /* Push armor output filter */ - if (opt.armor) - push_armor_filter (afx, out); + /* Push armor output filter */ + if (opt.armor) + push_armor_filter (afx, out); - /* Write the symmetric key packet */ - /*(current filters: armor)*/ + /* Write the symmetric key packet */ + /* (current filters: armor)*/ + { + PKT_symkey_enc *enc = xmalloc_clear( sizeof *enc ); + + enc->version = 4; + enc->cipher_algo = cfx.dek->algo; + enc->s2k = *s2k; + pkt.pkttype = PKT_SYMKEY_ENC; + pkt.pkt.symkey_enc = enc; + if ((rc = build_packet (out, &pkt))) + log_error ("build symkey packet failed: %s\n", gpg_strerror (rc)); + xfree (enc); + } + + /* Push the encryption filter */ + iobuf_push_filter (out, + cfx.dek->use_aead? cipher_filter_aead + /**/ : cipher_filter_cfb, + &cfx); + + /* Push the compress filter */ + if (default_compress_algo()) { - PKT_symkey_enc *enc = xmalloc_clear( sizeof *enc ); - enc->version = 4; - enc->cipher_algo = cfx.dek->algo; - enc->s2k = *s2k; - pkt.pkttype = PKT_SYMKEY_ENC; - pkt.pkt.symkey_enc = enc; - if( (rc = build_packet( out, &pkt )) ) - log_error("build symkey packet failed: %s\n", gpg_strerror (rc) ); - xfree(enc); + if (cfx.dek && (cfx.dek->use_mdc || cfx.dek->use_aead)) + zfx.new_ctb = 1; + push_compress_filter (out, &zfx,default_compress_algo() ); } - /* Push the encryption filter */ - iobuf_push_filter (out, - cfx.dek->use_aead? cipher_filter_aead - /**/ : cipher_filter_cfb, - &cfx); + /* Write the one-pass signature packets */ + /* (current filters: zip - encrypt - armor) */ + rc = write_onepass_sig_packets (sk_list, out, opt.textmode? 0x01:0x00); + if (rc) + goto leave; - /* Push the compress filter */ - if (default_compress_algo()) - { - if (cfx.dek && (cfx.dek->use_mdc || cfx.dek->use_aead)) - zfx.new_ctb = 1; - push_compress_filter (out, &zfx,default_compress_algo() ); - } + write_status_begin_signing (mfx.md); - /* Write the one-pass signature packets */ - /*(current filters: zip - encrypt - armor)*/ - rc = write_onepass_sig_packets (sk_list, out, - opt.textmode? 0x01:0x00); - if (rc) - goto leave; + /* Pipe data through all filters; i.e. write the signed stuff. */ + /* (current filters: zip - encrypt - armor) */ + rc = write_plaintext_packet (out, inp, fname, + opt.textmode ? (opt.mimemode?'m':'t'):'b', + &extrahash); + if (rc) + goto leave; - write_status_begin_signing (mfx.md); - - /* Pipe data through all filters; i.e. write the signed stuff */ - /*(current filters: zip - encrypt - armor)*/ - rc = write_plaintext_packet (out, inp, fname, - opt.textmode ? (opt.mimemode?'m':'t'):'b'); - if (rc) - goto leave; - - /* Write the signatures */ - /*(current filters: zip - encrypt - armor)*/ - rc = write_signature_packets (ctrl, sk_list, out, mfx.md, - opt.textmode? 0x01 : 0x00, - 0, duration, 'S', NULL); - if( rc ) - goto leave; + /* Write the signatures. */ + /* (current filters: zip - encrypt - armor) */ + rc = write_signature_packets (ctrl, sk_list, out, mfx.md, extrahash, + opt.textmode? 0x01 : 0x00, + 0, duration, 'S', NULL); + if (rc) + goto leave; - leave: - if( rc ) - iobuf_cancel(out); - else { - iobuf_close(out); - write_status( STATUS_END_ENCRYPTION ); + leave: + if (rc) + iobuf_cancel (out); + else + { + iobuf_close (out); + write_status (STATUS_END_ENCRYPTION); } - iobuf_close(inp); - release_sk_list( sk_list ); - gcry_md_close( mfx.md ); - xfree(cfx.dek); - xfree(s2k); - release_progress_context (pfx); - release_armor_context (afx); - return rc; + iobuf_close (inp); + release_sk_list (sk_list); + gcry_md_close (mfx.md); + xfree (cfx.dek); + xfree (s2k); + release_progress_context (pfx); + release_armor_context (afx); + xfree (extrahash); + return rc; } -/**************** +/* * Create a v4 signature in *RET_SIG. * * PK is the primary key to sign (required for all sigs) @@ -1472,105 +1598,102 @@ make_keysig_packet (ctrl_t ctrl, int (*mksubpkt)(PKT_signature *, void *), void *opaque, const char *cache_nonce) { - PKT_signature *sig; - int rc=0; - int sigversion; - gcry_md_hd_t md; + PKT_signature *sig; + int rc = 0; + int sigversion; + gcry_md_hd_t md; - log_assert ((sigclass >= 0x10 && sigclass <= 0x13) || sigclass == 0x1F - || sigclass == 0x20 || sigclass == 0x18 || sigclass == 0x19 - || sigclass == 0x30 || sigclass == 0x28 ); + log_assert ((sigclass >= 0x10 && sigclass <= 0x13) || sigclass == 0x1F + || sigclass == 0x20 || sigclass == 0x18 || sigclass == 0x19 + || sigclass == 0x30 || sigclass == 0x28 ); + if (pksk->version >= 5) + sigversion = 5; + else sigversion = 4; - if (sigversion < pksk->version) - sigversion = pksk->version; - if( !digest_algo ) - { - /* Basically, this means use SHA1 always unless the user - specified something (use whatever they said), or it's DSA - (use the best match). They still can't pick an - inappropriate hash for DSA or the signature will fail. - Note that this still allows the caller of - make_keysig_packet to override the user setting if it - must. */ - - if(opt.cert_digest_algo) - digest_algo=opt.cert_digest_algo; - else if(pksk->pubkey_algo == PUBKEY_ALGO_DSA) - digest_algo = match_dsa_hash (gcry_mpi_get_nbits (pksk->pkey[1])/8); - else if (pksk->pubkey_algo == PUBKEY_ALGO_ECDSA - || pksk->pubkey_algo == PUBKEY_ALGO_EDDSA) - { - if (openpgp_oid_is_ed25519 (pksk->pkey[0])) - digest_algo = DIGEST_ALGO_SHA256; - else - digest_algo = match_dsa_hash - (ecdsa_qbits_from_Q (gcry_mpi_get_nbits (pksk->pkey[1]))/8); - } - else - digest_algo = DEFAULT_DIGEST_ALGO; - } + if (!digest_algo) + { + /* Basically, this means use SHA1 always unless the user + * specified something (use whatever they said), or it's DSA + * (use the best match). They still can't pick an inappropriate + * hash for DSA or the signature will fail. Note that this + * still allows the caller of make_keysig_packet to override the + * user setting if it must. */ + + if (opt.cert_digest_algo) + digest_algo = opt.cert_digest_algo; + else if (pksk->pubkey_algo == PUBKEY_ALGO_DSA) + digest_algo = match_dsa_hash (gcry_mpi_get_nbits (pksk->pkey[1])/8); + else if (pksk->pubkey_algo == PUBKEY_ALGO_ECDSA + || pksk->pubkey_algo == PUBKEY_ALGO_EDDSA) + { + if (openpgp_oid_is_ed25519 (pksk->pkey[0])) + digest_algo = DIGEST_ALGO_SHA256; + else + digest_algo = match_dsa_hash + (ecdsa_qbits_from_Q (gcry_mpi_get_nbits (pksk->pkey[1]))/8); + } + else + digest_algo = DEFAULT_DIGEST_ALGO; + } - if ( gcry_md_open (&md, digest_algo, 0 ) ) - BUG (); + if (gcry_md_open (&md, digest_algo, 0)) + BUG (); - /* Hash the public key certificate. */ - hash_public_key( md, pk ); + /* Hash the public key certificate. */ + hash_public_key (md, pk); - if( sigclass == 0x18 || sigclass == 0x19 || sigclass == 0x28 ) - { - /* hash the subkey binding/backsig/revocation */ - hash_public_key( md, subpk ); - } - else if( sigclass != 0x1F && sigclass != 0x20 ) - { - /* hash the user id */ - hash_uid (md, sigversion, uid); - } - /* and make the signature packet */ - sig = xmalloc_clear( sizeof *sig ); - sig->version = sigversion; - sig->flags.exportable=1; - sig->flags.revocable=1; - keyid_from_pk (pksk, sig->keyid); - sig->pubkey_algo = pksk->pubkey_algo; - sig->digest_algo = digest_algo; - if(timestamp) - sig->timestamp=timestamp; - else - sig->timestamp=make_timestamp(); - if(duration) - sig->expiredate=sig->timestamp+duration; - sig->sig_class = sigclass; - - build_sig_subpkt_from_sig (sig, pksk); - mk_notation_policy_etc (sig, pk, pksk); - - /* Crucial that the call to mksubpkt comes LAST before the calls - to finalize the sig as that makes it possible for the mksubpkt - function to get a reliable pointer to the subpacket area. */ - if (mksubpkt) - rc = (*mksubpkt)( sig, opaque ); - - if( !rc ) { - hash_sigversion_to_magic (md, sig); - gcry_md_final (md); - - rc = complete_sig (ctrl, sig, pksk, md, cache_nonce); + if (sigclass == 0x18 || sigclass == 0x19 || sigclass == 0x28) + { + /* Hash the subkey binding/backsig/revocation. */ + hash_public_key (md, subpk); + } + else if (sigclass != 0x1F && sigclass != 0x20) + { + /* Hash the user id. */ + hash_uid (md, sigversion, uid); + } + /* Make the signature packet. */ + sig = xmalloc_clear (sizeof *sig); + sig->version = sigversion; + sig->flags.exportable = 1; + sig->flags.revocable = 1; + keyid_from_pk (pksk, sig->keyid); + sig->pubkey_algo = pksk->pubkey_algo; + sig->digest_algo = digest_algo; + sig->timestamp = timestamp? timestamp : make_timestamp (); + if (duration) + sig->expiredate = sig->timestamp + duration; + sig->sig_class = sigclass; + + build_sig_subpkt_from_sig (sig, pksk); + mk_notation_policy_etc (sig, pk, pksk); + + /* Crucial that the call to mksubpkt comes LAST before the calls + * to finalize the sig as that makes it possible for the mksubpkt + * function to get a reliable pointer to the subpacket area. */ + if (mksubpkt) + rc = (*mksubpkt)(sig, opaque); + + if (!rc) + { + hash_sigversion_to_magic (md, sig, NULL); + gcry_md_final (md); + rc = complete_sig (ctrl, sig, pksk, md, cache_nonce); } - gcry_md_close (md); - if( rc ) - free_seckey_enc( sig ); - else - *ret_sig = sig; - return rc; + gcry_md_close (md); + if (rc) + free_seckey_enc (sig); + else + *ret_sig = sig; + return rc; } -/**************** +/* * Create a new signature packet based on an existing one. * Only user ID signatures are supported for now. * PK is the public key to work on. @@ -1589,82 +1712,82 @@ update_keysig_packet (ctrl_t ctrl, int (*mksubpkt)(PKT_signature *, void *), void *opaque) { - PKT_signature *sig; - gpg_error_t rc = 0; - int digest_algo; - gcry_md_hd_t md; - - if ((!orig_sig || !pk || !pksk) - || (orig_sig->sig_class >= 0x10 && orig_sig->sig_class <= 0x13 && !uid) - || (orig_sig->sig_class == 0x18 && !subpk)) - return GPG_ERR_GENERAL; - - if ( opt.cert_digest_algo ) - digest_algo = opt.cert_digest_algo; - else - digest_algo = orig_sig->digest_algo; + PKT_signature *sig; + gpg_error_t rc = 0; + int digest_algo; + gcry_md_hd_t md; + + if ((!orig_sig || !pk || !pksk) + || (orig_sig->sig_class >= 0x10 && orig_sig->sig_class <= 0x13 && !uid) + || (orig_sig->sig_class == 0x18 && !subpk)) + return GPG_ERR_GENERAL; + + if (opt.cert_digest_algo) + digest_algo = opt.cert_digest_algo; + else + digest_algo = orig_sig->digest_algo; - if ( gcry_md_open (&md, digest_algo, 0 ) ) - BUG (); + if (gcry_md_open (&md, digest_algo, 0)) + BUG (); - /* Hash the public key certificate and the user id. */ - hash_public_key( md, pk ); + /* Hash the public key certificate and the user id. */ + hash_public_key (md, pk); - if( orig_sig->sig_class == 0x18 ) - hash_public_key( md, subpk ); - else - hash_uid (md, orig_sig->version, uid); + if (orig_sig->sig_class == 0x18) + hash_public_key (md, subpk); + else + hash_uid (md, orig_sig->version, uid); - /* create a new signature packet */ - sig = copy_signature (NULL, orig_sig); + /* Create a new signature packet. */ + sig = copy_signature (NULL, orig_sig); - sig->digest_algo=digest_algo; + sig->digest_algo = digest_algo; - /* We need to create a new timestamp so that new sig expiration - calculations are done correctly... */ - sig->timestamp=make_timestamp(); + /* We need to create a new timestamp so that new sig expiration + * calculations are done correctly... */ + sig->timestamp = make_timestamp(); - /* ... but we won't make a timestamp earlier than the existing - one. */ - { - int tmout = 0; - while(sig->timestamp<=orig_sig->timestamp) - { - if (++tmout > 5 && !opt.ignore_time_conflict) - { - rc = gpg_error (GPG_ERR_TIME_CONFLICT); - goto leave; - } - gnupg_sleep (1); - sig->timestamp=make_timestamp(); - } - } - - /* Note that already expired sigs will remain expired (with a - duration of 1) since build-packet.c:build_sig_subpkt_from_sig - detects this case. */ + /* ... but we won't make a timestamp earlier than the existing + * one. */ + { + int tmout = 0; + while (sig->timestamp <= orig_sig->timestamp) + { + if (++tmout > 5 && !opt.ignore_time_conflict) + { + rc = gpg_error (GPG_ERR_TIME_CONFLICT); + goto leave; + } + gnupg_sleep (1); + sig->timestamp = make_timestamp(); + } + } - /* Put the updated timestamp into the sig. Note that this will - automagically lower any sig expiration dates to correctly - correspond to the differences in the timestamps (i.e. the - duration will shrink). */ - build_sig_subpkt_from_sig (sig, pksk); + /* Note that already expired sigs will remain expired (with a + * duration of 1) since build-packet.c:build_sig_subpkt_from_sig + * detects this case. */ - if (mksubpkt) - rc = (*mksubpkt)(sig, opaque); + /* Put the updated timestamp into the sig. Note that this will + * automagically lower any sig expiration dates to correctly + * correspond to the differences in the timestamps (i.e. the + * duration will shrink). */ + build_sig_subpkt_from_sig (sig, pksk); - if (!rc) { - hash_sigversion_to_magic (md, sig); - gcry_md_final (md); + if (mksubpkt) + rc = (*mksubpkt)(sig, opaque); - rc = complete_sig (ctrl, sig, pksk, md, NULL); + if (!rc) + { + hash_sigversion_to_magic (md, sig, NULL); + gcry_md_final (md); + rc = complete_sig (ctrl, sig, pksk, md, NULL); } leave: - gcry_md_close (md); - if( rc ) - free_seckey_enc (sig); - else - *ret_sig = sig; - return rc; + gcry_md_close (md); + if (rc) + free_seckey_enc (sig); + else + *ret_sig = sig; + return rc; } diff --git a/g10/skclist.c b/g10/skclist.c index fd747fb2b..c9c41d0d9 100644 --- a/g10/skclist.c +++ b/g10/skclist.c @@ -337,7 +337,7 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) kbnode_t keyblock; kbnode_t node; getkey_ctx_t ctx; - pubkey_t results; + SK_LIST results; } *c = *context; if (!c) @@ -345,7 +345,11 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) /* Make a new context. */ c = xtrycalloc (1, sizeof *c); if (!c) - return gpg_error_from_syserror (); + { + err = gpg_error_from_syserror (); + free_public_key (sk); + return err; + } *context = c; } @@ -354,7 +358,7 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) /* Free the context. */ xfree (c->serialno); free_strlist (c->card_list); - pubkeys_free (c->results); + release_sk_list (c->results); release_kbnode (c->keyblock); getkey_end (ctrl, c->ctx); xfree (c); @@ -363,7 +367,10 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) } if (c->eof) - return gpg_error (GPG_ERR_EOF); + { + free_public_key (sk); + return gpg_error (GPG_ERR_EOF); + } for (;;) { @@ -373,6 +380,8 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) /* Loop over the list of secret keys. */ do { + char *serialno; + name = NULL; keyblock = NULL; switch (c->state) @@ -408,8 +417,6 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) case 4: /* Get next item from card list. */ if (c->sl) { - char *serialno; - err = agent_scd_serialno (&serialno, c->sl->d); if (err) { @@ -437,9 +444,13 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) } else { - if (c->serialno) - /* Select the original card again. */ - agent_scd_serialno (&c->serialno, c->serialno); + serialno = c->serialno; + if (serialno) + { + /* Select the original card again. */ + agent_scd_serialno (&c->serialno, serialno); + xfree (serialno); + } c->state++; } break; @@ -475,6 +486,7 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) default: /* No more names to check - stop. */ c->eof = 1; + free_public_key (sk); return gpg_error (GPG_ERR_EOF); } } @@ -504,7 +516,7 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) if (c->node->pkt->pkttype == PKT_PUBLIC_KEY || c->node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { - pubkey_t r; + SK_LIST r; /* Skip this candidate if it's already enumerated. */ for (r = c->results; r; r = r->next) @@ -525,7 +537,6 @@ enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) } r->pk = sk; - r->keyblock = NULL; r->next = c->results; c->results = r; diff --git a/g10/tdbio.c b/g10/tdbio.c index fed0cf5ab..b6c38bd24 100644 --- a/g10/tdbio.c +++ b/g10/tdbio.c @@ -106,7 +106,7 @@ struct cmp_xdir_struct static char *db_name; /* The handle for locking the trustdb file and a counter to record how - * often this lock has been taken. That counter is required becuase + * often this lock has been taken. That counter is required because * dotlock does not implemen recursive locks. */ static dotlock_t lockhandle; static unsigned int is_locked; @@ -562,6 +562,12 @@ tdbio_update_version_record (ctrl_t ctrl) { TRUSTREC rec; int rc; + int opt_tm; + + /* Never store a TOFU trust model in the trustdb. Use PGP instead. */ + opt_tm = opt.trust_model; + if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP) + opt_tm = TM_PGP; memset (&rec, 0, sizeof rec); @@ -572,7 +578,7 @@ tdbio_update_version_record (ctrl_t ctrl) rec.r.ver.marginals = opt.marginals_needed; rec.r.ver.completes = opt.completes_needed; rec.r.ver.cert_depth = opt.max_cert_depth; - rec.r.ver.trust_model = opt.trust_model; + rec.r.ver.trust_model = opt_tm; rec.r.ver.min_cert_level = opt.min_cert_level; rc = tdbio_write_record (ctrl, &rec); } @@ -583,7 +589,7 @@ tdbio_update_version_record (ctrl_t ctrl) /* * Create and write the trustdb version record. - * This is called with the writelock activ. + * This is called with the writelock active. * Returns: 0 on success or an error code. */ static int @@ -591,6 +597,12 @@ create_version_record (ctrl_t ctrl) { TRUSTREC rec; int rc; + int opt_tm; + + /* Never store a TOFU trust model in the trustdb. Use PGP instead. */ + opt_tm = opt.trust_model; + if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP) + opt_tm = TM_PGP; memset (&rec, 0, sizeof rec); rec.r.ver.version = 3; @@ -598,8 +610,8 @@ create_version_record (ctrl_t ctrl) rec.r.ver.marginals = opt.marginals_needed; rec.r.ver.completes = opt.completes_needed; rec.r.ver.cert_depth = opt.max_cert_depth; - if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC) - rec.r.ver.trust_model = opt.trust_model; + if (opt_tm == TM_PGP || opt_tm == TM_CLASSIC) + rec.r.ver.trust_model = opt_tm; else rec.r.ver.trust_model = TM_PGP; rec.r.ver.min_cert_level = opt.min_cert_level; @@ -883,16 +895,25 @@ tdbio_db_matches_options() { TRUSTREC vr; int rc; + int opt_tm, tm; rc = tdbio_read_record (0, &vr, RECTYPE_VER); if( rc ) log_fatal( _("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc) ); + /* Consider tofu and pgp the same. */ + tm = vr.r.ver.trust_model; + if (tm == TM_TOFU || tm == TM_TOFU_PGP) + tm = TM_PGP; + opt_tm = opt.trust_model; + if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP) + opt_tm = TM_PGP; + yes_no = vr.r.ver.marginals == opt.marginals_needed && vr.r.ver.completes == opt.completes_needed && vr.r.ver.cert_depth == opt.max_cert_depth - && vr.r.ver.trust_model == opt.trust_model + && tm == opt_tm && vr.r.ver.min_cert_level == opt.min_cert_level; } @@ -1118,7 +1139,7 @@ upd_hashtable (ctrl_t ctrl, ulong table, byte *key, int keylen, ulong newrecnum) if (rec.r.hlst.next) { - /* read the next reord of the list. */ + /* read the next record of the list. */ rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST); if (rc) { diff --git a/g10/tofu.c b/g10/tofu.c index 762b19b7a..44f354512 100644 --- a/g10/tofu.c +++ b/g10/tofu.c @@ -3292,7 +3292,7 @@ show_warning (const char *fingerprint, strlist_t user_id_list) static char * email_from_user_id (const char *user_id) { - char *email = mailbox_from_userid (user_id); + char *email = mailbox_from_userid (user_id, 0); if (! email) { /* Hmm, no email address was provided or we are out of core. Just diff --git a/g10/trustdb.c b/g10/trustdb.c index 8ef6db542..a230a6c03 100644 --- a/g10/trustdb.c +++ b/g10/trustdb.c @@ -1131,7 +1131,7 @@ tdb_get_validity_core (ctrl_t ctrl, if (sig && sig->signers_uid) /* Make sure the UID matches. */ { - char *email = mailbox_from_userid (user_id->name); + char *email = mailbox_from_userid (user_id->name, 0); if (!email || !*email || strcmp (sig->signers_uid, email) != 0) { if (DBG_TRUST) @@ -1506,7 +1506,7 @@ store_validation_status (ctrl_t ctrl, int depth, /* Returns a sanitized copy of the regexp (which might be "", but not NULL). */ #ifndef DISABLE_REGEX -/* Operator charactors except '.' and backslash. +/* Operator characters except '.' and backslash. See regex(7) on BSD. */ #define REGEXP_OPERATOR_CHARS "^[$()|*+?{" diff --git a/g10/verify.c b/g10/verify.c index caeb1a244..73ac4bad8 100644 --- a/g10/verify.c +++ b/g10/verify.c @@ -69,7 +69,7 @@ verify_signatures (ctrl_t ctrl, int nfiles, char **files ) * we can do it is by reading one byte from stdin and then unget * it; the problem here is that we may be reading from the * terminal (which could be detected using isatty() but won't work - * when under contol of a pty using program (e.g. expect)) and + * when under control of a pty using program (e.g. expect)) and * might get us in trouble when stdin is used for another purpose * (--passphrase-fd 0). So we have to break with the behaviour * prior to gpg 1.0.4 by assuming that case 3 is a normal diff --git a/g13/call-syshelp.c b/g13/call-syshelp.c index b160ba32d..a69573bd1 100644 --- a/g13/call-syshelp.c +++ b/g13/call-syshelp.c @@ -174,7 +174,7 @@ call_syshelp_release (ctrl_t ctrl) -/* Staus callback for call_syshelp_find_device. */ +/* Status callback for call_syshelp_find_device. */ static gpg_error_t finddevice_status_cb (void *opaque, const char *line) { diff --git a/g13/mountinfo.c b/g13/mountinfo.c index ed898b836..50cc153fa 100644 --- a/g13/mountinfo.c +++ b/g13/mountinfo.c @@ -117,7 +117,7 @@ mountinfo_del_mount (const char *container, const char *mountpoint, size_t idx; mtab_t m; - /* If a container or mountpint is givem search the RID via the + /* If a container or mountpint is given search the RID via the standard find function. */ if (container || mountpoint) { diff --git a/g13/runner.c b/g13/runner.c index 138269d21..b08d99030 100644 --- a/g13/runner.c +++ b/g13/runner.c @@ -278,7 +278,7 @@ runner_set_pid (runner_t runner, pid_t pid) } -/* Register the engine handler fucntions HANDLER and HANDLER_CLEANUP +/* Register the engine handler functions HANDLER and HANDLER_CLEANUP and its private HANDLER_DATA with RUNNER. */ void runner_set_handler (runner_t runner, diff --git a/kbx/kbxutil.c b/kbx/kbxutil.c index 2cfd07019..35f92ab6e 100644 --- a/kbx/kbxutil.c +++ b/kbx/kbxutil.c @@ -330,6 +330,18 @@ dump_fpr (const unsigned char *buffer, size_t len) static void +dump_grip (const unsigned char *buffer, size_t len) +{ + int i; + + for (i=0; i < len; i++, buffer++) + { + printf ("%02X", buffer[0]); + } +} + + +static void dump_openpgp_key (keybox_openpgp_info_t info, const unsigned char *image) { printf ("pub %2d %02X%02X%02X%02X", @@ -338,6 +350,9 @@ dump_openpgp_key (keybox_openpgp_info_t info, const unsigned char *image) info->primary.keyid[6], info->primary.keyid[7] ); dump_fpr (info->primary.fpr, info->primary.fprlen); putchar ('\n'); + fputs ("grp ", stdout); + dump_grip (info->primary.grip, 20); + putchar ('\n'); if (info->nsubkeys) { struct _keybox_openpgp_key_info *k; @@ -351,6 +366,9 @@ dump_openpgp_key (keybox_openpgp_info_t info, const unsigned char *image) k->keyid[6], k->keyid[7] ); dump_fpr (k->fpr, k->fprlen); putchar ('\n'); + fputs ("grp ", stdout); + dump_grip (k->grip, 20); + putchar ('\n'); k = k->next; } while (k); diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c index 687421219..0bcd4a323 100644 --- a/kbx/keybox-blob.c +++ b/kbx/keybox-blob.c @@ -62,7 +62,8 @@ 2 = OpenPGP 3 = X509 - byte Version number of this blob type - 1 = The only defined value + 1 = Blob with 20 byte fingerprints + 2 = Blob with 32 byte fingerprints and no keyids. - u16 Blob flags bit 0 = contains secret key material (not used) bit 1 = ephemeral blob (e.g. used while querying external resources) @@ -70,19 +71,36 @@ certificate - u32 The length of the keyblock or certificate - u16 [NKEYS] Number of keys (at least 1!) [X509: always 1] - - u16 Size of the key information structure (at least 28). + - u16 Size of the key information structure (at least 28 or 56). - NKEYS times: + Version 1 blob: - b20 The fingerprint of the key. Fingerprints are always 20 bytes, MD5 left padded with zeroes. - u32 Offset to the n-th key's keyID (a keyID is always 8 byte) or 0 if not known which is the case only for X.509. + Note that this separate keyid is not anymore used by + gnupg since the support for v3 keys has been removed. + We create this field anyway for backward compatibility with + old EOL-ed versions. Eventually we will completely move + to the version 2 blob format. - u16 Key flags bit 0 = qualified signature (not yet implemented} - u16 RFU - bN Optional filler up to the specified length of this structure. + Version 2 blob: + - b32 The fingerprint of the key. This fingerprint is + either 20 or 32 bytes. A 20 byte fingerprint is + right filled with zeroes. + - u16 Key flags + bit 0 = qualified signature (not yet implemented} + bit 7 = 32 byte fingerprint in use. + - u16 RFU + - b20 keygrip + - bN Optional filler up to the specified length of this + structure. - u16 Size of the serial number (may be zero) - - bN The serial number. N as giiven above. + - bN The serial number. N as given above. - u16 Number of user IDs - u16 [NUIDS] Size of user ID information structure - NUIDS times: @@ -172,15 +190,12 @@ struct membuf { }; -/* #if MAX_FINGERPRINT_LEN < 20 */ -/* #error fingerprints are 20 bytes */ -/* #endif */ - struct keyboxblob_key { - char fpr[20]; + char fpr[32]; u32 off_kid; ulong off_kid_addr; u16 flags; + u16 fprlen; /* Either 20 or 32 */ }; struct keyboxblob_uid { u32 off; @@ -380,10 +395,9 @@ pgp_create_key_part_single (KEYBOXBLOB blob, int n, int off; fprlen = kinfo->fprlen; - if (fprlen > 20) - fprlen = 20; memcpy (blob->keys[n].fpr, kinfo->fpr, fprlen); - if (fprlen != 20) /* v3 fpr - shift right and fill with zeroes. */ + blob->keys[n].fprlen = fprlen; + if (fprlen < 20) /* v3 fpr - shift right and fill with zeroes. */ { memmove (blob->keys[n].fpr + 20 - fprlen, blob->keys[n].fpr, fprlen); memset (blob->keys[n].fpr, 0, 20 - fprlen); @@ -533,30 +547,51 @@ release_kid_list (struct keyid_list *kl) } - +/* Create a new blob header. If WANT_FPR32 is set a version 2 blob is + * created. */ static int -create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral) +create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral, + int want_fpr32) { struct membuf *a = blob->buf; int i; put32 ( a, 0 ); /* blob length, needs fixup */ put8 ( a, blobtype); - put8 ( a, 1 ); /* blob type version */ + put8 ( a, want_fpr32? 2:1 ); /* blob type version */ put16 ( a, as_ephemeral? 2:0 ); /* blob flags */ put32 ( a, 0 ); /* offset to the raw data, needs fixup */ put32 ( a, 0 ); /* length of the raw data, needs fixup */ put16 ( a, blob->nkeys ); - put16 ( a, 20 + 4 + 2 + 2 ); /* size of key info */ + if (want_fpr32) + put16 ( a, 32 + 2 + 2 + 20); /* size of key info */ + else + put16 ( a, 20 + 4 + 2 + 2 ); /* size of key info */ for ( i=0; i < blob->nkeys; i++ ) { - put_membuf (a, blob->keys[i].fpr, 20); - blob->keys[i].off_kid_addr = a->len; - put32 ( a, 0 ); /* offset to keyid, fixed up later */ - put16 ( a, blob->keys[i].flags ); - put16 ( a, 0 ); /* reserved */ + if (want_fpr32) + { + put_membuf (a, blob->keys[i].fpr, blob->keys[i].fprlen); + blob->keys[i].off_kid_addr = a->len; + if (blob->keys[i].fprlen == 32) + put16 ( a, (blob->keys[i].flags | 0x80)); + else + put16 ( a, blob->keys[i].flags); + put16 ( a, 0 ); /* reserved */ + /* FIXME: Put the real grip here instead of the filler. */ + put_membuf (a, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 20); + } + else + { + log_assert (blob->keys[i].fprlen <= 20); + put_membuf (a, blob->keys[i].fpr, 20); + blob->keys[i].off_kid_addr = a->len; + put32 ( a, 0 ); /* offset to keyid, fixed up later */ + put16 ( a, blob->keys[i].flags ); + put16 ( a, 0 ); /* reserved */ + } } put16 (a, blob->seriallen); /*fixme: check that it fits into 16 bits*/ @@ -593,11 +628,14 @@ create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral) /* space where we write keyIDs and other stuff so that the pointers can actually point to somewhere */ - if (blobtype == KEYBOX_BLOBTYPE_PGP) + if (blobtype == KEYBOX_BLOBTYPE_PGP && !want_fpr32) { - /* We need to store the keyids for all pgp v3 keys because those key - IDs are not part of the fingerprint. While we are doing that, we - fixup all the keyID offsets */ + /* For version 1 blobs, we need to store the keyids for all v3 + * keys because those key IDs are not part of the fingerprint. + * While we are doing that, we fixup all the keyID offsets. For + * version 2 blobs (which can't carry v3 keys) we compute the + * keyids in the fly because they are just stripped down + * fingerprints. */ for (i=0; i < blob->nkeys; i++ ) { if (blob->keys[i].off_kid) @@ -616,7 +654,7 @@ create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral) if (blobtype == KEYBOX_BLOBTYPE_X509) { /* We don't want to point to ASN.1 encoded UserIDs (DNs) but to - the utf-8 string represenation of them */ + the utf-8 string representation of them */ for (i=0; i < blob->nuids; i++ ) { if (blob->uids[i].name) @@ -711,9 +749,27 @@ _keybox_create_openpgp_blob (KEYBOXBLOB *r_blob, { gpg_error_t err; KEYBOXBLOB blob; + int need_fpr32 = 0; *r_blob = NULL; + + /* Check whether we need a blob with 32 bit fingerprints. We could + * use this always but for backward compatiblity we do this only for + * v5 keys. */ + if (info->primary.version == 5) + need_fpr32 = 1; + else + { + struct _keybox_openpgp_key_info *kinfo; + for (kinfo = &info->subkeys; kinfo; kinfo = kinfo->next) + if (kinfo->version == 5) + { + need_fpr32 = 1; + break; + } + } + blob = xtrycalloc (1, sizeof *blob); if (!blob) return gpg_error_from_syserror (); @@ -756,7 +812,8 @@ _keybox_create_openpgp_blob (KEYBOXBLOB *r_blob, init_membuf (&blob->bufbuf, 1024); blob->buf = &blob->bufbuf; - err = create_blob_header (blob, KEYBOX_BLOBTYPE_PGP, as_ephemeral); + err = create_blob_header (blob, KEYBOX_BLOBTYPE_PGP, + as_ephemeral, need_fpr32); if (err) goto leave; err = pgp_create_blob_keyblock (blob, image, imagelen); @@ -943,7 +1000,7 @@ _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert, init_membuf (&blob->bufbuf, 1024); blob->buf = &blob->bufbuf; /* write out what we already have */ - rc = create_blob_header (blob, KEYBOX_BLOBTYPE_X509, as_ephemeral); + rc = create_blob_header (blob, KEYBOX_BLOBTYPE_X509, as_ephemeral, 0); if (rc) goto leave; rc = x509_create_blob_cert (blob, cert); diff --git a/kbx/keybox-defs.h b/kbx/keybox-defs.h index be2dd721f..354d5fd11 100644 --- a/kbx/keybox-defs.h +++ b/kbx/keybox-defs.h @@ -94,14 +94,16 @@ struct keybox_handle { }; -/* Openpgp helper structures. */ +/* OpenPGP helper structures. */ struct _keybox_openpgp_key_info { struct _keybox_openpgp_key_info *next; int algo; + int version; + unsigned char grip[20]; unsigned char keyid[8]; - int fprlen; /* Either 16 or 20 */ - unsigned char fpr[20]; + int fprlen; /* Either 16, 20 or 32 */ + unsigned char fpr[32]; }; struct _keybox_openpgp_uid_info diff --git a/kbx/keybox-dump.c b/kbx/keybox-dump.c index aa1d93be7..48c3f63c5 100644 --- a/kbx/keybox-dump.c +++ b/kbx/keybox-dump.c @@ -170,6 +170,7 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp) ulong nserial; ulong unhashed; const byte *p; + int is_fpr32; /* blob ersion 2 */ buffer = _keybox_get_blob_image (blob, &length); @@ -207,7 +208,9 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp) fprintf (fp, "[can't dump this blob type]\n"); return 0; } + /* Here we have either BLOGTYPE_X509 or BLOBTYPE_OPENPGP */ fprintf (fp, "Version: %d\n", buffer[5]); + is_fpr32 = buffer[5] == 2; if (length < 40) { @@ -267,15 +270,24 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp) ulong kidoff, kflags; fprintf (fp, "Key-Fpr[%lu]: ", n ); - for (i=0; i < 20; i++ ) - fprintf (fp, "%02X", p[i]); - kidoff = get32 (p + 20); - fprintf (fp, "\nKey-Kid-Off[%lu]: %lu\n", n, kidoff ); - fprintf (fp, "Key-Kid[%lu]: ", n ); - /* fixme: check bounds */ - for (i=0; i < 8; i++ ) - fprintf (fp, "%02X", buffer[kidoff+i] ); - kflags = get16 (p + 24 ); + if (is_fpr32) + { + kflags = get16 (p + 32 ); + for (i=0; i < ((kflags & 0x80)?32:20); i++ ) + fprintf (fp, "%02X", p[i]); + } + else + { + for (i=0; i < 20; i++ ) + fprintf (fp, "%02X", p[i]); + kidoff = get32 (p + 20); + fprintf (fp, "\nKey-Kid-Off[%lu]: %lu\n", n, kidoff ); + fprintf (fp, "Key-Kid[%lu]: ", n ); + /* fixme: check bounds */ + for (i=0; i < 8; i++ ) + fprintf (fp, "%02X", buffer[kidoff+i] ); + kflags = get16 (p + 24 ); + } fprintf( fp, "\nKey-Flags[%lu]: %04lX\n", n, kflags); } diff --git a/kbx/keybox-errors.c b/kbx/keybox-errors.c index ce2b498d0..cb5a092d9 100644 --- a/kbx/keybox-errors.c +++ b/kbx/keybox-errors.c @@ -8,7 +8,7 @@ * keybox_strerror: * @err: Error code * - * This function returns a textual representaion of the given + * This function returns a textual representation of the given * errorcode. If this is an unknown value, a string with the value * is returned (Beware: it is hold in a static buffer). * diff --git a/kbx/keybox-openpgp.c b/kbx/keybox-openpgp.c index 0ba0b9ae8..7a35475ca 100644 --- a/kbx/keybox-openpgp.c +++ b/kbx/keybox-openpgp.c @@ -38,6 +38,13 @@ #include "../common/openpgpdefs.h" #include "../common/host2net.h" +struct keyparm_s +{ + const char *mpi; + int len; /* int to avoid a cast in gcry_sexp_build. */ +}; + + /* Assume a valid OpenPGP packet at the address pointed to by BUFBTR which has a maximum length as stored at BUFLEN. Return the header information of that packet and advance the pointer stored at BUFPTR @@ -165,6 +172,86 @@ next_packet (unsigned char const **bufptr, size_t *buflen, } +/* Take a list of key parameters KP for the OpenPGP ALGO and compute + * the keygrip which will be stored at GRIP. GRIP needs to be a + * buffer of 20 bytes. */ +static gpg_error_t +keygrip_from_keyparm (int algo, struct keyparm_s *kp, unsigned char *grip) +{ + gpg_error_t err; + gcry_sexp_t s_pkey = NULL; + + switch (algo) + { + case PUBKEY_ALGO_DSA: + err = gcry_sexp_build (&s_pkey, NULL, + "(public-key(dsa(p%b)(q%b)(g%b)(y%b)))", + kp[0].len, kp[0].mpi, + kp[1].len, kp[1].mpi, + kp[2].len, kp[2].mpi, + kp[3].len, kp[3].mpi); + break; + + case PUBKEY_ALGO_ELGAMAL: + case PUBKEY_ALGO_ELGAMAL_E: + err = gcry_sexp_build (&s_pkey, NULL, + "(public-key(elg(p%b)(g%b)(y%b)))", + kp[0].len, kp[0].mpi, + kp[1].len, kp[1].mpi, + kp[2].len, kp[2].mpi); + break; + + case PUBKEY_ALGO_RSA: + case PUBKEY_ALGO_RSA_S: + case PUBKEY_ALGO_RSA_E: + err = gcry_sexp_build (&s_pkey, NULL, + "(public-key(rsa(n%b)(e%b)))", + kp[0].len, kp[0].mpi, + kp[1].len, kp[1].mpi); + break; + + case PUBKEY_ALGO_EDDSA: + case PUBKEY_ALGO_ECDSA: + case PUBKEY_ALGO_ECDH: + { + char *curve = openpgp_oidbuf_to_str (kp[0].mpi, kp[0].len); + if (!curve) + err = gpg_error_from_syserror (); + else + { + err = gcry_sexp_build + (&s_pkey, NULL, + (algo == PUBKEY_ALGO_EDDSA)? + "(public-key(ecc(curve%s)(flags eddsa)(q%b)))": + (algo == PUBKEY_ALGO_ECDH + && openpgp_oidbuf_is_cv25519 (kp[0].mpi, kp[0].len))? + "(public-key(ecc(curve%s)(flags djb-tweak)(q%b)))": + "(public-key(ecc(curve%s)(q%b)))", + curve, kp[1].len, kp[1].mpi); + xfree (curve); + } + } + break; + + default: + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + break; + } + + if (!err && !gcry_pk_get_keygrip (s_pkey, grip)) + { + log_info ("kbx: error computing keygrip\n"); + err = gpg_error (GPG_ERR_GENERAL); + } + + gcry_sexp_release (s_pkey); + + if (err) + memset (grip, 0, 20); + return err; +} + + /* Parse a key packet and store the information in KI. */ static gpg_error_t parse_key (const unsigned char *data, size_t datalen, @@ -176,16 +263,19 @@ parse_key (const unsigned char *data, size_t datalen, size_t n; int npkey; unsigned char hashbuffer[768]; - const unsigned char *mpi_n = NULL; - size_t mpi_n_len = 0, mpi_e_len = 0; gcry_md_hd_t md; int is_ecc = 0; + int is_v5; + /* unsigned int pkbytes; for v5: # of octets of the public key params. */ + struct keyparm_s keyparm[OPENPGP_MAX_NPKEY]; + unsigned char *helpmpibuf[OPENPGP_MAX_NPKEY] = { NULL }; if (datalen < 5) return gpg_error (GPG_ERR_INV_PACKET); version = *data++; datalen--; - if (version < 2 || version > 4 ) + if (version < 2 || version > 5 ) return gpg_error (GPG_ERR_INV_PACKET); /* Invalid version. */ + is_v5 = version == 5; /*timestamp = ((data[0]<<24)|(data[1]<<16)|(data[2]<<8)|(data[3]));*/ data +=4; datalen -=4; @@ -201,6 +291,15 @@ parse_key (const unsigned char *data, size_t datalen, return gpg_error (GPG_ERR_INV_PACKET); algorithm = *data++; datalen--; + if (is_v5) + { + if (datalen < 4) + return gpg_error (GPG_ERR_INV_PACKET); + /* pkbytes = buf32_to_uint (data); */ + data += 4; + datalen -= 4; + } + switch (algorithm) { case PUBKEY_ALGO_RSA: @@ -228,6 +327,7 @@ parse_key (const unsigned char *data, size_t datalen, return gpg_error (GPG_ERR_UNKNOWN_ALGORITHM); } + ki->version = version; ki->algo = algorithm; for (i=0; i < npkey; i++ ) @@ -245,6 +345,9 @@ parse_key (const unsigned char *data, size_t datalen, nbytes++; /* The size byte itself. */ if (datalen < nbytes) return gpg_error (GPG_ERR_INV_PACKET); + + keyparm[i].mpi = data; + keyparm[i].len = nbytes; } else { @@ -254,21 +357,40 @@ parse_key (const unsigned char *data, size_t datalen, nbytes = (nbits+7) / 8; if (datalen < nbytes) return gpg_error (GPG_ERR_INV_PACKET); - /* For use by v3 fingerprint calculation we need to know the RSA - modulus and exponent. */ - if (i==0) - { - mpi_n = data; - mpi_n_len = nbytes; - } - else if (i==1) - mpi_e_len = nbytes; + + keyparm[i].mpi = data; + keyparm[i].len = nbytes; } data += nbytes; datalen -= nbytes; } n = data - data_start; + + /* Note: Starting here we need to jump to leave on error. */ + + /* Make sure the MPIs are unsigned. */ + for (i=0; i < npkey; i++) + { + if (!keyparm[i].len || (keyparm[i].mpi[0] & 0x80)) + { + helpmpibuf[i] = xtrymalloc (1+keyparm[i].len); + if (!helpmpibuf[i]) + { + err = gpg_error_from_syserror (); + goto leave; + } + helpmpibuf[i][0] = 0; + memcpy (helpmpibuf[i]+1, keyparm[i].mpi, keyparm[i].len); + keyparm[i].mpi = helpmpibuf[i]; + keyparm[i].len++; + } + } + + err = keygrip_from_keyparm (algorithm, keyparm, ki->grip); + if (err) + goto leave; + if (version < 4) { /* We do not support any other algorithm than RSA in v3 @@ -279,20 +401,20 @@ parse_key (const unsigned char *data, size_t datalen, err = gcry_md_open (&md, GCRY_MD_MD5, 0); if (err) return err; /* Oops */ - gcry_md_write (md, mpi_n, mpi_n_len); - gcry_md_write (md, mpi_n+mpi_n_len+2, mpi_e_len); + gcry_md_write (md, keyparm[0].mpi, keyparm[0].len); + gcry_md_write (md, keyparm[1].mpi, keyparm[1].len); memcpy (ki->fpr, gcry_md_read (md, 0), 16); gcry_md_close (md); ki->fprlen = 16; - if (mpi_n_len < 8) + if (keyparm[0].len < 8) { /* Moduli less than 64 bit are out of the specs scope. Zero them out because this is what gpg does too. */ memset (ki->keyid, 0, 8); } else - memcpy (ki->keyid, mpi_n + mpi_n_len - 8, 8); + memcpy (ki->keyid, keyparm[0].mpi + keyparm[0].len - 8, 8); } else { @@ -302,32 +424,70 @@ parse_key (const unsigned char *data, size_t datalen, have a scatter-gather enabled hash function. What we do here is to use a static buffer if this one is large enough and only use the regular hash functions if this buffer is not - large enough. */ - if ( 3 + n < sizeof hashbuffer ) + large enough. + FIXME: Factor this out to a shared fingerprint function. + */ + if (version == 5) { - hashbuffer[0] = 0x99; /* CTB */ - hashbuffer[1] = (n >> 8); /* 2 byte length header. */ - hashbuffer[2] = n; - memcpy (hashbuffer + 3, data_start, n); - gcry_md_hash_buffer (GCRY_MD_SHA1, ki->fpr, hashbuffer, 3 + n); + if ( 5 + n < sizeof hashbuffer ) + { + hashbuffer[0] = 0x9a; /* CTB */ + hashbuffer[1] = (n >> 24);/* 4 byte length header. */ + hashbuffer[2] = (n >> 16); + hashbuffer[3] = (n >> 8); + hashbuffer[4] = (n ); + memcpy (hashbuffer + 5, data_start, n); + gcry_md_hash_buffer (GCRY_MD_SHA256, ki->fpr, hashbuffer, 5 + n); + } + else + { + err = gcry_md_open (&md, GCRY_MD_SHA256, 0); + if (err) + return err; /* Oops */ + gcry_md_putc (md, 0x9a ); /* CTB */ + gcry_md_putc (md, (n >> 24)); /* 4 byte length header. */ + gcry_md_putc (md, (n >> 16)); + gcry_md_putc (md, (n >> 8)); + gcry_md_putc (md, (n )); + gcry_md_write (md, data_start, n); + memcpy (ki->fpr, gcry_md_read (md, 0), 32); + gcry_md_close (md); + } + ki->fprlen = 32; + memcpy (ki->keyid, ki->fpr, 8); } else { - err = gcry_md_open (&md, GCRY_MD_SHA1, 0); - if (err) - return err; /* Oops */ - gcry_md_putc (md, 0x99 ); /* CTB */ - gcry_md_putc (md, (n >> 8) ); /* 2 byte length header. */ - gcry_md_putc (md, n ); - gcry_md_write (md, data_start, n); - memcpy (ki->fpr, gcry_md_read (md, 0), 20); - gcry_md_close (md); + if ( 3 + n < sizeof hashbuffer ) + { + hashbuffer[0] = 0x99; /* CTB */ + hashbuffer[1] = (n >> 8); /* 2 byte length header. */ + hashbuffer[2] = (n ); + memcpy (hashbuffer + 3, data_start, n); + gcry_md_hash_buffer (GCRY_MD_SHA1, ki->fpr, hashbuffer, 3 + n); + } + else + { + err = gcry_md_open (&md, GCRY_MD_SHA1, 0); + if (err) + return err; /* Oops */ + gcry_md_putc (md, 0x99 ); /* CTB */ + gcry_md_putc (md, (n >> 8)); /* 2 byte length header. */ + gcry_md_putc (md, (n )); + gcry_md_write (md, data_start, n); + memcpy (ki->fpr, gcry_md_read (md, 0), 20); + gcry_md_close (md); + } + ki->fprlen = 20; + memcpy (ki->keyid, ki->fpr+12, 8); } - ki->fprlen = 20; - memcpy (ki->keyid, ki->fpr+12, 8); } - return 0; + leave: + for (i=0; i < npkey; i++) + xfree (helpmpibuf[i]); + + return err; } diff --git a/kbx/keybox-search-desc.h b/kbx/keybox-search-desc.h index 6298994e9..7286d2ae3 100644 --- a/kbx/keybox-search-desc.h +++ b/kbx/keybox-search-desc.h @@ -36,9 +36,7 @@ typedef enum { KEYDB_SEARCH_MODE_WORDS, KEYDB_SEARCH_MODE_SHORT_KID, KEYDB_SEARCH_MODE_LONG_KID, - KEYDB_SEARCH_MODE_FPR16, - KEYDB_SEARCH_MODE_FPR20, - KEYDB_SEARCH_MODE_FPR, + KEYDB_SEARCH_MODE_FPR, /* (Length of fpr in .fprlen) */ KEYDB_SEARCH_MODE_ISSUER, KEYDB_SEARCH_MODE_ISSUER_SN, KEYDB_SEARCH_MODE_SN, @@ -49,7 +47,7 @@ typedef enum { } KeydbSearchMode; -/* Forwward declaration. See g10/packet.h. */ +/* Forward declaration. See g10/packet.h. */ struct gpg_pkt_user_id_s; typedef struct gpg_pkt_user_id_s *gpg_pkt_user_id_t; @@ -69,10 +67,11 @@ struct keydb_search_desc int snlen; /* -1 := sn is a hex string */ union { const char *name; - unsigned char fpr[24]; + unsigned char fpr[32]; u32 kid[2]; /* Note that this is in native endianness. */ unsigned char grip[20]; } u; + byte fprlen; /* Only used with KEYDB_SEARCH_MODE_FPR. */ int exact; /* Use exactly this key ('!' suffix in gpg). */ }; diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c index e309cce98..101e1b5ea 100644 --- a/kbx/keybox-search.c +++ b/kbx/keybox-search.c @@ -66,18 +66,31 @@ blob_get_first_keyid (KEYBOXBLOB blob, u32 *kid) { const unsigned char *buffer; size_t length, nkeys, keyinfolen; + int fpr32; buffer = _keybox_get_blob_image (blob, &length); if (length < 48) return 0; /* blob too short */ + fpr32 = buffer[5] == 2; + if (fpr32 && length < 56) + return 0; /* blob to short */ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18); - if (!nkeys || keyinfolen < 28) + if (!nkeys || keyinfolen < (fpr32?56:28)) return 0; /* invalid blob */ - kid[0] = get32 (buffer + 32); - kid[1] = get32 (buffer + 36); + if (fpr32 && (get16 (buffer + 20 + 32) & 0x80)) + { + /* 32 byte fingerprint. */ + kid[0] = get32 (buffer + 20); + kid[1] = get32 (buffer + 20 + 4); + } + else /* 20 byte fingerprint. */ + { + kid[0] = get32 (buffer + 20 + 12); + kid[1] = get32 (buffer + 20 + 16); + } return 1; } @@ -229,22 +242,23 @@ blob_cmp_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen) For X.509 this is always 1, for OpenPGP this is 1 for the primary key and 2 and more for the subkeys. */ static int -blob_cmp_fpr (KEYBOXBLOB blob, const unsigned char *fpr) +blob_cmp_fpr (KEYBOXBLOB blob, const unsigned char *fpr, unsigned int fprlen) { const unsigned char *buffer; size_t length; size_t pos, off; size_t nkeys, keyinfolen; - int idx; + int idx, fpr32, storedfprlen; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ + fpr32 = buffer[5] == 2; /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); - if (keyinfolen < 28) + if (keyinfolen < (fpr32?56:28)) return 0; /* invalid blob */ pos = 20; if (pos + (uint64_t)keyinfolen*nkeys > (uint64_t)length) @@ -253,12 +267,19 @@ blob_cmp_fpr (KEYBOXBLOB blob, const unsigned char *fpr) for (idx=0; idx < nkeys; idx++) { off = pos + idx*keyinfolen; - if (!memcmp (buffer + off, fpr, 20)) + if (fpr32) + storedfprlen = (get16 (buffer + off + 32) & 0x80)? 32:20; + else + storedfprlen = 20; + if (storedfprlen == fprlen + && !memcmp (buffer + off, fpr, storedfprlen)) return idx+1; /* found */ } return 0; /* not found */ } + +/* Helper for has_short_kid and has_long_kid. */ static int blob_cmp_fpr_part (KEYBOXBLOB blob, const unsigned char *fpr, int fproff, int fprlen) @@ -267,25 +288,33 @@ blob_cmp_fpr_part (KEYBOXBLOB blob, const unsigned char *fpr, size_t length; size_t pos, off; size_t nkeys, keyinfolen; - int idx; + int idx, fpr32, storedfprlen; buffer = _keybox_get_blob_image (blob, &length); if (length < 40) return 0; /* blob too short */ + fpr32 = buffer[5] == 2; /*keys*/ nkeys = get16 (buffer + 16); keyinfolen = get16 (buffer + 18 ); - if (keyinfolen < 28) + if (keyinfolen < (fpr32?56:28)) return 0; /* invalid blob */ pos = 20; if (pos + (uint64_t)keyinfolen*nkeys > (uint64_t)length) return 0; /* out of bounds */ + if (fpr32) + fproff = 0; /* keyid are the high-order bits. */ for (idx=0; idx < nkeys; idx++) { off = pos + idx*keyinfolen; - if (!memcmp (buffer + off + fproff, fpr, fprlen)) + if (fpr32) + storedfprlen = (get16 (buffer + off + 32) & 0x80)? 32:20; + else + storedfprlen = 20; + if (storedfprlen == fproff + fprlen + && !memcmp (buffer + off + fproff, fpr, fprlen)) return idx+1; /* found */ } return 0; /* not found */ @@ -497,6 +526,58 @@ blob_cmp_mail (KEYBOXBLOB blob, const char *name, size_t namelen, int substr, } +/* Return true if the key in BLOB matches the 20 bytes keygrip GRIP. + * We don't have the keygrips as meta data, thus we need to parse the + * certificate. Fixme: We might want to return proper error codes + * instead of failing a search for invalid certificates etc. */ +static int +blob_openpgp_has_grip (KEYBOXBLOB blob, const unsigned char *grip) +{ + int rc = 0; + const unsigned char *buffer; + size_t length; + size_t cert_off, cert_len; + struct _keybox_openpgp_info info; + struct _keybox_openpgp_key_info *k; + + buffer = _keybox_get_blob_image (blob, &length); + if (length < 40) + return 0; /* Too short. */ + cert_off = get32 (buffer+8); + cert_len = get32 (buffer+12); + if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length) + return 0; /* Too short. */ + + if (_keybox_parse_openpgp (buffer + cert_off, cert_len, NULL, &info)) + return 0; /* Parse error. */ + + if (!memcmp (info.primary.grip, grip, 20)) + { + rc = 1; + goto leave; + } + + if (info.nsubkeys) + { + k = &info.subkeys; + do + { + if (!memcmp (k->grip, grip, 20)) + { + rc = 1; + goto leave; + } + k = k->next; + } + while (k); + } + + leave: + _keybox_destroy_openpgp_info (&info); + return rc; +} + + #ifdef KEYBOX_WITH_X509 /* Return true if the key in BLOB matches the 20 bytes keygrip GRIP. We don't have the keygrips as meta data, thus we need to parse the @@ -598,20 +679,19 @@ has_long_kid (KEYBOXBLOB blob, u32 mkid, u32 lkid) } static inline int -has_fingerprint (KEYBOXBLOB blob, const unsigned char *fpr) +has_fingerprint (KEYBOXBLOB blob, const unsigned char *fpr, unsigned int fprlen) { - return blob_cmp_fpr (blob, fpr); + return blob_cmp_fpr (blob, fpr, fprlen); } static inline int has_keygrip (KEYBOXBLOB blob, const unsigned char *grip) { + if (blob_get_type (blob) == KEYBOX_BLOBTYPE_PGP) + return blob_openpgp_has_grip (blob, grip); #ifdef KEYBOX_WITH_X509 if (blob_get_type (blob) == KEYBOX_BLOBTYPE_X509) return blob_x509_has_grip (blob, grip); -#else - (void)blob; - (void)grip; #endif return 0; } @@ -996,12 +1076,13 @@ keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc, if (pk_no) goto found; break; + case KEYDB_SEARCH_MODE_FPR: - case KEYDB_SEARCH_MODE_FPR20: - pk_no = has_fingerprint (blob, desc[n].u.fpr); + pk_no = has_fingerprint (blob, desc[n].u.fpr, desc[n].fprlen); if (pk_no) goto found; break; + case KEYDB_SEARCH_MODE_KEYGRIP: if (has_keygrip (blob, desc[n].u.grip)) goto found; @@ -1069,7 +1150,7 @@ keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc, /* Return the last found keyblock. Returns 0 on success and stores a - * new iobuf at R_IOBUF. R_UID_NO and R_PK_NO are used to retun the + * new iobuf at R_IOBUF. R_UID_NO and R_PK_NO are used to return the * number of the key or user id which was matched the search criteria; * if not known they are set to 0. */ gpg_error_t diff --git a/kbx/mkerrors b/kbx/mkerrors index 629485ae0..c0bca108c 100755 --- a/kbx/mkerrors +++ b/kbx/mkerrors @@ -29,7 +29,7 @@ cat <<EOF * keybox_strerror: * @err: Error code * - * This function returns a textual representaion of the given + * This function returns a textual representation of the given * errorcode. If this is an unknown value, a string with the value * is returned (Beware: it is hold in a static buffer). * diff --git a/m4/Makefile.am b/m4/Makefile.am index 3232413a5..250a4ac2c 100644 --- a/m4/Makefile.am +++ b/m4/Makefile.am @@ -1,6 +1,6 @@ EXTRA_DIST = intl.m4 intldir.m4 glibc2.m4 lock.m4 visibility.m4 intmax.m4 longdouble.m4 printf-posix.m4 signed.m4 size_max.m4 wchar_t.m4 wint_t.m4 xsize.m4 codeset.m4 gettext.m4 glibc21.m4 iconv.m4 intdiv0.m4 inttypes.m4 inttypes_h.m4 inttypes-pri.m4 isc-posix.m4 lcmessage.m4 lib-ld.m4 lib-link.m4 lib-prefix.m4 progtest.m4 stdint_h.m4 uintmax_t.m4 -EXTRA_DIST += ldap.m4 libcurl.m4 libusb.m4 tar-ustar.m4 readline.m4 pkg.m4 +EXTRA_DIST += ldap.m4 libcurl.m4 libusb.m4 readline.m4 pkg.m4 EXTRA_DIST += gnupg-pth.m4 diff --git a/m4/gpg-error.m4 b/m4/gpg-error.m4 index 1661204c2..a9d572fb5 100644 --- a/m4/gpg-error.m4 +++ b/m4/gpg-error.m4 @@ -1,5 +1,5 @@ # gpg-error.m4 - autoconf macro to detect libgpg-error. -# Copyright (C) 2002, 2003, 2004, 2011, 2014 g10 Code GmbH +# Copyright (C) 2002, 2003, 2004, 2011, 2014, 2018 g10 Code GmbH # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without @@ -9,7 +9,7 @@ # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # -# Last-changed: 2014-10-02 +# Last-changed: 2018-11-02 dnl AM_PATH_GPG_ERROR([MINIMUM-VERSION, @@ -61,16 +61,55 @@ AC_DEFUN([AM_PATH_GPG_ERROR], fi AC_PATH_PROG(GPG_ERROR_CONFIG, gpg-error-config, no) - min_gpg_error_version=ifelse([$1], ,0.0,$1) - AC_MSG_CHECKING(for GPG Error - version >= $min_gpg_error_version) + min_gpg_error_version=ifelse([$1], ,1.33,$1) ok=no - if test "$GPG_ERROR_CONFIG" != "no" \ - && test -f "$GPG_ERROR_CONFIG" ; then + + if test "$prefix" = NONE ; then + prefix_option_expanded=/usr/local + else + prefix_option_expanded="$prefix" + fi + if test "$exec_prefix" = NONE ; then + exec_prefix_option_expanded=$prefix_option_expanded + else + exec_prefix_option_expanded=$(prefix=$prefix_option_expanded eval echo $exec_prefix) + fi + libdir_option_expanded=$(prefix=$prefix_option_expanded exec_prefix=$exec_prefix_option_expanded eval echo $libdir) + + if test -f $libdir_option_expanded/pkgconfig/gpg-error.pc; then + gpgrt_libdir=$libdir_option_expanded + else + if crt1_path=$(${CC:-cc} -print-file-name=crt1.o 2>/dev/null); then + if possible_libdir=$(cd ${crt1_path%/*} && pwd 2>/dev/null); then + if test -f $possible_libdir/pkgconfig/gpg-error.pc; then + gpgrt_libdir=$possible_libdir + fi + fi + fi + fi + + if test "$GPG_ERROR_CONFIG" = "no" -a -n "$gpgrt_libdir"; then + AC_PATH_PROG(GPGRT_CONFIG, gpgrt-config, no) + if test "$GPGRT_CONFIG" = "no"; then + unset GPGRT_CONFIG + else + GPGRT_CONFIG="$GPGRT_CONFIG --libdir=$gpgrt_libdir" + if $GPGRT_CONFIG gpg-error >/dev/null 2>&1; then + GPG_ERROR_CONFIG="$GPGRT_CONFIG gpg-error" + AC_MSG_NOTICE([Use gpgrt-config with $gpgrt_libdir as gpg-error-config]) + gpg_error_config_version=`$GPG_ERROR_CONFIG --modversion` + else + unset GPGRT_CONFIG + fi + fi + else + gpg_error_config_version=`$GPG_ERROR_CONFIG --version` + fi + if test "$GPG_ERROR_CONFIG" != "no"; then req_major=`echo $min_gpg_error_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)/\1/'` req_minor=`echo $min_gpg_error_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` - gpg_error_config_version=`$GPG_ERROR_CONFIG $gpg_error_config_args --version` major=`echo $gpg_error_config_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` minor=`echo $gpg_error_config_version | \ @@ -84,23 +123,51 @@ AC_DEFUN([AM_PATH_GPG_ERROR], fi fi fi + if test -z "$GPGRT_CONFIG" -a -n "$gpgrt_libdir"; then + if test "$major" -gt 1 -o "$major" -eq 1 -a "$minor" -ge 33; then + AC_PATH_PROG(GPGRT_CONFIG, gpgrt-config, no) + if test "$GPGRT_CONFIG" = "no"; then + unset GPGRT_CONFIG + else + GPGRT_CONFIG="$GPGRT_CONFIG --libdir=$gpgrt_libdir" + if $GPGRT_CONFIG gpg-error >/dev/null 2>&1; then + GPG_ERROR_CONFIG="$GPGRT_CONFIG gpg-error" + AC_MSG_NOTICE([Use gpgrt-config with $gpgrt_libdir as gpg-error-config]) + else + unset GPGRT_CONFIG + fi + fi + fi + fi fi + AC_MSG_CHECKING(for GPG Error - version >= $min_gpg_error_version) if test $ok = yes; then - GPG_ERROR_CFLAGS=`$GPG_ERROR_CONFIG $gpg_error_config_args --cflags` - GPG_ERROR_LIBS=`$GPG_ERROR_CONFIG $gpg_error_config_args --libs` - GPG_ERROR_MT_CFLAGS=`$GPG_ERROR_CONFIG $gpg_error_config_args --mt --cflags 2>/dev/null` - GPG_ERROR_MT_LIBS=`$GPG_ERROR_CONFIG $gpg_error_config_args --mt --libs 2>/dev/null` + GPG_ERROR_CFLAGS=`$GPG_ERROR_CONFIG --cflags` + GPG_ERROR_LIBS=`$GPG_ERROR_CONFIG --libs` + if test -z "$GPGRT_CONFIG"; then + GPG_ERROR_MT_CFLAGS=`$GPG_ERROR_CONFIG --mt --cflags 2>/dev/null` + GPG_ERROR_MT_LIBS=`$GPG_ERROR_CONFIG --mt --libs 2>/dev/null` + else + GPG_ERROR_MT_CFLAGS=`$GPG_ERROR_CONFIG --variable=mtcflags 2>/dev/null` + GPG_ERROR_MT_CFLAGS="$GPG_ERROR_CFLAGS${GPG_ERROR_CFLAGS:+ }$GPG_ERROR_MT_CFLAGS" + GPG_ERROR_MT_LIBS=`$GPG_ERROR_CONFIG --variable=mtlibs 2>/dev/null` + GPG_ERROR_MT_LIBS="$GPG_ERROR_LIBS${GPG_ERROR_LIBS:+ }$GPG_ERROR_MT_LIBS" + fi AC_MSG_RESULT([yes ($gpg_error_config_version)]) ifelse([$2], , :, [$2]) - gpg_error_config_host=`$GPG_ERROR_CONFIG $gpg_error_config_args --host 2>/dev/null || echo none` + if test -z "$GPGRT_CONFIG"; then + gpg_error_config_host=`$GPG_ERROR_CONFIG --host 2>/dev/null || echo none` + else + gpg_error_config_host=`$GPG_ERROR_CONFIG --variable=host 2>/dev/null || echo none` + fi if test x"$gpg_error_config_host" != xnone ; then if test x"$gpg_error_config_host" != x"$host" ; then AC_MSG_WARN([[ *** -*** The config script $GPG_ERROR_CONFIG was +*** The config script "$GPG_ERROR_CONFIG" was *** built for $gpg_error_config_host and thus may not match the *** used host $host. -*** You may want to use the configure option --with-gpg-error-prefix +*** You may want to use the configure option --with-libgpg-error-prefix *** to specify a matching config script or use \$SYSROOT. ***]]) gpg_config_script_warn="$gpg_config_script_warn libgpg-error" diff --git a/m4/ksba.m4 b/m4/ksba.m4 index 3e14e6744..ad8de4f32 100644 --- a/m4/ksba.m4 +++ b/m4/ksba.m4 @@ -1,5 +1,5 @@ # ksba.m4 - autoconf macro to detect ksba -# Copyright (C) 2002 g10 Code GmbH +# Copyright (C) 2002, 2018 g10 Code GmbH # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without @@ -13,27 +13,47 @@ dnl AM_PATH_KSBA([MINIMUM-VERSION, dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) dnl Test for libksba and define KSBA_CFLAGS and KSBA_LIBS -dnl MINIMUM-VERSION is a string with the version number optionalliy prefixed +dnl MINIMUN-VERSION is a string with the version number optionalliy prefixed dnl with the API version to also check the API compatibility. Example: -dnl a MINIMUM-VERSION of 1:1.0.7 won't pass the test unless the installed +dnl a MINIMUN-VERSION of 1:1.0.7 won't pass the test unless the installed dnl version of libksba is at least 1.0.7 *and* the API number is 1. Using -dnl this feature prevents building against newer versions of libksba +dnl this features allows to prevent build against newer versions of libksba dnl with a changed API. dnl AC_DEFUN([AM_PATH_KSBA], -[AC_REQUIRE([AC_CANONICAL_HOST]) - AC_ARG_WITH(ksba-prefix, - AC_HELP_STRING([--with-ksba-prefix=PFX], - [prefix where KSBA is installed (optional)]), +[ AC_REQUIRE([AC_CANONICAL_HOST]) + dnl --with-libksba-prefix=PFX is the preferred name for this option, + dnl since that is consistent with how our three siblings use the directory/ + dnl package name in --with-$dir_name-prefix=PFX. + AC_ARG_WITH(libksba-prefix, + AC_HELP_STRING([--with-libksba-prefix=PFX], + [prefix where KSBA is installed (optional)]), ksba_config_prefix="$withval", ksba_config_prefix="") + + dnl Accept --with-ksba-prefix and make it work the same as + dnl --with-libksba-prefix above, for backwards compatibility, + dnl but do not document this old, inconsistently-named option. + AC_ARG_WITH(ksba-prefix,, + ksba_config_prefix="$withval", ksba_config_prefix="") + if test x$ksba_config_prefix != x ; then - ksba_config_args="$ksba_config_args --prefix=$ksba_config_prefix" - if test x${KSBA_CONFIG+set} != xset ; then - KSBA_CONFIG=$ksba_config_prefix/bin/ksba-config - fi + if test x${KSBA_CONFIG+set} != xset ; then + KSBA_CONFIG=$ksba_config_prefix/bin/ksba-config + fi + fi + + use_gpgrt_config="" + if test x"$KSBA_CONFIG" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then + if $GPGRT_CONFIG ksba --exists; then + KSBA_CONFIG="$GPGRT_CONFIG ksba" + AC_MSG_NOTICE([Use gpgrt-config as ksba-config]) + use_gpgrt_config=yes + fi + fi + if test -z "$use_gpgrt_config"; then + AC_PATH_PROG(KSBA_CONFIG, ksba-config, no) fi - AC_PATH_PROG(KSBA_CONFIG, ksba-config, no) tmp=ifelse([$1], ,1:1.0.0,$1) if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then req_ksba_api=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\1/'` @@ -52,7 +72,11 @@ AC_DEFUN([AM_PATH_KSBA], sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` req_micro=`echo $min_ksba_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'` - ksba_config_version=`$KSBA_CONFIG $ksba_config_args --version` + if test -z "$use_gpgrt_config"; then + ksba_config_version=`$KSBA_CONFIG --version` + else + ksba_config_version=`$KSBA_CONFIG --modversion` + fi major=`echo $ksba_config_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` minor=`echo $ksba_config_version | \ @@ -84,7 +108,11 @@ AC_DEFUN([AM_PATH_KSBA], # Even if we have a recent libksba, we should check that the # API is compatible. if test "$req_ksba_api" -gt 0 ; then - tmp=`$KSBA_CONFIG --api-version 2>/dev/null || echo 0` + if test -z "$use_gpgrt_config"; then + tmp=`$KSBA_CONFIG --api-version 2>/dev/null || echo 0` + else + tmp=`$KSBA_CONFIG --variable=api_version 2>/dev/null || echo 0` + fi if test "$tmp" -gt 0 ; then AC_MSG_CHECKING([KSBA API version]) if test "$req_ksba_api" -eq "$tmp" ; then @@ -97,15 +125,19 @@ AC_DEFUN([AM_PATH_KSBA], fi fi if test $ok = yes; then - KSBA_CFLAGS=`$KSBA_CONFIG $ksba_config_args --cflags` - KSBA_LIBS=`$KSBA_CONFIG $ksba_config_args --libs` + KSBA_CFLAGS=`$KSBA_CONFIG --cflags` + KSBA_LIBS=`$KSBA_CONFIG --libs` ifelse([$2], , :, [$2]) - libksba_config_host=`$LIBKSBA_CONFIG $ksba_config_args --host 2>/dev/null || echo none` + if test -z "$use_gpgrt_config"; then + libksba_config_host=`$KSBA_CONFIG --host 2>/dev/null || echo none` + else + libksba_config_host=`$KSBA_CONFIG --variable=host 2>/dev/null || echo none` + fi if test x"$libksba_config_host" != xnone ; then if test x"$libksba_config_host" != x"$host" ; then AC_MSG_WARN([[ *** -*** The config script $LIBKSBA_CONFIG was +*** The config script "$KSBA_CONFIG" was *** built for $libksba_config_host and thus may not match the *** used host $host. *** You may want to use the configure option --with-libksba-prefix diff --git a/m4/libassuan.m4 b/m4/libassuan.m4 index 004eee392..4af2d04f5 100644 --- a/m4/libassuan.m4 +++ b/m4/libassuan.m4 @@ -1,5 +1,5 @@ dnl Autoconf macros for libassuan -dnl Copyright (C) 2002, 2003 Free Software Foundation, Inc. +dnl Copyright (C) 2002, 2003, 2011 Free Software Foundation, Inc. dnl dnl This file is free software; as a special exception the author gives dnl unlimited permission to copy and/or distribute it, with or without @@ -8,23 +8,35 @@ dnl dnl This file is distributed in the hope that it will be useful, but dnl WITHOUT ANY WARRANTY, to the extent permitted by law; without even the dnl implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +dnl SPDX-License-Identifier: FSFULLR dnl dnl Common code used for libassuan detection [internal] dnl Returns ok set to yes or no. dnl AC_DEFUN([_AM_PATH_LIBASSUAN_COMMON], -[ AC_ARG_WITH(libassuan-prefix, +[ AC_REQUIRE([AC_CANONICAL_HOST]) + AC_ARG_WITH(libassuan-prefix, AC_HELP_STRING([--with-libassuan-prefix=PFX], [prefix where LIBASSUAN is installed (optional)]), libassuan_config_prefix="$withval", libassuan_config_prefix="") if test x$libassuan_config_prefix != x ; then - libassuan_config_args="$libassuan_config_args --prefix=$libassuan_config_prefix" if test x${LIBASSUAN_CONFIG+set} != xset ; then LIBASSUAN_CONFIG=$libassuan_config_prefix/bin/libassuan-config fi fi - AC_PATH_PROG(LIBASSUAN_CONFIG, libassuan-config, no) + + use_gpgrt_config="" + if test x"${LIBASSUAN_CONFIG}" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then + if $GPGRT_CONFIG libassuan --exists; then + LIBASSUAN_CONFIG="$GPGRT_CONFIG libassuan" + AC_MSG_NOTICE([Use gpgrt-config as libassuan-config]) + use_gpgrt_config=yes + fi + fi + if test -z "$use_gpgrt_config"; then + AC_PATH_PROG(LIBASSUAN_CONFIG, libassuan-config, no) + fi tmp=ifelse([$1], ,1:0.9.2,$1) if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then @@ -35,55 +47,60 @@ AC_DEFUN([_AM_PATH_LIBASSUAN_COMMON], min_libassuan_version="$tmp" fi - if test "$LIBASSUAN_CONFIG" != "no" ; then - libassuan_version=`$LIBASSUAN_CONFIG --version` - fi - libassuan_version_major=`echo $libassuan_version | \ - sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` - libassuan_version_minor=`echo $libassuan_version | \ - sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'` - libassuan_version_micro=`echo $libassuan_version | \ - sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\3/'` - - AC_MSG_CHECKING(for LIBASSUAN ifelse([$2], ,,[$2 ])- version >= $min_libassuan_version) + AC_MSG_CHECKING(for LIBASSUAN - version >= $min_libassuan_version) ok=no - if test "$LIBASSUAN_CONFIG" != "no" ; then - ifelse([$2], ,,[if `$LIBASSUAN_CONFIG --thread=$2 2> /dev/null` ; then]) + if test "$LIBASSUAN_CONFIG" != "no"; then req_major=`echo $min_libassuan_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\1/'` req_minor=`echo $min_libassuan_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` req_micro=`echo $min_libassuan_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'` - if test "$libassuan_version_major" -gt "$req_major"; then + + if test -z "$use_gpgrt_config"; then + libassuan_config_version=`$LIBASSUAN_CONFIG --version` + else + libassuan_config_version=`$LIBASSUAN_CONFIG --modversion` + fi + major=`echo $libassuan_config_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` + minor=`echo $libassuan_config_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'` + micro=`echo $libassuan_config_version | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\3/'` + + if test "$major" -gt "$req_major"; then ok=yes - else - if test "$libassuan_version_major" -eq "$req_major"; then - if test "$libassuan_version_minor" -gt "$req_minor"; then + else + if test "$major" -eq "$req_major"; then + if test "$minor" -gt "$req_minor"; then ok=yes else - if test "$libassuan_version_minor" -eq "$req_minor"; then - if test "$libassuan_version_micro" -ge "$req_micro"; then + if test "$minor" -eq "$req_minor"; then + if test "$micro" -ge "$req_micro"; then ok=yes fi fi fi fi fi - ifelse([$2], ,,[fi]) fi if test $ok = yes; then - AC_MSG_RESULT([yes ($libassuan_version)]) + AC_MSG_RESULT([yes ($libassuan_config_version)]) else AC_MSG_RESULT(no) fi if test $ok = yes; then if test "$req_libassuan_api" -gt 0 ; then - tmp=`$LIBASSUAN_CONFIG --api-version 2>/dev/null || echo 0` + if test -z "$use_gpgrt_config"; then + tmp=`$LIBASSUAN_CONFIG --api-version 2>/dev/null || echo 0` + else + tmp=`$LIBASSUAN_CONFIG --variable=api_version 2>/dev/null || echo 0` + fi if test "$tmp" -gt 0 ; then - AC_MSG_CHECKING([LIBASSUAN ifelse([$2], ,,[$2 ])API version]) + AC_MSG_CHECKING([LIBASSUAN API version]) if test "$req_libassuan_api" -eq "$tmp" ; then AC_MSG_RESULT(okay) else @@ -94,6 +111,27 @@ AC_DEFUN([_AM_PATH_LIBASSUAN_COMMON], fi fi + if test $ok = yes; then + if test x"$host" != x ; then + if test -z "$use_gpgrt_config"; then + libassuan_config_host=`$LIBASSUAN_CONFIG --host 2>/dev/null || echo none` + else + libassuan_config_host=`$LIBASSUAN_CONFIG --variable=host 2>/dev/null || echo none` + fi + if test x"$libassuan_config_host" != xnone ; then + if test x"$libassuan_config_host" != x"$host" ; then + AC_MSG_WARN([[ +*** +*** The config script "$LIBASSUAN_CONFIG" was +*** built for $libassuan_config_host and thus may not match the +*** used host $host. +*** You may want to use the configure option --with-libassuan-prefix +*** to specify a matching config script. +***]]) + fi + fi + fi + fi ]) dnl AM_CHECK_LIBASSUAN([MINIMUM-VERSION, @@ -120,8 +158,8 @@ dnl AC_DEFUN([AM_PATH_LIBASSUAN], [ _AM_PATH_LIBASSUAN_COMMON($1) if test $ok = yes; then - LIBASSUAN_CFLAGS=`$LIBASSUAN_CONFIG $libassuan_config_args --cflags` - LIBASSUAN_LIBS=`$LIBASSUAN_CONFIG $libassuan_config_args --libs` + LIBASSUAN_CFLAGS=`$LIBASSUAN_CONFIG --cflags` + LIBASSUAN_LIBS=`$LIBASSUAN_CONFIG --libs` ifelse([$2], , :, [$2]) else LIBASSUAN_CFLAGS="" @@ -131,45 +169,3 @@ AC_DEFUN([AM_PATH_LIBASSUAN], AC_SUBST(LIBASSUAN_CFLAGS) AC_SUBST(LIBASSUAN_LIBS) ]) - - -dnl AM_PATH_LIBASSUAN_PTH([MINIMUM-VERSION, -dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) -dnl Test for libassuan and define LIBASSUAN_PTH_CFLAGS and LIBASSUAN_PTH_LIBS -dnl -AC_DEFUN([AM_PATH_LIBASSUAN_PTH], -[ _AM_PATH_LIBASSUAN_COMMON($1,pth) - if test $ok = yes; then - LIBASSUAN_PTH_CFLAGS=`$LIBASSUAN_CONFIG $libassuan_config_args --thread=pth --cflags` - LIBASSUAN_PTH_LIBS=`$LIBASSUAN_CONFIG $libassuan_config_args --thread=pth --libs` - ifelse([$2], , :, [$2]) - else - LIBASSUAN_PTH_CFLAGS="" - LIBASSUAN_PTH_LIBS="" - ifelse([$3], , :, [$3]) - fi - AC_SUBST(LIBASSUAN_PTH_CFLAGS) - AC_SUBST(LIBASSUAN_PTH_LIBS) -]) - - -dnl AM_PATH_LIBASSUAN_PTHREAD([MINIMUM-VERSION, -dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) -dnl Test for libassuan and define LIBASSUAN_PTHREAD_CFLAGS -dnl and LIBASSUAN_PTHREAD_LIBS -dnl -AC_DEFUN([AM_PATH_LIBASSUAN_PTHREAD], -[ _AM_PATH_LIBASSUAN_COMMON($1,pthread) - if test $ok = yes; then - LIBASSUAN_PTHREAD_CFLAGS=`$LIBASSUAN_CONFIG $libassuan_config_args --thread=pthread --cflags` - LIBASSUAN_PTHREAD_LIBS=`$LIBASSUAN_CONFIG $libassuan_config_args --thread=pthread --libs` - ifelse([$2], , :, [$2]) - else - LIBASSUAN_PTHREAD_CFLAGS="" - LIBASSUAN_PTHREAD_LIBS="" - ifelse([$3], , :, [$3]) - fi - AC_SUBST(LIBASSUAN_PTHREAD_CFLAGS) - AC_SUBST(LIBASSUAN_PTHREAD_LIBS) -]) - diff --git a/m4/libgcrypt.m4 b/m4/libgcrypt.m4 index d89fe1137..37dfbea24 100644 --- a/m4/libgcrypt.m4 +++ b/m4/libgcrypt.m4 @@ -1,5 +1,5 @@ # libgcrypt.m4 - Autoconf macros to detect libgcrypt -# Copyright (C) 2002, 2003, 2004, 2011, 2014 g10 Code GmbH +# Copyright (C) 2002, 2003, 2004, 2011, 2014, 2018 g10 Code GmbH # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without @@ -9,17 +9,17 @@ # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # -# Last-changed: 2014-10-02 +# Last-changed: 2018-11-13 dnl AM_PATH_LIBGCRYPT([MINIMUM-VERSION, dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) dnl Test for libgcrypt and define LIBGCRYPT_CFLAGS and LIBGCRYPT_LIBS. -dnl MINIMUM-VERSION is a string with the version number optionalliy prefixed +dnl MINIMUN-VERSION is a string with the version number optionalliy prefixed dnl with the API version to also check the API compatibility. Example: -dnl a MINIMUM-VERSION of 1:1.2.5 won't pass the test unless the installed +dnl a MINIMUN-VERSION of 1:1.2.5 won't pass the test unless the installed dnl version of libgcrypt is at least 1.2.5 *and* the API number is 1. Using -dnl this feature prevents building against newer versions of libgcrypt +dnl this features allows to prevent build against newer versions of libgcrypt dnl with a changed API. dnl dnl If a prefix option is not used, the config script is first @@ -36,8 +36,20 @@ AC_DEFUN([AM_PATH_LIBGCRYPT], if test x"${LIBGCRYPT_CONFIG}" = x ; then if test x"${libgcrypt_config_prefix}" != x ; then LIBGCRYPT_CONFIG="${libgcrypt_config_prefix}/bin/libgcrypt-config" - else - case "${SYSROOT}" in + fi + fi + + use_gpgrt_config="" + if test x"${LIBGCRYPT_CONFIG}" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then + if $GPGRT_CONFIG libgcrypt --exists; then + LIBGCRYPT_CONFIG="$GPGRT_CONFIG libgcrypt" + AC_MSG_NOTICE([Use gpgrt-config as libgcrypt-config]) + use_gpgrt_config=yes + fi + fi + if test -z "$use_gpgrt_config"; then + if test x"${LIBGCRYPT_CONFIG}" = x ; then + case "${SYSROOT}" in /*) if test -x "${SYSROOT}/bin/libgcrypt-config" ; then LIBGCRYPT_CONFIG="${SYSROOT}/bin/libgcrypt-config" @@ -48,11 +60,11 @@ AC_DEFUN([AM_PATH_LIBGCRYPT], *) AC_MSG_WARN([Ignoring \$SYSROOT as it is not an absolute path.]) ;; - esac - fi + esac + fi + AC_PATH_PROG(LIBGCRYPT_CONFIG, libgcrypt-config, no) fi - AC_PATH_PROG(LIBGCRYPT_CONFIG, libgcrypt-config, no) tmp=ifelse([$1], ,1:1.2.0,$1) if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then req_libgcrypt_api=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\1/'` @@ -71,7 +83,11 @@ AC_DEFUN([AM_PATH_LIBGCRYPT], sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` req_micro=`echo $min_libgcrypt_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'` - libgcrypt_config_version=`$LIBGCRYPT_CONFIG --version` + if test -z "$use_gpgrt_config"; then + libgcrypt_config_version=`$LIBGCRYPT_CONFIG --version` + else + libgcrypt_config_version=`$LIBGCRYPT_CONFIG --modversion` + fi major=`echo $libgcrypt_config_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` minor=`echo $libgcrypt_config_version | \ @@ -103,7 +119,11 @@ AC_DEFUN([AM_PATH_LIBGCRYPT], # If we have a recent libgcrypt, we should also check that the # API is compatible if test "$req_libgcrypt_api" -gt 0 ; then - tmp=`$LIBGCRYPT_CONFIG --api-version 2>/dev/null || echo 0` + if test -z "$use_gpgrt_config"; then + tmp=`$LIBGCRYPT_CONFIG --api-version 2>/dev/null || echo 0` + else + tmp=`$LIBGCRYPT_CONFIG --variable=api_version 2>/dev/null || echo 0` + fi if test "$tmp" -gt 0 ; then AC_MSG_CHECKING([LIBGCRYPT API version]) if test "$req_libgcrypt_api" -eq "$tmp" ; then @@ -119,12 +139,16 @@ AC_DEFUN([AM_PATH_LIBGCRYPT], LIBGCRYPT_CFLAGS=`$LIBGCRYPT_CONFIG --cflags` LIBGCRYPT_LIBS=`$LIBGCRYPT_CONFIG --libs` ifelse([$2], , :, [$2]) - libgcrypt_config_host=`$LIBGCRYPT_CONFIG --host 2>/dev/null || echo none` + if test -z "$use_gpgrt_config"; then + libgcrypt_config_host=`$LIBGCRYPT_CONFIG --host 2>/dev/null || echo none` + else + libgcrypt_config_host=`$LIBGCRYPT_CONFIG --variable=host 2>/dev/null || echo none` + fi if test x"$libgcrypt_config_host" != xnone ; then if test x"$libgcrypt_config_host" != x"$host" ; then AC_MSG_WARN([[ *** -*** The config script $LIBGCRYPT_CONFIG was +*** The config script "$LIBGCRYPT_CONFIG" was *** built for $libgcrypt_config_host and thus may not match the *** used host $host. *** You may want to use the configure option --with-libgcrypt-prefix diff --git a/m4/npth.m4 b/m4/npth.m4 index 17c264491..7d3e73f00 100644 --- a/m4/npth.m4 +++ b/m4/npth.m4 @@ -1,5 +1,5 @@ # npth.m4 - autoconf macro to detect NPTH. -# Copyright (C) 2002, 2003, 2004, 2011 g10 Code GmbH +# Copyright (C) 2002, 2003, 2004, 2011, 2018 g10 Code GmbH # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without @@ -17,10 +17,25 @@ AC_DEFUN([_AM_PATH_NPTH_CONFIG], if test "x$npth_config_prefix" != x ; then NPTH_CONFIG="$npth_config_prefix/bin/npth-config" fi - AC_PATH_PROG(NPTH_CONFIG, npth-config, no) + + use_gpgrt_config="" + if test x"$NPTH_CONFIG" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then + if $GPGRT_CONFIG npth --exists; then + NPTH_CONFIG="$GPGRT_CONFIG npth" + AC_MSG_NOTICE([Use gpgrt-config as npth-config]) + use_gpgrt_config=yes + fi + fi + if test -z "$use_gpgrt_config"; then + AC_PATH_PROG(NPTH_CONFIG, npth-config, no) + fi if test "$NPTH_CONFIG" != "no" ; then - npth_version=`$NPTH_CONFIG --version` + if test -z "$use_gpgrt_config"; then + npth_version=`$NPTH_CONFIG --version` + else + npth_version=`$NPTH_CONFIG --modversion` + fi fi npth_version_major=`echo $npth_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` @@ -70,31 +85,39 @@ AC_DEFUN([AM_PATH_NPTH], AC_MSG_RESULT(no) fi if test $ok = yes; then - # If we have a recent NPTH, we should also check that the - # API is compatible. - if test "$req_npth_api" -gt 0 ; then + # If we have a recent NPTH, we should also check that the + # API is compatible. + if test "$req_npth_api" -gt 0 ; then + if test -z "$use_gpgrt_config"; then tmp=`$NPTH_CONFIG --api-version 2>/dev/null || echo 0` - if test "$tmp" -gt 0 ; then - AC_MSG_CHECKING([NPTH API version]) - if test "$req_npth_api" -eq "$tmp" ; then - AC_MSG_RESULT([okay]) - else - ok=no - AC_MSG_RESULT([does not match. want=$req_npth_api got=$tmp]) - fi + else + tmp=`$NPTH_CONFIG --variable=api_version 2>/dev/null || echo 0` + fi + if test "$tmp" -gt 0 ; then + AC_MSG_CHECKING([NPTH API version]) + if test "$req_npth_api" -eq "$tmp" ; then + AC_MSG_RESULT([okay]) + else + ok=no + AC_MSG_RESULT([does not match. want=$req_npth_api got=$tmp]) fi - fi + fi + fi fi if test $ok = yes; then NPTH_CFLAGS=`$NPTH_CONFIG --cflags` NPTH_LIBS=`$NPTH_CONFIG --libs` ifelse([$2], , :, [$2]) - npth_config_host=`$NPTH_CONFIG --host 2>/dev/null || echo none` + if test -z "$use_gpgrt_config"; then + npth_config_host=`$NPTH_CONFIG --host 2>/dev/null || echo none` + else + npth_config_host=`$NPTH_CONFIG --variable=host 2>/dev/null || echo none` + fi if test x"$npth_config_host" != xnone ; then if test x"$npth_config_host" != x"$host" ; then AC_MSG_WARN([[ *** -*** The config script $NPTH_CONFIG was +*** The config script "$NPTH_CONFIG" was *** built for $npth_config_host and thus may not match the *** used host $host. *** You may want to use the configure option --with-npth-prefix diff --git a/m4/ntbtls.m4 b/m4/ntbtls.m4 index 0a30d9200..18b43d939 100644 --- a/m4/ntbtls.m4 +++ b/m4/ntbtls.m4 @@ -8,17 +8,19 @@ dnl dnl This file is distributed in the hope that it will be useful, but dnl WITHOUT ANY WARRANTY, to the extent permitted by law; without even the dnl implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +dnl +dnl Last-changed: 2018-11-13 dnl AM_PATH_NTBTLS([MINIMUM-VERSION, dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) dnl dnl Test for NTBTLS and define NTBTLS_CFLAGS and NTBTLS_LIBS. -dnl MINIMUM-VERSION is a string with the version number optionalliy prefixed +dnl MINIMUN-VERSION is a string with the version number optionalliy prefixed dnl with the API version to also check the API compatibility. Example: -dnl a MINIMUM-VERSION of 1:1.2.5 won't pass the test unless the installed -dnl version of ntbtls is at least 1.2.5 *and* the API number is 1. Using -dnl this feature prevents building against newer versions of ntbtls +dnl a MINIMUN-VERSION of 1:1.2.5 won't pass the test unless the installed +dnl version of libgcrypt is at least 1.2.5 *and* the API number is 1. Using +dnl this features allows to prevent build against newer versions of libgcrypt dnl with a changed API. dnl AC_DEFUN([AM_PATH_NTBTLS], @@ -30,23 +32,35 @@ AC_DEFUN([AM_PATH_NTBTLS], if test x"${NTBTLS_CONFIG}" = x ; then if test x"${ntbtls_config_prefix}" != x ; then NTBTLS_CONFIG="${ntbtls_config_prefix}/bin/ntbtls-config" - else - case "${SYSROOT}" in - /*) - if test -x "${SYSROOT}/bin/ntbtls-config" ; then - NTBTLS_CONFIG="${SYSROOT}/bin/ntbtls-config" - fi - ;; - '') - ;; - *) - AC_MSG_WARN([Ignoring \$SYSROOT as it is not an absolute path.]) - ;; - esac fi fi - AC_PATH_PROG(NTBTLS_CONFIG, ntbtls-config, no) + use_gpgrt_config="" + if test x"${NTBTLS_CONFIG}" = x -a x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then + if $GPGRT_CONFIG ntbtls --exists; then + NTBTLS_CONFIG="$GPGRT_CONFIG ntbtls" + AC_MSG_NOTICE([Use gpgrt-config as ntbtls-config]) + use_gpgrt_config=yes + fi + fi + if test -z "$use_gpgrt_config"; then + if test x"${NTBTLS_CONFIG}" = x ; then + case "${SYSROOT}" in + /*) + if test -x "${SYSROOT}/bin/ntbtls-config" ; then + NTBTLS_CONFIG="${SYSROOT}/bin/ntbtls-config" + fi + ;; + '') + ;; + *) + AC_MSG_WARN([Ignoring \$SYSROOT as it is not an absolute path.]) + ;; + esac + fi + AC_PATH_PROG(NTBTLS_CONFIG, ntbtls-config, no) + fi + tmp=ifelse([$1], ,1:1.0.0,$1) if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then req_ntbtls_api=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\1/'` @@ -65,7 +79,11 @@ AC_DEFUN([AM_PATH_NTBTLS], sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` req_micro=`echo $min_ntbtls_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'` - ntbtls_config_version=`$NTBTLS_CONFIG --version` + if test -z "$use_gpgrt_config"; then + ntbtls_config_version=`$NTBTLS_CONFIG --version` + else + ntbtls_config_version=`$NTBTLS_CONFIG --modversion` + fi major=`echo $ntbtls_config_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` minor=`echo $ntbtls_config_version | \ @@ -97,7 +115,11 @@ AC_DEFUN([AM_PATH_NTBTLS], # If we have a recent ntbtls, we should also check that the # API is compatible if test "$req_ntbtls_api" -gt 0 ; then - tmp=`$NTBTLS_CONFIG --api-version 2>/dev/null || echo 0` + if test -z "$use_gpgrt_config"; then + tmp=`$NTBTLS_CONFIG --api-version 2>/dev/null || echo 0` + else + tmp=`$NTBTLS_CONFIG --variable=api_version 2>/dev/null || echo 0` + fi if test "$tmp" -gt 0 ; then AC_MSG_CHECKING([NTBTLS API version]) if test "$req_ntbtls_api" -eq "$tmp" ; then @@ -113,12 +135,16 @@ AC_DEFUN([AM_PATH_NTBTLS], NTBTLS_CFLAGS=`$NTBTLS_CONFIG --cflags` NTBTLS_LIBS=`$NTBTLS_CONFIG --libs` ifelse([$2], , :, [$2]) - ntbtls_config_host=`$NTBTLS_CONFIG --host 2>/dev/null || echo none` + if test -z "$use_gpgrt_config"; then + ntbtls_config_host=`$NTBTLS_CONFIG --host 2>/dev/null || echo none` + else + ntbtls_config_host=`$NTBTLS_CONFIG --variable=host 2>/dev/null || echo none` + fi if test x"$ntbtls_config_host" != xnone ; then if test x"$ntbtls_config_host" != x"$host" ; then AC_MSG_WARN([[ *** -*** The config script $NTBTLS_CONFIG was +*** The config script "$NTBTLS_CONFIG" was *** built for $ntbtls_config_host and thus may not match the *** used host $host. *** You may want to use the configure option --with-ntbtls-prefix diff --git a/m4/tar-ustar.m4 b/m4/tar-ustar.m4 deleted file mode 100644 index 4ae9e63aa..000000000 --- a/m4/tar-ustar.m4 +++ /dev/null @@ -1,43 +0,0 @@ -dnl Check for a tar program that speaks ustar format -dnl Copyright (C) 2005, 2006 Free Software Foundation, Inc. -dnl -dnl This file is free software, distributed under the terms of the GNU -dnl General Public License. As a special exception to the GNU General -dnl Public License, this file may be distributed as part of a program -dnl that contains a configuration script generated by Autoconf, under -dnl the same distribution terms as the rest of that program. - -AC_DEFUN([GNUPG_CHECK_USTAR], -[ - AC_ARG_WITH(tar, - AC_HELP_STRING([--with-tar=PATH],[look for a tar program in PATH]), - [_do_tar=$withval]) - - if test x$_do_tar != xno ; then - - if test x$_do_tar = x ; then - AC_PATH_PROG(TAR,"tar") - _mytar=$ac_cv_path_TAR - fi - - # Check if our tar is ustar format. If so, it's good. TODO: Add some - # code to check various options, etc, to try and create ustar - # format. - - if test x$_mytar != x ; then - AC_MSG_CHECKING([whether $_mytar speaks USTAR]) - echo hithere > conftest.txt - $_mytar -cf - conftest.txt | (dd skip=257 bs=1 count=5 2>/dev/null || cat) | grep ustar > /dev/null - _tar_bad=$? - rm conftest.txt - - if test x$_tar_bad = x0 ; then - AC_MSG_RESULT([yes]) - else - AC_MSG_RESULT([no]) - fi - fi - fi - - AM_CONDITIONAL(HAVE_USTAR, test x$_tar_bad = x0) -])dnl diff --git a/po/Makevars b/po/Makevars index 60a6fb36e..d5318a9b0 100644 --- a/po/Makevars +++ b/po/Makevars @@ -62,8 +62,10 @@ XGETTEXT_OPTIONS = \ --flag=ks_printf_help:2:c-format \ --flag=print_further_info:1:c-format \ --flag=write_status_printf:2:c-format \ + --flag=gnupg_printf_status:2:c-format \ --flag=kbxd_print_status:3:c-format \ --flag=gpgconf_write_status:2:c-format \ + --flag=send_status_printf:3:c-format \ --flag=wks_write_status:2:c-format diff --git a/po/POTFILES.in b/po/POTFILES.in index fe8d45f7e..da5581168 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -127,4 +127,8 @@ tools/gpgconf.c tools/no-libgcrypt.c tools/symcryptrun.c tools/gpg-check-pattern.c - +tools/gpg-card.c +tools/card-misc.c +tools/card-keys.c +tools/card-yubikey.c +tools/card-call-scd.c @@ -3180,7 +3180,7 @@ msgid "Really sign all text user IDs? (y/N) " msgstr "Er du sikker pÃ¥ at du vil signerere alle bruker-id-er? (j/N) " msgid "Really sign all user IDs? (y/N) " -msgstr "Er du sikekr pÃ¥ at du vil signerere alle bruker-id-er? (j/N) " +msgstr "Er du sikker pÃ¥ at du vil signerere alle bruker-id-er? (j/N) " msgid "Hint: Select the user IDs to sign\n" msgstr "Tips: Velg bruker-id-en(e) du vil signere\n" diff --git a/scd/Makefile.am b/scd/Makefile.am index cbd1f9f0f..0cc50dca8 100644 --- a/scd/Makefile.am +++ b/scd/Makefile.am @@ -33,7 +33,8 @@ AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \ $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) -card_apps = app-openpgp.c app-nks.c app-dinsig.c app-p15.c app-geldkarte.c app-sc-hsm.c +card_apps = app-openpgp.c app-piv.c app-nks.c app-dinsig.c app-p15.c \ + app-geldkarte.c app-sc-hsm.c scdaemon_SOURCES = \ scdaemon.c scdaemon.h \ diff --git a/scd/apdu.c b/scd/apdu.c index ce7f41f61..816938ac5 100644 --- a/scd/apdu.c +++ b/scd/apdu.c @@ -105,6 +105,7 @@ struct reader_table_s { int (*check_pinpad)(int, int, pininfo_t *); void (*dump_status_reader)(int); int (*set_progress_cb)(int, gcry_handler_progress_t, void*); + int (*set_prompt_cb)(int, void (*) (void *, int), void*); int (*pinpad_verify)(int, int, int, int, int, pininfo_t *); int (*pinpad_modify)(int, int, int, int, int, pininfo_t *); @@ -444,6 +445,7 @@ new_reader_slot (void) reader_table[reader].check_pinpad = check_pcsc_pinpad; reader_table[reader].dump_status_reader = NULL; reader_table[reader].set_progress_cb = NULL; + reader_table[reader].set_prompt_cb = NULL; reader_table[reader].pinpad_verify = pcsc_pinpad_verify; reader_table[reader].pinpad_modify = pcsc_pinpad_modify; @@ -1404,6 +1406,14 @@ set_progress_cb_ccid_reader (int slot, gcry_handler_progress_t cb, void *cb_arg) return ccid_set_progress_cb (slotp->ccid.handle, cb, cb_arg); } +static int +set_prompt_cb_ccid_reader (int slot, void (*cb) (void *, int ), void *cb_arg) +{ + reader_table_t slotp = reader_table + slot; + + return ccid_set_prompt_cb (slotp->ccid.handle, cb, cb_arg); +} + static int get_status_ccid (int slot, unsigned int *status, int on_wire) @@ -1543,6 +1553,7 @@ open_ccid_reader (struct dev_list *dl) reader_table[slot].check_pinpad = check_ccid_pinpad; reader_table[slot].dump_status_reader = dump_ccid_reader_status; reader_table[slot].set_progress_cb = set_progress_cb_ccid_reader; + reader_table[slot].set_prompt_cb = set_prompt_cb_ccid_reader; reader_table[slot].pinpad_verify = ccid_pinpad_operation; reader_table[slot].pinpad_modify = ccid_pinpad_operation; /* Our CCID reader code does not support T=0 at all, thus reset the @@ -2382,6 +2393,29 @@ apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg) } +int +apdu_set_prompt_cb (int slot, void (*cb) (void *, int), void *cb_arg) +{ + int sw; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (reader_table[slot].set_prompt_cb) + { + sw = lock_slot (slot); + if (!sw) + { + sw = reader_table[slot].set_prompt_cb (slot, cb, cb_arg); + unlock_slot (slot); + } + } + else + sw = 0; + return sw; +} + + /* Do a reset for the card in reader at SLOT. */ int apdu_reset (int slot) @@ -2612,7 +2646,7 @@ send_apdu (int slot, unsigned char *apdu, size_t apdulen, } -/* Core APDU tranceiver function. Parameters are described at +/* Core APDU transceiver function. Parameters are described at apdu_send_le with the exception of PININFO which indicates pinpad related operations if not NULL. If EXTENDED_MODE is not 0 command chaining or extended length will be used according to these @@ -3029,19 +3063,25 @@ apdu_send_simple (int slot, int extended_mode, /* This is a more generic version of the apdu sending routine. It - takes an already formatted APDU in APDUDATA or length APDUDATALEN - and returns with an APDU including the status word. With - HANDLE_MORE set to true this function will handle the MORE DATA - status and return all APDUs concatenated with one status word at - the end. If EXTENDED_LENGTH is != 0 extended lengths are allowed - with a max. result data length of EXTENDED_LENGTH bytes. The - function does not return a regular status word but 0 on success. - If the slot is locked, the function returns immediately with an - error. */ + * takes an already formatted APDU in APDUDATA or length APDUDATALEN + * and returns with an APDU including the status word. With + * HANDLE_MORE set to true this function will handle the MORE DATA + * status and return all APDUs concatenated with one status word at + * the end. If EXTENDED_LENGTH is != 0 extended lengths are allowed + * with a max. result data length of EXTENDED_LENGTH bytes. The + * function does not return a regular status word but 0 on success. + * If the slot is locked, the function returns immediately with an + * error. + * + * Out of historical reasons the function returns 0 on success and + * outs the status word at the end of the result to be able to get the + * status word in the case of a not provided RETBUF, R_SW can be used + * to store the SW. But note that R_SW qill only be set if the + * function returns 0. */ int apdu_send_direct (int slot, size_t extended_length, const unsigned char *apdudata, size_t apdudatalen, - int handle_more, + int handle_more, unsigned int *r_sw, unsigned char **retbuf, size_t *retbuflen) { #define SHORT_RESULT_BUFFER_SIZE 258 @@ -3248,9 +3288,13 @@ apdu_send_direct (int slot, size_t extended_length, (*retbuf)[(*retbuflen)++] = sw; } + if (r_sw) + *r_sw = sw; + if (DBG_CARD_IO && retbuf) log_printhex (*retbuf, *retbuflen, " dump: "); + return 0; } diff --git a/scd/apdu.h b/scd/apdu.h index 8a0d4bda8..89df45cb8 100644 --- a/scd/apdu.h +++ b/scd/apdu.h @@ -31,6 +31,7 @@ enum { SW_EOF_REACHED = 0x6282, SW_TERM_STATE = 0x6285, /* Selected file is in termination state. */ SW_EEPROM_FAILURE = 0x6581, + SW_ACK_TIMEOUT = 0x6600, /* OpenPGPcard: Ack timeout. */ SW_WRONG_LENGTH = 0x6700, SW_SM_NOT_SUP = 0x6882, /* Secure Messaging is not supported. */ SW_CC_NOT_SUP = 0x6884, /* Command Chaining is not supported. */ @@ -117,6 +118,7 @@ int apdu_connect (int slot); int apdu_disconnect (int slot); int apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg); +int apdu_set_prompt_cb (int slot, void (*cb) (void *, int), void *cb_arg); int apdu_reset (int slot); int apdu_get_status (int slot, int hang, unsigned int *status); @@ -137,7 +139,7 @@ int apdu_send_le (int slot, int extended_mode, unsigned char **retbuf, size_t *retbuflen); int apdu_send_direct (int slot, size_t extended_length, const unsigned char *apdudata, size_t apdudatalen, - int handle_more, + int handle_more, unsigned int *r_sw, unsigned char **retbuf, size_t *retbuflen); const char *apdu_get_reader_name (int slot); diff --git a/scd/app-common.h b/scd/app-common.h index 38e6cc609..3df896228 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -25,9 +25,16 @@ #include <npth.h> #include <ksba.h> +/* Flags used with app_change_pin. */ +#define APP_CHANGE_FLAG_RESET 1 /* PIN Reset mode. */ +#define APP_CHANGE_FLAG_NULLPIN 2 /* NULL PIN mode. */ +#define APP_CHANGE_FLAG_CLEAR 4 /* Clear the given PIN. */ -#define APP_CHANGE_FLAG_RESET 1 -#define APP_CHANGE_FLAG_NULLPIN 2 +/* Flags used with app_genkey. */ +#define APP_GENKEY_FLAG_FORCE 1 /* Force overwriting existing key. */ + +/* Flags used with app_writekey. */ +#define APP_WRITEKEY_FLAG_FORCE 1 /* Force overwriting existing key. */ /* Bit flags set by the decipher function into R_INFO. */ #define APP_DECIPHER_INFO_NOPAD 1 /* Padding has been removed. */ @@ -51,8 +58,10 @@ struct app_ctx_s { unsigned char *serialno; /* Serialnumber in raw form, allocated. */ size_t serialnolen; /* Length in octets of serialnumber. */ + const char *cardtype; /* NULL or string with the token's type. */ const char *apptype; - unsigned int card_version; + unsigned int cardversion;/* Firmware version of the token or 0. */ + unsigned int appversion; /* Version of the application or 0. */ unsigned int card_status; unsigned int reset_requested:1; unsigned int periodical_check_needed:1; @@ -66,7 +75,7 @@ struct app_ctx_s { gpg_error_t (*learn_status) (app_t app, ctrl_t ctrl, unsigned int flags); gpg_error_t (*readcert) (app_t app, const char *certid, unsigned char **cert, size_t *certlen); - gpg_error_t (*readkey) (app_t app, int advanced, const char *certid, + gpg_error_t (*readkey) (app_t app, const char *certid, unsigned char **pk, size_t *pklen); gpg_error_t (*getattr) (app_t app, ctrl_t ctrl, const char *name); gpg_error_t (*setattr) (app_t app, const char *name, @@ -101,8 +110,8 @@ struct app_ctx_s { void *pincb_arg, const unsigned char *pk, size_t pklen); gpg_error_t (*genkey) (app_t app, ctrl_t ctrl, - const char *keynostr, unsigned int flags, - time_t createtime, + const char *keyref, const char *keytype, + unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t (*change_pin) (app_t app, ctrl_t ctrl, @@ -118,6 +127,8 @@ struct app_ctx_s { /*-- app-help.c --*/ unsigned int app_help_count_bits (const unsigned char *a, size_t len); gpg_error_t app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip); +gpg_error_t app_help_pubkey_from_cert (const void *cert, size_t certlen, + unsigned char **r_pk, size_t *r_pklen); size_t app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff); @@ -139,7 +150,7 @@ gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags); gpg_error_t app_readcert (app_t app, ctrl_t ctrl, const char *certid, unsigned char **cert, size_t *certlen); -gpg_error_t app_readkey (app_t app, ctrl_t ctrl, int advanced, +gpg_error_t app_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned char **pk, size_t *pklen); gpg_error_t app_getattr (app_t app, ctrl_t ctrl, const char *name); gpg_error_t app_setattr (app_t app, ctrl_t ctrl, const char *name, @@ -173,16 +184,16 @@ gpg_error_t app_writekey (app_t app, ctrl_t ctrl, void *pincb_arg, const unsigned char *keydata, size_t keydatalen); gpg_error_t app_genkey (app_t app, ctrl_t ctrl, - const char *keynostr, unsigned int flags, - time_t createtime, + const char *keynostr, const char *keytype, + unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer); gpg_error_t app_change_pin (app_t app, ctrl_t ctrl, - const char *chvnostr, int reset_mode, - gpg_error_t (*pincb)(void*, const char *, char **), - void *pincb_arg); + const char *chvnostr, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); gpg_error_t app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); @@ -206,5 +217,8 @@ gpg_error_t app_select_geldkarte (app_t app); /*-- app-sc-hsm.c --*/ gpg_error_t app_select_sc_hsm (app_t app); +/*-- app-piv.c --*/ +gpg_error_t app_select_piv (app_t app); + #endif /*GNUPG_SCD_APP_COMMON_H*/ diff --git a/scd/app-dinsig.c b/scd/app-dinsig.c index bea285687..983bed6e1 100644 --- a/scd/app-dinsig.c +++ b/scd/app-dinsig.c @@ -415,7 +415,7 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, && indatalen != (15+20) && indatalen != (19+32)) return gpg_error (GPG_ERR_INV_VALUE); - /* Check that the provided ID is vaid. This is not really needed + /* Check that the provided ID is valid. This is not really needed but we do it to enforce correct usage by the caller. */ if (strncmp (keyidstr, "DINSIG.", 7) ) return gpg_error (GPG_ERR_INV_ID); diff --git a/scd/app-geldkarte.c b/scd/app-geldkarte.c index 510beb550..85bcedc4f 100644 --- a/scd/app-geldkarte.c +++ b/scd/app-geldkarte.c @@ -254,7 +254,7 @@ copy_bcd (const unsigned char *string, size_t length) } -/* Convert the BCD number at STING of LENGTH into an integer and store +/* Convert the BCD number at STRING of LENGTH into an integer and store that at RESULT. Return 0 on success. */ static gpg_error_t bcd_to_int (const unsigned char *string, size_t length, int *result) diff --git a/scd/app-help.c b/scd/app-help.c index 842a73d5a..f0f551c55 100644 --- a/scd/app-help.c +++ b/scd/app-help.c @@ -29,9 +29,9 @@ #include "../common/tlv.h" -/* Count the number of bits, assuming the A represents an unsigned big - integer of length LEN bytes. If A is NULL a length of 0 is - returned. */ +/* Count the number of bits, assuming that A represents an unsigned + * big integer of length LEN bytes. If A is NULL a length of 0 is + * returned. */ unsigned int app_help_count_bits (const unsigned char *a, size_t len) { @@ -87,6 +87,45 @@ app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip) } +gpg_error_t +app_help_pubkey_from_cert (const void *cert, size_t certlen, + unsigned char **r_pk, size_t *r_pklen) +{ + gpg_error_t err; + ksba_cert_t kc; + unsigned char *pk; + size_t pklen; + + *r_pk = NULL; + *r_pklen = 0; + + err = ksba_cert_new (&kc); + if (err) + return err; + + err = ksba_cert_init_from_mem (kc, cert, certlen); + if (err) + goto leave; + + pk = ksba_cert_get_public_key (kc); + if (!pk) + { + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + pklen = gcry_sexp_canon_len (pk, 0, NULL, &err); + + leave: + if (!err) + { + *r_pk = pk; + *r_pklen = pklen; + } + else + ksba_free (pk); + ksba_cert_release (kc); + return err; +} /* Given the SLOT and the File ID FID, return the length of the certificate contained in that file. Returns 0 if the file does not diff --git a/scd/app-nks.c b/scd/app-nks.c index 9e720f0b0..40c941616 100644 --- a/scd/app-nks.c +++ b/scd/app-nks.c @@ -273,7 +273,7 @@ get_chv_status (app_t app, int sigg, int pwid) command[3] = pwid; if (apdu_send_direct (app->slot, 0, (unsigned char *)command, - 4, 0, &result, &resultlen)) + 4, 0, NULL, &result, &resultlen)) rc = -1; /* Error. */ else if (resultlen < 2) rc = -1; /* Error. */ @@ -618,17 +618,13 @@ do_readcert (app_t app, const char *certid, certificate parsing code in commands.c:cmd_readkey. For internal use PK and PKLEN may be NULL to just check for an existing key. */ static gpg_error_t -do_readkey (app_t app, int advanced, const char *keyid, - unsigned char **pk, size_t *pklen) +do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) { gpg_error_t err; unsigned char *buffer[2]; size_t buflen[2]; unsigned short path[1] = { 0x4500 }; - if (advanced) - return GPG_ERR_NOT_SUPPORTED; - /* We use a generic name to retrieve PK.AUT.IFD-SPK. */ if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3) ; @@ -702,7 +698,7 @@ do_writekey (app_t app, ctrl_t ctrl, else return gpg_error (GPG_ERR_INV_ID); - if (!force && !do_readkey (app, 0, keyid, NULL, NULL)) + if (!force && !do_readkey (app, keyid, NULL, NULL)) return gpg_error (GPG_ERR_EEXIST); /* Parse the S-expression. */ @@ -1169,6 +1165,9 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr, if (!newdesc) return gpg_error (GPG_ERR_INV_ID); + if ((flags & APP_CHANGE_FLAG_CLEAR)) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = switch_application (app, is_sigg); if (err) return err; @@ -1300,7 +1299,7 @@ get_nks_version (int slot) int type; if (iso7816_apdu_direct (slot, "\x80\xaa\x06\x00\x00", 5, 0, - &result, &resultlen)) + NULL, &result, &resultlen)) return 2; /* NKS 2 does not support this command. */ /* Example value: 04 11 19 22 21 6A 20 80 03 03 01 01 01 00 00 00 diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index c17452555..1e904b578 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -119,8 +119,11 @@ static struct { { 0x0104, 0, 0, 0, 0, 0, 0, 2, "Private DO 4"}, { 0x7F21, 1, 0, 1, 0, 0, 0, 1, "Cardholder certificate"}, /* V3.0 */ - { 0x7F74, 0, 0, 1, 0, 0, 0, 0, "General Feature Management"}, + { 0x7F74, 0, 0x6E, 1, 0, 0, 0, 0, "General Feature Management"}, { 0x00D5, 0, 0, 1, 0, 0, 0, 0, "AES key data"}, + { 0x00D6, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Signature"}, + { 0x00D7, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Decryption"}, + { 0x00D8, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Authentication"}, { 0x00F9, 0, 0, 1, 0, 0, 0, 0, "KDF data object"}, { 0 } }; @@ -473,7 +476,7 @@ get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes, for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) ; - if (app->card_version > 0x0100 && data_objects[i].get_immediate_in_v11) + if (app->appversion > 0x0100 && data_objects[i].get_immediate_in_v11) { exmode = 0; rc = iso7816_get_data (app->slot, exmode, tag, &buffer, &buflen); @@ -640,7 +643,7 @@ count_bits (const unsigned char *a, size_t len) Where FLAGS is a plain hexadecimal number representing flag values. The lsb is here the rightmost bit. Defined flags bits are: - Bit 0 = CHV1 and CHV2 are not syncronized + Bit 0 = CHV1 and CHV2 are not synchronized Bit 1 = CHV2 has been set to the default PIN of "123456" (this implies that bit 0 is also set). @@ -813,7 +816,7 @@ store_fpr (app_t app, int keynumber, u32 timestamp, unsigned char *fpr, xfree (buffer); - tag = (app->card_version > 0x0007? 0xC7 : 0xC6) + keynumber; + tag = (app->appversion > 0x0007? 0xC7 : 0xC6) + keynumber; flush_cache_item (app, 0xC5); tag2 = 0xCE + keynumber; flush_cache_item (app, 0xCD); @@ -822,7 +825,7 @@ store_fpr (app_t app, int keynumber, u32 timestamp, unsigned char *fpr, if (rc) log_error (_("failed to store the fingerprint: %s\n"),gpg_strerror (rc)); - if (!rc && app->card_version > 0x0100) + if (!rc && app->appversion > 0x0100) { unsigned char buf[4]; @@ -985,6 +988,9 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) { "PRIVATE-DO-4", 0x0104 }, { "$AUTHKEYID", 0x0000, -3 }, { "$DISPSERIALNO",0x0000, -4 }, + { "UIF-1", 0x00D6, 0 }, + { "UIF-2", 0x00D7, 0 }, + { "UIF-3", 0x00D8, 0 }, { "KDF", 0x00F9 }, { NULL, 0 } }; @@ -1442,13 +1448,13 @@ ecdh_params (const char *curve) /* See RFC-6637 for those constants. 0x03: Number of bytes 0x01: Version for this parameter format - KDF algo - KEK algo + KEK digest algorithm + KEK cipher algorithm */ if (nbits <= 256) return (const unsigned char*)"\x03\x01\x08\x07"; else if (nbits <= 384) - return (const unsigned char*)"\x03\x01\x09\x08"; + return (const unsigned char*)"\x03\x01\x09\x09"; else return (const unsigned char*)"\x03\x01\x0a\x09"; } @@ -1649,7 +1655,7 @@ get_public_key (app_t app, int keyno) m = e = NULL; /* (avoid cc warning) */ - if (app->card_version > 0x0100) + if (app->appversion > 0x0100) { int exmode, le_value; @@ -1779,6 +1785,7 @@ send_keypair_info (app_t app, ctrl_t ctrl, int key) unsigned char grip[20]; char gripstr[41]; char idbuf[50]; + const char *usage; err = get_public_key (app, keyno); if (err) @@ -1796,10 +1803,19 @@ send_keypair_info (app_t app, ctrl_t ctrl, int key) bin2hex (grip, 20, gripstr); + switch (keyno) + { + case 0: usage = "sc"; break; + case 1: usage = "e"; break; + case 2: usage = "sa"; break; + default: usage = ""; break; + } + sprintf (idbuf, "OPENPGP.%d", keyno+1); send_status_info (ctrl, "KEYPAIRINFO", gripstr, 40, idbuf, strlen (idbuf), + usage, strlen (usage), NULL, (size_t)0); leave: @@ -1822,11 +1838,19 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) do_getattr (app, ctrl, "PUBKEY-URL"); do_getattr (app, ctrl, "LOGIN-DATA"); do_getattr (app, ctrl, "KEY-FPR"); - if (app->card_version > 0x0100) + if (app->appversion > 0x0100) do_getattr (app, ctrl, "KEY-TIME"); do_getattr (app, ctrl, "CA-FPR"); do_getattr (app, ctrl, "CHV-STATUS"); do_getattr (app, ctrl, "SIG-COUNTER"); + if (app->app_local->extcap.kdf_do) + do_getattr (app, ctrl, "KDF"); + if (app->app_local->extcap.has_button) + { + do_getattr (app, ctrl, "UIF-1"); + do_getattr (app, ctrl, "UIF-2"); + do_getattr (app, ctrl, "UIF-3"); + } if (app->app_local->extcap.private_dos) { do_getattr (app, ctrl, "PRIVATE-DO-1"); @@ -1851,10 +1875,8 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) buffer. On error PK and PKLEN are not changed and an error code is returned. */ static gpg_error_t -do_readkey (app_t app, int advanced, const char *keyid, - unsigned char **pk, size_t *pklen) +do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) { -#if GNUPG_MAJOR_VERSION > 1 gpg_error_t err; int keyno; unsigned char *buf; @@ -1876,45 +1898,17 @@ do_readkey (app_t app, int advanced, const char *keyid, if (!buf) return gpg_error (GPG_ERR_NO_PUBKEY); - if (advanced) - { - gcry_sexp_t s_key; - - err = gcry_sexp_new (&s_key, buf, app->app_local->pk[keyno].keylen, 0); - if (err) - return err; - - *pklen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0); - *pk = xtrymalloc (*pklen); - if (!*pk) - { - err = gpg_error_from_syserror (); - *pklen = 0; - return err; - } - - gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, *pk, *pklen); - gcry_sexp_release (s_key); - /* Decrement for trailing '\0' */ - *pklen = *pklen - 1; - } - else + *pklen = app->app_local->pk[keyno].keylen; + *pk = xtrymalloc (*pklen); + if (!*pk) { - *pklen = app->app_local->pk[keyno].keylen; - *pk = xtrymalloc (*pklen); - if (!*pk) - { - err = gpg_error_from_syserror (); - *pklen = 0; - return err; - } - memcpy (*pk, buf, *pklen); + err = gpg_error_from_syserror (); + *pklen = 0; + return err; } + memcpy (*pk, buf, *pklen); return 0; -#else - return gpg_error (GPG_ERR_NOT_IMPLEMENTED); -#endif } /* Read the standard certificate of an OpenPGP v2 card. It is @@ -2437,29 +2431,33 @@ do_setattr (app_t app, const char *name, static struct { const char *name; int tag; + int flush_tag; /* The tag which needs to be flushed or 0. */ int need_chv; int special; unsigned int need_v2:1; } table[] = { - { "DISP-NAME", 0x005B, 3 }, - { "LOGIN-DATA", 0x005E, 3, 2 }, - { "DISP-LANG", 0x5F2D, 3 }, - { "DISP-SEX", 0x5F35, 3 }, - { "PUBKEY-URL", 0x5F50, 3 }, - { "CHV-STATUS-1", 0x00C4, 3, 1 }, - { "CA-FPR-1", 0x00CA, 3 }, - { "CA-FPR-2", 0x00CB, 3 }, - { "CA-FPR-3", 0x00CC, 3 }, - { "PRIVATE-DO-1", 0x0101, 2 }, - { "PRIVATE-DO-2", 0x0102, 3 }, - { "PRIVATE-DO-3", 0x0103, 2 }, - { "PRIVATE-DO-4", 0x0104, 3 }, - { "CERT-3", 0x7F21, 3, 0, 1 }, - { "SM-KEY-ENC", 0x00D1, 3, 0, 1 }, - { "SM-KEY-MAC", 0x00D2, 3, 0, 1 }, - { "KEY-ATTR", 0, 0, 3, 1 }, - { "AESKEY", 0x00D5, 3, 0, 1 }, - { "KDF", 0x00F9, 3, 4, 1 }, + { "DISP-NAME", 0x005B, 0, 3 }, + { "LOGIN-DATA", 0x005E, 0, 3, 2 }, + { "DISP-LANG", 0x5F2D, 0, 3 }, + { "DISP-SEX", 0x5F35, 0, 3 }, + { "PUBKEY-URL", 0x5F50, 0, 3 }, + { "CHV-STATUS-1", 0x00C4, 0, 3, 1 }, + { "CA-FPR-1", 0x00CA, 0x00C6, 3 }, + { "CA-FPR-2", 0x00CB, 0x00C6, 3 }, + { "CA-FPR-3", 0x00CC, 0x00C6, 3 }, + { "PRIVATE-DO-1", 0x0101, 0, 2 }, + { "PRIVATE-DO-2", 0x0102, 0, 3 }, + { "PRIVATE-DO-3", 0x0103, 0, 2 }, + { "PRIVATE-DO-4", 0x0104, 0, 3 }, + { "CERT-3", 0x7F21, 0, 3, 0, 1 }, + { "SM-KEY-ENC", 0x00D1, 0, 3, 0, 1 }, + { "SM-KEY-MAC", 0x00D2, 0, 3, 0, 1 }, + { "KEY-ATTR", 0, 0, 0, 3, 1 }, + { "AESKEY", 0x00D5, 0, 3, 0, 1 }, + { "UIF-1", 0x00D6, 0, 3, 5, 1 }, + { "UIF-2", 0x00D7, 0, 3, 5, 1 }, + { "UIF-3", 0x00D8, 0, 3, 5, 1 }, + { "KDF", 0x00F9, 0, 3, 4, 1 }, { NULL, 0 } }; int exmode; @@ -2471,6 +2469,9 @@ do_setattr (app_t app, const char *name, if (table[idx].need_v2 && !app->app_local->extcap.is_v2) return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Not yet supported. */ + if (table[idx].special == 5 && app->app_local->extcap.has_button == 0) + return gpg_error (GPG_ERR_INV_OBJ); + if (table[idx].special == 3) return change_keyattr_from_string (app, pincb, pincb_arg, value, valuelen); @@ -2491,7 +2492,8 @@ do_setattr (app_t app, const char *name, /* Flush the cache before writing it, so that the next get operation will reread the data from the card and thus get synced in case of errors (e.g. data truncated by the card). */ - flush_cache_item (app, table[idx].tag); + flush_cache_item (app, table[idx].flush_tag? table[idx].flush_tag + /* */ : table[idx].tag); if (app->app_local->cardcap.ext_lc_le && valuelen > 254) exmode = 1; /* Use extended length w/o a limit. */ @@ -2518,10 +2520,10 @@ do_setattr (app_t app, const char *name, } -/* Handle the WRITECERT command for OpenPGP. This rites the standard - certifciate to the card; CERTID needs to be set to "OPENPGP.3". - PINCB and PINCB_ARG are the usual arguments for the pinentry - callback. */ +/* Handle the WRITECERT command for OpenPGP. This writes the standard + * certificate to the card; CERTID needs to be set to "OPENPGP.3". + * PINCB and PINCB_ARG are the usual arguments for the pinentry + * callback. */ static gpg_error_t do_writecert (app_t app, ctrl_t ctrl, const char *certidstr, @@ -2546,6 +2548,42 @@ do_writecert (app_t app, ctrl_t ctrl, } +static gpg_error_t +clear_chv_status (app_t app, int chvno) +{ + unsigned char apdu[4]; + gpg_error_t err; + + if (!app->app_local->extcap.is_v2) + return GPG_ERR_UNSUPPORTED_OPERATION; + + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0xff; + apdu[3] = 0x80+chvno; + + err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_INV_VALUE) + err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + return err; + } + + if (chvno == 1) + { + apdu[3]++; + err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); + app->did_chv1 = app->did_chv2 = 0; + } + else if (chvno == 2) + app->did_chv2 = 0; + else if (chvno == 3) + app->did_chv3 = 0; + + return err; +} + /* Handle the PASSWD command. The following combinations are possible: @@ -2561,6 +2599,8 @@ do_writecert (app_t app, ctrl_t ctrl, - 2 1 Verify CHV2 and set a new CHV1 and CHV2. - 2 2 Verify Reset Code and set a new PW1. - 3 any Verify CHV3/PW3 and set a new CHV3/PW3. + + The CHVNO can be prefixed with "OPENPGP.". */ static gpg_error_t do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, @@ -2569,7 +2609,7 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, void *pincb_arg) { int rc = 0; - int chvno = atoi (chvnostr); + int chvno; char *resetcode = NULL; char *oldpinvalue = NULL; char *pinvalue = NULL; @@ -2582,10 +2622,25 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int pinlen = 0; (void)ctrl; + + if (digitp (chvnostr)) + chvno = atoi (chvnostr); + else if (!ascii_strcasecmp (chvnostr, "OPENPGP.1")) + chvno = 1; + else if (!ascii_strcasecmp (chvnostr, "OPENPGP.2")) + chvno = 2; + else if (!ascii_strcasecmp (chvnostr, "OPENPGP.3")) + chvno = 3; + else + return gpg_error (GPG_ERR_INV_ID); + memset (&pininfo, 0, sizeof pininfo); pininfo.fixedlen = -1; pininfo.minlen = minlen; + if ((flags & APP_CHANGE_FLAG_CLEAR)) + return clear_chv_status (app, chvno); + if (reset_mode && chvno == 3) { rc = gpg_error (GPG_ERR_INV_ID); @@ -2839,10 +2894,10 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */ } else - { + { rc = pin2hash_if_kdf (app, chvno, oldpinvalue, &pinlen0); if (!rc) - rc = pin2hash_if_kdf (app, chvno, pinvalue, &pinlen); + rc = pin2hash_if_kdf (app, chvno, pinvalue, &pinlen); if (!rc) rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, oldpinvalue, pinlen0, @@ -3644,7 +3699,7 @@ rsa_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), /* Store the key. */ err = iso7816_put_data (app->slot, 0, - (app->card_version > 0x0007? 0xE0:0xE9)+keyno, + (app->appversion > 0x0007? 0xE0:0xE9)+keyno, template, template_len); } if (err) @@ -4016,8 +4071,8 @@ do_writekey (app_t app, ctrl_t ctrl, /* Handle the GENKEY command. */ static gpg_error_t -do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, - time_t createtime, +do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, const char *keytype, + unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { @@ -4033,6 +4088,8 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, int exmode = 0; int le_value = 256; /* Use legacy value. */ + (void)keytype; /* Ignored for OpenPGP cards. */ + if (keyno < 0 || keyno > 2) return gpg_error (GPG_ERR_INV_ID); @@ -4081,7 +4138,7 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, log_info (_("please wait while key is being generated ...\n")); start_at = time (NULL); - err = iso7816_generate_keypair (app->slot, exmode, + err = iso7816_generate_keypair (app->slot, exmode, 0x80, 0, (keyno == 0? "\xB6" : keyno == 1? "\xB8" : "\xA4"), 2, le_value, &buffer, &buflen); @@ -4381,7 +4438,7 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, log_info (_("signatures created so far: %lu\n"), sigcount); /* Check CHV if needed. */ - if (!app->did_chv1 || app->force_chv1 ) + if (!app->did_chv1 || app->force_chv1) { char *pinvalue; int pinlen; @@ -4429,6 +4486,11 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, } rc = iso7816_compute_ds (app->slot, exmode, data, datalen, le_value, outdata, outdatalen); + if (gpg_err_code (rc) == GPG_ERR_TIMEOUT) + clear_chv_status (app, 1); + else if (!rc && app->force_chv1) + app->did_chv1 = 0; + return rc; } @@ -4535,6 +4597,8 @@ do_auth (app_t app, const char *keyidstr, rc = iso7816_internal_authenticate (app->slot, exmode, indata, indatalen, le_value, outdata, outdatalen); + if (gpg_err_code (rc) == GPG_ERR_TIMEOUT) + clear_chv_status (app, 1); } return rc; } @@ -4758,7 +4822,7 @@ do_decipher (app_t app, const char *keyidstr, indata, indatalen, le_value, padind, outdata, outdatalen); xfree (fixbuf); - if (app->app_local->keyattr[1].key_type == KEY_TYPE_ECC) + if (!rc && app->app_local->keyattr[1].key_type == KEY_TYPE_ECC) { unsigned char prefix = 0; @@ -4782,10 +4846,12 @@ do_decipher (app_t app, const char *keyidstr, *outdatalen = *outdatalen + 1; } } + if (gpg_err_code (rc) == GPG_ERR_TIMEOUT) + clear_chv_status (app, 1); if (gpg_err_code (rc) == GPG_ERR_CARD /* actual SW is 0x640a */ && app->app_local->manufacturer == 5 - && app->card_version == 0x0200) + && app->appversion == 0x0200) log_info ("NOTE: Cards with manufacturer id 5 and s/n <= 346 (0x15a)" " do not work with encryption keys > 2048 bits\n"); @@ -5144,8 +5210,8 @@ app_select_openpgp (app_t app) log_printhex (buffer, buflen, ""); } - app->card_version = buffer[6] << 8; - app->card_version |= buffer[7]; + app->appversion = buffer[6] << 8; + app->appversion |= buffer[7]; manufacturer = (buffer[8]<<8 | buffer[9]); xfree (app->serialno); @@ -5161,10 +5227,10 @@ app_select_openpgp (app_t app) app->app_local->manufacturer = manufacturer; - if (app->card_version >= 0x0200) + if (app->appversion >= 0x0200) app->app_local->extcap.is_v2 = 1; - if (app->card_version >= 0x0300) + if (app->appversion >= 0x0300) app->app_local->extcap.extcap_v3 = 1; /* Read the historical bytes. */ @@ -5231,7 +5297,7 @@ app_select_openpgp (app_t app) /* Some of the first cards accidentally don't set the CHANGE_FORCE_CHV bit but allow it anyway. */ - if (app->card_version <= 0x0100 && manufacturer == 1) + if (app->appversion <= 0x0100 && manufacturer == 1) app->app_local->extcap.change_force_chv = 1; /* Check optional DO of "General Feature Management" for button. */ diff --git a/scd/app-piv.c b/scd/app-piv.c new file mode 100644 index 000000000..5748c70b8 --- /dev/null +++ b/scd/app-piv.c @@ -0,0 +1,3350 @@ +/* app-piv.c - The OpenPGP card application. + * Copyright (C) 2019 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +/* Some notes: + * - Specs for PIV are at http://dx.doi.org/10.6028/NIST.SP.800-73-4 + * + * - Access control matrix: + * | Action | 9B | PIN | PUK | | + * |--------------+-----+-----+-----+------------------------------| + * | Generate key | yes | | | | + * | Change 9B | yes | | | | + * | Change retry | yes | yes | | Yubikey only | + * | Import key | yes | | | | + * | Import cert | yes | | | | + * | Change CHUID | yes | | | | + * | Reset card | | | | PIN and PUK in blocked state | + * | Verify PIN | | yes | | | + * | Sign data | | yes | | | + * | Decrypt data | | yes | | | + * | Change PIN | | yes | | | + * | Change PUK | | | yes | | + * | Unblock PIN | | | yes | New PIN required | + * |---------------------------------------------------------------| + * (9B indicates the 24 byte PIV Card Application Administration Key) + * + * - When generating a key we store the created public key in the + * corresponding data object, so that gpg and gpgsm are able to get + * the public key, create a certificate and store that then in that + * data object. That is not standard compliant but due to the use + * of other tags, it should not harm. See do_genkey for the actual + * used tag structure. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <time.h> + +#include "scdaemon.h" + +#include "../common/util.h" +#include "../common/i18n.h" +#include "iso7816.h" +#include "app-common.h" +#include "../common/tlv.h" +#include "../common/host2net.h" +#include "apdu.h" /* We use apdu_send_direct. */ + +#define PIV_ALGORITHM_3DES_ECB_0 0x00 +#define PIV_ALGORITHM_2DES_ECB 0x01 +#define PIV_ALGORITHM_2DES_CBC 0x02 +#define PIV_ALGORITHM_3DES_ECB 0x03 +#define PIV_ALGORITHM_3DES_CBC 0x04 +#define PIV_ALGORITHM_RSA 0x07 +#define PIV_ALGORITHM_AES128_ECB 0x08 +#define PIV_ALGORITHM_AES128_CBC 0x09 +#define PIV_ALGORITHM_AES192_ECB 0x0A +#define PIV_ALGORITHM_AES192_CBC 0x0B +#define PIV_ALGORITHM_AES256_ECB 0x0C +#define PIV_ALGORITHM_AES256_CBC 0x0D +#define PIV_ALGORITHM_ECC_P256 0x11 +#define PIV_ALGORITHM_ECC_P384 0x14 + + + +/* A table describing the DOs of a PIV card. */ +struct data_object_s +{ + unsigned int tag; + unsigned int mandatory:1; + unsigned int acr_contact:2; /* 0=always, 1=VCI, 2=PIN, 3=PINorOCC */ + unsigned int acr_contactless:2; /* 0=always, 1=VCI, 2=VCIandPIN, + 3=VCIand(PINorOCC) */ + unsigned int dont_cache:1; /* Data item will not be cached. */ + unsigned int flush_on_error:1; /* Flush cached item on error. */ + unsigned int keypair:1; /* Has a public key for a keypair. */ + const char keyref[3]; /* The key reference. */ + const char *oidsuffix; /* Suffix of the OID. */ + const char *usage; /* Usage string for a keypair or NULL. */ + const char *desc; /* Description of the DO. */ +}; +typedef struct data_object_s *data_object_t; +static struct data_object_s data_objects[] = { + { 0x5FC107, 1, 0,1, 0,0, 0, "", "1.219.0", NULL, + "Card Capability Container"}, + { 0x5FC102, 1, 0,0, 0,0, 0, "", "2.48.0", NULL, + "Cardholder Unique Id" }, + { 0x5FC105, 1, 0,1, 0,0, 1, "9A", "2.1.1", "a", + "Cert PIV Authentication" }, + { 0x5FC103, 1, 2,2, 0,0, 0, "", "2.96.16", NULL, + "Cardholder Fingerprints" }, + { 0x5FC106, 1, 0,1, 0,0, 0, "", "2.144.0", NULL, + "Security Object" }, + { 0x5FC108, 1, 2,2, 0,0, 0, "", "2.96.48", NULL, + "Cardholder Facial Image" }, + { 0x5FC101, 1, 0,0, 0,0, 1, "9E", "2.5.0", "a", + "Cert Card Authentication"}, + { 0x5FC10A, 0, 0,1, 0,0, 1, "9C", "2.1.0", "sc", + "Cert Digital Signature" }, + { 0x5FC10B, 0, 0,1, 0,0, 1, "9D", "2.1.2", "e", + "Cert Key Management" }, + { 0x5FC109, 0, 3,3, 0,0, 0, "", "2.48.1", NULL, + "Printed Information" }, + { 0x7E, 0, 0,0, 0,0, 0, "", "2.96.80", NULL, + "Discovery Object" }, + { 0x5FC10C, 0, 0,1, 0,0, 0, "", "2.96.96", NULL, + "Key History Object" }, + { 0x5FC10D, 0, 0,1, 0,0, 0, "82", "2.16.1", "e", + "Retired Cert Key Mgm 1" }, + { 0x5FC10E, 0, 0,1, 0,0, 0, "83", "2.16.2", "e", + "Retired Cert Key Mgm 2" }, + { 0x5FC10F, 0, 0,1, 0,0, 0, "84", "2.16.3", "e", + "Retired Cert Key Mgm 3" }, + { 0x5FC110, 0, 0,1, 0,0, 0, "85", "2.16.4", "e", + "Retired Cert Key Mgm 4" }, + { 0x5FC111, 0, 0,1, 0,0, 0, "86", "2.16.5", "e", + "Retired Cert Key Mgm 5" }, + { 0x5FC112, 0, 0,1, 0,0, 0, "87", "2.16.6", "e", + "Retired Cert Key Mgm 6" }, + { 0x5FC113, 0, 0,1, 0,0, 0, "88", "2.16.7", "e", + "Retired Cert Key Mgm 7" }, + { 0x5FC114, 0, 0,1, 0,0, 0, "89", "2.16.8", "e", + "Retired Cert Key Mgm 8" }, + { 0x5FC115, 0, 0,1, 0,0, 0, "8A", "2.16.9", "e", + "Retired Cert Key Mgm 9" }, + { 0x5FC116, 0, 0,1, 0,0, 0, "8B", "2.16.10", "e", + "Retired Cert Key Mgm 10" }, + { 0x5FC117, 0, 0,1, 0,0, 0, "8C", "2.16.11", "e", + "Retired Cert Key Mgm 11" }, + { 0x5FC118, 0, 0,1, 0,0, 0, "8D", "2.16.12", "e", + "Retired Cert Key Mgm 12" }, + { 0x5FC119, 0, 0,1, 0,0, 0, "8E", "2.16.13", "e", + "Retired Cert Key Mgm 13" }, + { 0x5FC11A, 0, 0,1, 0,0, 0, "8F", "2.16.14", "e", + "Retired Cert Key Mgm 14" }, + { 0x5FC11B, 0, 0,1, 0,0, 0, "90", "2.16.15", "e", + "Retired Cert Key Mgm 15" }, + { 0x5FC11C, 0, 0,1, 0,0, 0, "91", "2.16.16", "e", + "Retired Cert Key Mgm 16" }, + { 0x5FC11D, 0, 0,1, 0,0, 0, "92", "2.16.17", "e", + "Retired Cert Key Mgm 17" }, + { 0x5FC11E, 0, 0,1, 0,0, 0, "93", "2.16.18", "e", + "Retired Cert Key Mgm 18" }, + { 0x5FC11F, 0, 0,1, 0,0, 0, "94", "2.16.19", "e", + "Retired Cert Key Mgm 19" }, + { 0x5FC120, 0, 0,1, 0,0, 0, "95", "2.16.20", "e", + "Retired Cert Key Mgm 20" }, + { 0x5FC121, 0, 2,2, 0,0, 0, "", "2.16.21", NULL, + "Cardholder Iris Images" }, + { 0x7F61, 0, 0,0, 0,0, 0, "", "2.16.22", NULL, + "BIT Group Template" }, + { 0x5FC122, 0, 0,0, 0,0, 0, "", "2.16.23", NULL, + "SM Cert Signer" }, + { 0x5FC123, 0, 3,3, 0,0, 0, "", "2.16.24", NULL, + "Pairing Code Ref Data" }, + { 0 } + /* Other key reference values without a data object: + * "00" Global PIN (not cleared by application switching) + * "04" PIV Secure Messaging Key + * "80" PIV Application PIN + * "81" PIN Unblocking Key + * "96" Primary Finger OCC + * "97" Secondary Finger OCC + * "98" Pairing Code + * "9B" PIV Card Application Administration Key + * + * Yubikey specific data objects: + * "F9" Attestation key (preloaded can be replaced) + */ +}; + + +/* One cache item for DOs. */ +struct cache_s { + struct cache_s *next; + int tag; + size_t length; + unsigned char data[1]; +}; + + +/* Object with application specific data. */ +struct app_local_s { + /* A linked list with cached DOs. */ + struct cache_s *cache; + + /* Various flags. */ + struct + { + unsigned int yubikey:1; /* This is on a Yubikey. */ + } flags; + +}; + + +/***** Local prototypes *****/ +static gpg_error_t get_keygrip_by_tag (app_t app, unsigned int tag, + char **r_keygripstr, int *got_cert); +static gpg_error_t genkey_parse_rsa (const unsigned char *data, size_t datalen, + gcry_sexp_t *r_sexp); +static gpg_error_t genkey_parse_ecc (const unsigned char *data, size_t datalen, + int mechanism, gcry_sexp_t *r_sexp); + + + + + +/* Deconstructor. */ +static void +do_deinit (app_t app) +{ + if (app && app->app_local) + { + struct cache_s *c, *c2; + + for (c = app->app_local->cache; c; c = c2) + { + c2 = c->next; + xfree (c); + } + + xfree (app->app_local); + app->app_local = NULL; + } +} + + +/* Wrapper around iso7816_get_data which first tries to get the data + * from the cache. With GET_IMMEDIATE passed as true, the cache is + * bypassed. The tag-53 container is also removed. */ +static gpg_error_t +get_cached_data (app_t app, int tag, + unsigned char **result, size_t *resultlen, + int get_immediate) +{ + gpg_error_t err; + int i; + unsigned char *p; + const unsigned char *s; + size_t len, n; + struct cache_s *c; + + *result = NULL; + *resultlen = 0; + + if (!get_immediate) + { + for (c=app->app_local->cache; c; c = c->next) + if (c->tag == tag) + { + if(c->length) + { + p = xtrymalloc (c->length); + if (!p) + return gpg_error_from_syserror (); + memcpy (p, c->data, c->length); + *result = p; + } + + *resultlen = c->length; + + return 0; + } + } + + err = iso7816_get_data_odd (app->slot, 0, tag, &p, &len); + if (err) + return err; + + /* Unless the Discovery Object or the BIT Group Template is + * requested, remove the outer container. + * (SP800-73.4 Part 2, section 3.1.2) */ + if (tag == 0x7E || tag == 0x7F61) + ; + else if (len && *p == 0x53 && (s = find_tlv (p, len, 0x53, &n))) + { + memmove (p, s, n); + len = n; + } + + if (len) + *result = p; + *resultlen = len; + + /* Check whether we should cache this object. */ + if (get_immediate) + return 0; + + for (i=0; data_objects[i].tag; i++) + if (data_objects[i].tag == tag) + { + if (data_objects[i].dont_cache) + return 0; + break; + } + + /* Okay, cache it. */ + for (c=app->app_local->cache; c; c = c->next) + log_assert (c->tag != tag); + + c = xtrymalloc (sizeof *c + len); + if (c) + { + if (len) + memcpy (c->data, p, len); + else + xfree (p); + c->length = len; + c->tag = tag; + c->next = app->app_local->cache; + app->app_local->cache = c; + } + + return 0; +} + + +/* 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 || !tag) + { + if (cprev) + cprev->next = c->next; + else + app->app_local->cache = c->next; + xfree (c); + + for (c=app->app_local->cache; c ; c = c->next) + { + log_assert (c->tag != tag); /* Oops: duplicated entry. */ + } + return; + } +} + + +/* Get the DO identified by TAG from the card in SLOT and return a + * buffer with its content in RESULT and NBYTES. The return value is + * NULL if not found or a pointer which must be used to release the + * buffer holding value. */ +static void * +get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes, + int *r_err) +{ + gpg_error_t err; + int i; + unsigned char *buffer; + size_t buflen; + unsigned char *value; + size_t valuelen; + gpg_error_t dummyerr; + + if (!r_err) + r_err = &dummyerr; + + *result = NULL; + *nbytes = 0; + *r_err = 0; + for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) + ; + + value = NULL; + err = gpg_error (GPG_ERR_ENOENT); + + if (!value) /* Not in a constructed DO, try simple. */ + { + err = get_cached_data (app, tag, &buffer, &buflen, + data_objects[i].dont_cache); + if (!err) + { + value = buffer; + valuelen = buflen; + } + } + + if (!err) + { + *nbytes = valuelen; + *result = value; + return buffer; + } + + *r_err = err; + return NULL; +} + + +static void +dump_all_do (int slot) +{ + gpg_error_t err; + int i; + unsigned char *buffer; + size_t buflen; + + for (i=0; data_objects[i].tag; i++) + { + /* We don't try extended length APDU because such large DO would + be pretty useless in a log file. */ + err = iso7816_get_data_odd (slot, 0, data_objects[i].tag, + &buffer, &buflen); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_ENOENT + && !data_objects[i].mandatory) + ; + else + log_info ("DO '%s' not available: %s\n", + data_objects[i].desc, gpg_strerror (err)); + } + else + { + if (data_objects[i].tag == 0x5FC109) + log_info ("DO '%s': '%.*s'\n", data_objects[i].desc, + (int)buflen, buffer); + else + { + log_info ("DO '%s': ", data_objects[i].desc); + if (buflen > 16 && opt.verbose < 2) + { + log_printhex (buffer, 16, NULL); + log_printf ("[...]\n"); + } + else + log_printhex (buffer, buflen, ""); + } + + } + xfree (buffer); buffer = NULL; + } +} + + +/* Create a TLV tag and value and store it at BUFFER. Return the + * length of tag and length. A LENGTH greater than 65535 is + * truncated. TAG must be less or equal to 2^16. If BUFFER is NULL, + * only the required length is computed. */ +static size_t +add_tlv (unsigned char *buffer, unsigned int tag, size_t length) +{ + if (length > 0xffff) + length = 0xffff; + + if (buffer) + { + unsigned char *p = buffer; + + if (tag > 0xff) + *p++ = tag >> 8; + *p++ = tag; + if (length < 128) + *p++ = length; + else if (length < 256) + { + *p++ = 0x81; + *p++ = length; + } + else + { + *p++ = 0x82; + *p++ = length >> 8; + *p++ = length; + } + + return p - buffer; + } + else + { + size_t n = 0; + + if (tag > 0xff) + n++; + n++; + if (length < 128) + n++; + else if (length < 256) + n += 2; + else + n += 3; + return n; + } +} + + +/* 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. If SECMEM is true the + * returned buffer is allocated in secure memory. */ +static gpg_error_t +concat_tlv_list (int secure, 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 = secure? xtrymalloc_secure (datalen) : 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 + * terminates the list. Up to 10 tuples are supported. */ +static gpg_error_t +put_data (int slot, unsigned int tag, ...) +{ + gpg_error_t err; + va_list arg_ptr; + struct { + int tag; + size_t len; + const void *data; + } argv[10]; + int i, argc; + unsigned char data5c[5]; + size_t data5clen; + unsigned char *data = NULL; + size_t datalen; + unsigned char *p; + size_t n; + + /* Collect all args. Check that length is <= 2^16 to match the + * behaviour of add_tlv. */ + va_start (arg_ptr, tag); + argc = 0; + while (((argv[argc].tag = va_arg (arg_ptr, int)))) + { + argv[argc].len = va_arg (arg_ptr, size_t); + argv[argc].data = va_arg (arg_ptr, const void *); + if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff) + { + va_end (arg_ptr); + return GPG_ERR_EINVAL; + } + argc++; + } + va_end (arg_ptr); + + /* Build the TLV with the tag to be updated. */ + data5c[0] = 0x5c; /* Tag list */ + if (tag <= 0xff) + { + data5c[1] = 1; + data5c[2] = tag; + data5clen = 3; + } + else if (tag <= 0xffff) + { + data5c[1] = 2; + data5c[2] = (tag >> 8); + data5c[3] = tag; + data5clen = 4; + } + else + { + data5c[1] = 3; + data5c[2] = (tag >> 16); + data5c[3] = (tag >> 8); + data5c[4] = tag; + data5clen = 5; + } + + /* Compute the required buffer length and allocate the buffer. */ + n = 0; + for (i=0; i < argc; i++) + { + n += add_tlv (NULL, argv[i].tag, argv[i].len); + n += argv[i].len; + } + datalen = data5clen + add_tlv (NULL, 0x53, n) + n; + data = xtrymalloc (datalen); + if (!data) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Copy that data to the buffer. */ + p = data; + memcpy (p, data5c, data5clen); + p += data5clen; + p += add_tlv (p, 0x53, n); + for (i=0; i < argc; i++) + { + 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 ); + err = iso7816_put_data_odd (slot, -1 /* use command chaining */, + 0x3fff, data, datalen); + + leave: + xfree (data); + return err; +} + + +/* Parse the key reference KEYREFSTR which is expected to hold a key + * reference for a CHV object. Return the one octet keyref or -1 for + * an invalid reference. */ +static int +parse_chv_keyref (const char *keyrefstr) +{ + if (!keyrefstr) + return -1; + else if (!ascii_strcasecmp (keyrefstr, "PIV.00")) + return 0x00; + else if (!ascii_strcasecmp (keyrefstr, "PIV.80")) + return 0x80; + else if (!ascii_strcasecmp (keyrefstr, "PIV.81")) + return 0x81; + else + return -1; +} + + +/* Return an allocated string with the serial number in a format to be + * show to the user. With FAILMODE is true return NULL if such an + * abbreviated S/N is not available, else return the full serial + * number as a hex string. May return NULL on malloc problem. */ +static char * +get_dispserialno (app_t app, int failmode) +{ + char *result; + + if (app->serialno && app->serialnolen == 3+1+4 + && !memcmp (app->serialno, "\xff\x02\x00", 3)) + { + /* This is a 4 byte S/N of a Yubikey which seems to be printed + * on the token in decimal. Maybe they will print larger S/N + * also in decimal but we can't be sure, thus do it only for + * these 32 bit numbers. */ + unsigned long sn; + sn = app->serialno[4] * 16777216; + sn += app->serialno[5] * 65536; + sn += app->serialno[6] * 256; + sn += app->serialno[7]; + result = xtryasprintf ("yk-%lu", sn); + } + else if (failmode) + result = NULL; /* No Abbreviated S/N. */ + else + result = app_get_serialno (app); + + return result; +} + + +/* The verify command can be used to retrieve the security status of + * the card. Given the PIN name (e.g. "PIV.80" for thge application + * pin, a status is returned: + * + * -1 = Error retrieving the data, + * -2 = No such PIN, + * -3 = PIN blocked, + * -5 = Verify still valid, + * n >= 0 = Number of verification attempts left. + */ +static int +get_chv_status (app_t app, const char *keyrefstr) +{ + unsigned char apdu[4]; + unsigned int sw; + int result; + int keyref; + + keyref = parse_chv_keyref (keyrefstr); + if (!keyrefstr) + return -1; + + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0x00; + apdu[3] = keyref; + if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) + result = -5; /* No need to verification. */ + else if (sw == 0x6a88 || sw == 0x6a80) + result = -2; /* No such PIN. */ + else if (sw == 0x6983) + result = -3; /* PIN is blocked. */ + else if ((sw & 0xfff0) == 0x63C0) + result = (sw & 0x000f); + else + result = -1; /* Error. */ + + return result; +} + + +/* Implementation of the GETATTR command. This is similar to the + * LEARN command but returns only one value via status lines. */ +static gpg_error_t +do_getattr (app_t app, ctrl_t ctrl, const char *name) +{ + static struct { + const char *name; + int tag; + int special; + } table[] = { + { "SERIALNO", 0x0000, -1 }, + { "$AUTHKEYID", 0x0000, -2 }, /* Default key for ssh. */ + { "$DISPSERIALNO",0x0000, -3 }, + { "CHV-STATUS", 0x0000, -4 }, + { "CHV-USAGE", 0x007E, -5 } + }; + gpg_error_t err = 0; + int idx; + void *relptr; + unsigned char *value; + size_t valuelen; + const unsigned char *s; + size_t n; + + for (idx=0; (idx < DIM (table) + && ascii_strcasecmp (table[idx].name, name)); idx++) + ; + if (!(idx < DIM (table))) + err = gpg_error (GPG_ERR_INV_NAME); + else if (table[idx].special == -1) + { + char *serial = app_get_serialno (app); + + if (serial) + { + send_status_direct (ctrl, "SERIALNO", serial); + xfree (serial); + } + } + else if (table[idx].special == -2) + { + char const tmp[] = "PIV.9A"; /* Cert PIV Authenticate. */ + send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); + } + else if (table[idx].special == -3) + { + char *tmp = get_dispserialno (app, 1); + + if (tmp) + { + send_status_info (ctrl, table[idx].name, + tmp, strlen (tmp), + NULL, (size_t)0); + xfree (tmp); + } + else + err = gpg_error (GPG_ERR_INV_NAME); /* No Abbreviated S/N. */ + } + else if (table[idx].special == -4) /* CHV-STATUS */ + { + int tmp[4]; + + tmp[0] = get_chv_status (app, "PIV.00"); + tmp[1] = get_chv_status (app, "PIV.80"); + tmp[2] = get_chv_status (app, "PIV.81"); + err = send_status_printf (ctrl, table[idx].name, "%d %d %d", + tmp[0], tmp[1], tmp[2]); + } + else if (table[idx].special == -5) /* CHV-USAGE (aka PIN Usage Policy) */ + { + /* We return 2 hex bytes or nothing in case the discovery object + * is not supported. */ + relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err); + if (relptr) + { + s = find_tlv (value, valuelen, 0x7E, &n); + if (s && n && (s = find_tlv (s, n, 0x5F2F, &n)) && n >=2 ) + err = send_status_printf (ctrl, table[idx].name, "%02X %02X", + s[0], s[1]); + xfree (relptr); + } + } + else + { + relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err); + if (relptr) + { + send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0); + xfree (relptr); + } + } + + return err; +} + + +/* Authenticate the card using the Card Application Administration + * Key. (VALUE,VALUELEN) has that 24 byte key. */ +static gpg_error_t +auth_adm_key (app_t app, const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + unsigned char tmpl[4+24]; + size_t tmpllen; + unsigned char *outdata = NULL; + size_t outdatalen; + const unsigned char *s; + char witness[8]; + size_t n; + gcry_cipher_hd_t cipher = NULL; + + /* Prepare decryption. */ + err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0); + if (err) + goto leave; + err = gcry_cipher_setkey (cipher, value, valuelen); + if (err) + goto leave; + + /* Request a witness. */ + tmpl[0] = 0x7c; + tmpl[1] = 0x02; + tmpl[2] = 0x80; + tmpl[3] = 0; /* (Empty witness requests a witness.) */ + tmpllen = 4; + err = iso7816_general_authenticate (app->slot, 0, + PIV_ALGORITHM_3DES_ECB_0, 0x9B, + tmpl, tmpllen, 0, + &outdata, &outdatalen); + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_BAD_AUTH); + if (err) + goto leave; + if (!(outdatalen && *outdata == 0x7c + && (s = find_tlv (outdata, outdatalen, 0x80, &n)) + && n == 8)) + { + err = gpg_error (GPG_ERR_CARD); + log_error ("piv: improper witness received\n"); + goto leave; + } + err = gcry_cipher_decrypt (cipher, witness, 8, s, 8); + if (err) + goto leave; + + /* Return decrypted witness and send our challenge. */ + tmpl[0] = 0x7c; + tmpl[1] = 22; + tmpl[2] = 0x80; + tmpl[3] = 8; + memcpy (tmpl+4, witness, 8); + tmpl[12] = 0x81; + tmpl[13] = 8; + gcry_create_nonce (tmpl+14, 8); + tmpl[22] = 0x80; + tmpl[23] = 0; + tmpllen = 24; + xfree (outdata); + err = iso7816_general_authenticate (app->slot, 0, + PIV_ALGORITHM_3DES_ECB_0, 0x9B, + tmpl, tmpllen, 0, + &outdata, &outdatalen); + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_BAD_AUTH); + if (err) + goto leave; + if (!(outdatalen && *outdata == 0x7c + && (s = find_tlv (outdata, outdatalen, 0x82, &n)) + && n == 8)) + { + err = gpg_error (GPG_ERR_CARD); + log_error ("piv: improper challenge received\n"); + goto leave; + } + /* (We reuse the witness buffer.) */ + err = gcry_cipher_decrypt (cipher, witness, 8, s, 8); + if (err) + goto leave; + if (memcmp (witness, tmpl+14, 8)) + { + err = gpg_error (GPG_ERR_BAD_AUTH); + goto leave; + } + + leave: + xfree (outdata); + gcry_cipher_close (cipher); + return err; +} + + +/* Set a new admin key. */ +static gpg_error_t +set_adm_key (app_t app, const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + unsigned char apdu[8+24]; + unsigned int sw; + + /* Check whether it is a weak key and that it is of proper length. */ + { + gcry_cipher_hd_t cipher; + + err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0); + if (!err) + { + err = gcry_cipher_setkey (cipher, value, valuelen); + gcry_cipher_close (cipher); + } + if (err) + goto leave; + } + + if (app->app_local->flags.yubikey) + { + /* This is a Yubikey. */ + if (valuelen != 24) + { + err = gpg_error (GPG_ERR_INV_LENGTH); + goto leave; + } + + /* We use a proprietary Yubikey command. */ + apdu[0] = 0; + apdu[1] = 0xff; + apdu[2] = 0xff; + apdu[3] = 0xff; /* touch policy: 0xff=never, 0xfe = always. */ + apdu[4] = 3 + 24; + apdu[5] = PIV_ALGORITHM_3DES_ECB; + apdu[6] = 0x9b; + apdu[7] = 24; + memcpy (apdu+8, value, 24); + err = iso7816_apdu_direct (app->slot, apdu, 8+24, 0, &sw, NULL, NULL); + wipememory (apdu+8, 24); + if (err) + log_error ("piv: setting admin key failed; sw=%04x\n", sw); + /* A PIN is not required, thus use a better error code. */ + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_NO_AUTH); + } + else + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + + + leave: + return err; +} + +/* Handle the SETATTR operation. All arguments are already basically + * checked. */ +static gpg_error_t +do_setattr (app_t app, const char *name, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + static struct { + const char *name; + unsigned short tag; + unsigned short flush_tag; /* The tag which needs to be flushed or 0. */ + int special; /* Special mode to use for thus NAME. */ + } table[] = { + /* Authenticate using the PIV Card Application Administration Key + * (0x0B). Note that Yubico calls this key the "management key" + * which we don't do because that term is too similar to "Cert + * Management Key" (0x9D). */ + { "AUTH-ADM-KEY", 0x0000, 0x0000, 1 }, + { "SET-ADM-KEY", 0x0000, 0x0000, 2 } + }; + int idx; + + (void)pincb; + (void)pincb_arg; + + for (idx=0; (idx < DIM (table) + && ascii_strcasecmp (table[idx].name, name)); idx++) + ; + if (!(idx < DIM (table))) + return gpg_error (GPG_ERR_INV_NAME); + + /* Flush the cache before writing it, so that the next get operation + * will reread the data from the card and thus get synced in case of + * errors (e.g. data truncated by the card). */ + if (table[idx].tag) + flush_cached_data (app, table[idx].flush_tag? table[idx].flush_tag + /* */ : table[idx].tag); + + switch (table[idx].special) + { + case 1: + err = auth_adm_key (app, value, valuelen); + break; + + case 2: + err = set_adm_key (app, value, valuelen); + break; + + default: + err = gpg_error (GPG_ERR_BUG); + break; + } + + return err; +} + + +/* Send the KEYPAIRINFO back. DOBJ describes the data object carrying + * the key. This is used by the LEARN command. */ +static gpg_error_t +send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj, + int only_keypair) +{ + gpg_error_t err = 0; + char *keygripstr = NULL; + int got_cert; + char idbuf[50]; + const char *usage; + + err = get_keygrip_by_tag (app, dobj->tag, &keygripstr, &got_cert); + if (err) + goto leave; + + usage = dobj->usage? dobj->usage : ""; + + snprintf (idbuf, sizeof idbuf, "PIV.%s", dobj->keyref); + send_status_info (ctrl, "KEYPAIRINFO", + keygripstr, strlen (keygripstr), + idbuf, strlen (idbuf), + usage, strlen (usage), + NULL, (size_t)0); + if (!only_keypair && got_cert) + { + /* All certificates are of type 100 (Regular X.509 Cert). */ + send_status_info (ctrl, "CERTINFO", + "100", 3, + idbuf, strlen (idbuf), + NULL, (size_t)0); + } + + leave: + xfree (keygripstr); + return err; +} + + +/* Handle the LEARN command. */ +static gpg_error_t +do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) +{ + int i; + + (void)flags; + + do_getattr (app, ctrl, "CHV-USAGE"); + do_getattr (app, ctrl, "CHV-STATUS"); + + for (i=0; data_objects[i].tag; i++) + if (data_objects[i].keypair) + send_keypair_and_cert_info (app, ctrl, data_objects + i, !!(flags & 1)); + + + return 0; +} + + +/* 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. If + * on success a non-zero value is stored at R_MECHANISM, the returned + * data is not a certificate but a public key (in the format used by the + * container '7f49'. */ +static gpg_error_t +readcert_by_tag (app_t app, unsigned int tag, + unsigned char **r_cert, size_t *r_certlen, int *r_mechanism) +{ + gpg_error_t err; + unsigned char *buffer; + size_t buflen; + void *relptr; + const unsigned char *s, *s2; + size_t n, n2; + + *r_cert = NULL; + *r_certlen = 0; + *r_mechanism = 0; + + relptr = get_one_do (app, tag, &buffer, &buflen, NULL); + if (!relptr || !buflen) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + + s = find_tlv (buffer, buflen, 0x71, &n); + if (!s) + { + /* No certificate; check whether a public key has been stored + * using our own scheme. */ + s = find_tlv (buffer, buflen, 0x7f49, &n); + if (!s || !n) + { + log_error ("piv: No public key in 0x%X\n", tag); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + s2 = find_tlv (buffer, buflen, 0x80, &n2); + if (!s2 || n2 != 1 || !*s2) + { + log_error ("piv: No mechanism for public key in 0x%X\n", tag); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + *r_mechanism = *s2; + } + else + { + if (n != 1) + { + log_error ("piv: invalid CertInfo in 0x%X\n", tag); + err = gpg_error (GPG_ERR_INV_CERT_OBJ); + goto leave; + } + if (*s == 0x01) + { + log_error ("piv: gzip compression not yet supported (tag 0x%X)\n", + tag); + err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); + goto leave; + } + if (*s) + { + log_error ("piv: invalid CertInfo 0x%02x in 0x%X\n", *s, tag); + err = gpg_error (GPG_ERR_INV_CERT_OBJ); + goto leave; + } + + /* Note: We don't check that the LRC octet has a length of zero + * as required by the specs. */ + + /* Get the cert from the container. */ + s = find_tlv (buffer, buflen, 0x70, &n); + if (!s || !n) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + } + + /* The next is common for certificate and public key. */ + if (!(*r_cert = xtrymalloc (n))) + { + err = gpg_error_from_syserror (); + goto leave; + } + + memcpy (*r_cert, s, n); + *r_certlen = n; + err = 0; + + leave: + xfree (relptr); + return err; +} + + +/* Get the keygrip in hex format of a key from the certificate stored + * at TAG. Caller must free the string at R_KEYGRIPSTR. */ +static gpg_error_t +get_keygrip_by_tag (app_t app, unsigned int tag, + char **r_keygripstr, int *r_got_cert) +{ + gpg_error_t err; + unsigned char *certbuf = NULL; + size_t certbuflen; + int mechanism; + gcry_sexp_t s_pkey = NULL; + ksba_cert_t cert = NULL; + unsigned char grip[KEYGRIP_LEN]; + + *r_got_cert = 0; + *r_keygripstr = xtrymalloc (2*KEYGRIP_LEN+1); + if (!r_keygripstr) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* We need to get the public key from the certificate. */ + err = readcert_by_tag (app, tag, &certbuf, &certbuflen, &mechanism); + if (err) + goto leave; + if (mechanism) /* Compute keygrip from public key. */ + { + if (mechanism == PIV_ALGORITHM_RSA) + err = genkey_parse_rsa (certbuf, certbuflen, &s_pkey); + else if (mechanism == PIV_ALGORITHM_ECC_P256 + || mechanism == PIV_ALGORITHM_ECC_P384) + err = genkey_parse_ecc (certbuf, certbuflen, mechanism, &s_pkey); + else + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + if (err) + goto leave; + + if (!gcry_pk_get_keygrip (s_pkey, grip)) + { + log_error ("piv: error computing keygrip\n"); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + bin2hex (grip, sizeof grip, *r_keygripstr); + } + else /* Compute keygrip from certificate. */ + { + *r_got_cert = 0; + err = ksba_cert_new (&cert); + if (err) + goto leave; + err = ksba_cert_init_from_mem (cert, certbuf, certbuflen); + if (err) + goto leave; + err = app_help_get_keygrip_string (cert, *r_keygripstr); + } + + leave: + gcry_sexp_release (s_pkey); + ksba_cert_release (cert); + xfree (certbuf); + if (err) + { + xfree (*r_keygripstr); + *r_keygripstr = NULL; + } + return err; +} + + +/* Locate the data object from the given KEYREF. The KEYREF may also + * be the corresponding OID of the key object. Returns the data + * object or NULL if not found. */ +static data_object_t +find_dobj_by_keyref (app_t app, const char *keyref) +{ + int i; + + (void)app; + + if (!ascii_strncasecmp (keyref, "PIV.", 4)) + { + keyref += 4; + for (i=0; data_objects[i].tag; i++) + if (*data_objects[i].keyref + && !ascii_strcasecmp (keyref, data_objects[i].keyref)) + { + return data_objects + i; + } + } + else if (!strncmp (keyref, "2.16.840.1.101.3.7.", 19)) + { + keyref += 19; + for (i=0; data_objects[i].tag; i++) + if (*data_objects[i].keyref + && !strcmp (keyref, data_objects[i].oidsuffix)) + { + return data_objects + i; + } + } + + return NULL; +} + + +/* 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 + * container or of the form "PIV.<two_hexdigit_keyref>" */ +static gpg_error_t +do_readcert (app_t app, const char *certid, + unsigned char **r_cert, size_t *r_certlen) +{ + gpg_error_t err; + data_object_t dobj; + int mechanism; + + *r_cert = NULL; + *r_certlen = 0; + + /* Hack to read a Yubikey attestation certificate. */ + if (app->app_local->flags.yubikey + && strlen (certid) == 11 + && !ascii_strncasecmp (certid, "PIV.ATST.", 9) + && hexdigitp (certid+9) && hexdigitp (certid+10)) + { + unsigned char apdu[4]; + unsigned char *result; + size_t resultlen; + + apdu[0] = 0; + apdu[1] = 0xf9; /* Yubikey: Get attestation cert. */ + apdu[2] = xtoi_2 (certid+9); + apdu[3] = 0; + err = iso7816_apdu_direct (app->slot, apdu, 4, 1, + NULL, &result, &resultlen); + if (!err) + { + *r_cert = result; + *r_certlen = resultlen; + } + return err; + } + + dobj = find_dobj_by_keyref (app, certid); + if (!dobj) + return gpg_error (GPG_ERR_INV_ID); + + err = readcert_by_tag (app, dobj->tag, r_cert, r_certlen, &mechanism); + if (!err && mechanism) + { + /* Well, no certificate but a public key - we don't want it. */ + xfree (*r_cert); + *r_cert = NULL; + *r_certlen = 0; + err = gpg_error (GPG_ERR_NOT_FOUND); + } + return err; +} + + +/* 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 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, const char *keyrefstr, + unsigned char **r_pk, size_t *r_pklen) +{ + gpg_error_t err; + data_object_t dobj; + int keyref; + unsigned char *cert = NULL; + size_t certlen; + int mechanism; + gcry_sexp_t s_pkey = NULL; + 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; + } + + err = readcert_by_tag (app, dobj->tag, &cert, &certlen, &mechanism); + if (err) + goto leave; + if (!mechanism) + { + /* We got a certificate. Extract the pubkey from it. */ + err = app_help_pubkey_from_cert (cert, certlen, &pk, &pklen); + if (err) + { + log_error ("failed to parse the certificate: %s\n", + gpg_strerror (err)); + goto leave; + } + } + else + { + /* Convert the public key into the expected s-expression. */ + if (mechanism == PIV_ALGORITHM_RSA) + err = genkey_parse_rsa (cert, certlen, &s_pkey); + else if (mechanism == PIV_ALGORITHM_ECC_P256 + || mechanism == PIV_ALGORITHM_ECC_P384) + err = genkey_parse_ecc (cert, certlen, mechanism, &s_pkey); + else + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + if (err) + goto leave; + + err = make_canon_sexp (s_pkey, &pk, &pklen); + if (err) + goto leave; + } + + *r_pk = pk; + pk = NULL; + *r_pklen = pklen; + + leave: + gcry_sexp_release (s_pkey); + xfree (pk); + xfree (cert); + 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. */ +static gpg_error_t +get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_mechanism) +{ + gpg_error_t err; + unsigned char *certbuf = NULL; + size_t certbuflen; + int mechanism; + ksba_cert_t cert = NULL; + ksba_sexp_t k_pkey = NULL; + gcry_sexp_t s_pkey = NULL; + gcry_sexp_t l1 = NULL; + char *algoname = NULL; + int algo; + size_t n; + const char *curve_name; + + *r_mechanism = 0; + + err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen, &mechanism); + if (err) + goto leave; + if (mechanism) + { + /* A public key was found. That makes it easy. */ + switch (mechanism) + { + case PIV_ALGORITHM_RSA: + case PIV_ALGORITHM_ECC_P256: + case PIV_ALGORITHM_ECC_P384: + *r_mechanism = mechanism; + break; + + default: + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + log_error ("piv: unknown mechanism %d in public key at %s\n", + mechanism, dobj->keyref); + break; + } + goto leave; + } + + err = ksba_cert_new (&cert); + if (err) + goto leave; + + err = ksba_cert_init_from_mem (cert, certbuf, certbuflen); + if (err) + { + log_error ("piv: failed to parse the certificate %s: %s\n", + dobj->keyref, gpg_strerror (err)); + goto leave; + } + xfree (certbuf); + certbuf = NULL; + + k_pkey = ksba_cert_get_public_key (cert); + if (!k_pkey) + { + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + n = gcry_sexp_canon_len (k_pkey, 0, NULL, NULL); + err = gcry_sexp_new (&s_pkey, k_pkey, n, 0); + if (err) + goto leave; + + l1 = gcry_sexp_find_token (s_pkey, "public-key", 0); + if (!l1) + { + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + + { + gcry_sexp_t l_tmp = gcry_sexp_cadr (l1); + gcry_sexp_release (l1); + l1 = l_tmp; + } + algoname = gcry_sexp_nth_string (l1, 0); + if (!algoname) + { + err = gpg_error_from_syserror (); + goto leave; + } + + algo = gcry_pk_map_name (algoname); + switch (algo) + { + case GCRY_PK_RSA: + algo = PIV_ALGORITHM_RSA; + break; + + case GCRY_PK_ECC: + case GCRY_PK_ECDSA: + case GCRY_PK_ECDH: + curve_name = gcry_pk_get_curve (s_pkey, 0, NULL); + if (curve_name && !strcmp (curve_name, "NIST P-256")) + algo = PIV_ALGORITHM_ECC_P256; + else if (curve_name && !strcmp (curve_name, "NIST P-384")) + algo = PIV_ALGORITHM_ECC_P384; + else + { + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + log_error ("piv: certificate %s, curve '%s': %s\n", + dobj->keyref, curve_name, gpg_strerror (err)); + goto leave; + } + break; + + default: + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + log_error ("piv: certificate %s, pubkey algo '%s': %s\n", + dobj->keyref, algoname, gpg_strerror (err)); + goto leave; + } + *r_mechanism = algo; + + leave: + gcry_free (algoname); + gcry_sexp_release (l1); + gcry_sexp_release (s_pkey); + ksba_free (k_pkey); + xfree (certbuf); + return err; +} + + +/* Return an allocated string to be used as prompt. Returns NULL on + * malloc error. */ +static char * +make_prompt (app_t app, int remaining, const char *firstline) +{ + char *serial, *tmpbuf, *result; + + serial = get_dispserialno (app, 0); + if (!serial) + return NULL; + + /* TRANSLATORS: Put a \x1f right before a colon. This can be + * used by pinentry to nicely align the names and values. Keep + * the %s at the start and end of the string. */ + result = xtryasprintf (_("%s" + "Number\x1f: %s%%0A" + "Holder\x1f: %s" + "%s"), + "\x1e", + serial, + "Unknown", /* Fixme */ + ""); + xfree (serial); + + /* Append a "remaining attempts" info if needed. */ + if (remaining != -1 && remaining < 3) + { + char *rembuf; + + /* TRANSLATORS: This is the number of remaining attempts to + * enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */ + rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining); + if (rembuf) + { + tmpbuf = strconcat (firstline, "%0A%0A", result, + "%0A%0A", rembuf, NULL); + xfree (rembuf); + } + else + tmpbuf = NULL; + xfree (result); + result = tmpbuf; + } + else + { + tmpbuf = strconcat (firstline, "%0A%0A", result, NULL); + xfree (result); + result = tmpbuf; + } + + return result; +} + + +/* Helper for verify_chv to ask for the PIN and to prepare/pad it. On + * success the result is stored at (R_PIN,R_PINLEN). */ +static gpg_error_t +ask_and_prepare_chv (app_t app, int keyref, int ask_new, int remaining, + gpg_error_t (*pincb)(void*,const char *,char **), + void *pincb_arg, char **r_pin, unsigned int *r_pinlen) +{ + gpg_error_t err; + const char *label; + char *prompt; + char *pinvalue = NULL; + unsigned int pinlen; + char *pinbuffer = NULL; + int minlen, maxlen, padding, onlydigits; + + *r_pin = NULL; + *r_pinlen = 0; + + if (ask_new) + remaining = -1; + + if (remaining != -1) + log_debug ("piv: CHV %02X has %d attempts left\n", keyref, remaining); + + switch (keyref) + { + case 0x00: + minlen = 6; + maxlen = 8; + padding = 1; + onlydigits = 1; + label = (ask_new? _("|N|Please enter the new Global-PIN") + /**/ : _("||Please enter the Global-PIN of your PIV card")); + break; + case 0x80: + minlen = 6; + maxlen = 8; + padding = 1; + onlydigits = 1; + label = (ask_new? _("|N|Please enter the new PIN") + /**/ : _("||Please enter the PIN of your PIV card")); + break; + case 0x81: + minlen = 8; + maxlen = 8; + padding = 0; + onlydigits = 0; + label = (ask_new? _("|N|Please enter the new Unblocking Key") + /**/ :_("||Please enter the Unblocking Key of your PIV card")); + break; + + case 0x96: + case 0x97: + case 0x98: + case 0x9B: + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + default: + return gpg_error (GPG_ERR_INV_ID); + } + + /* Ask for the PIN. */ + prompt = make_prompt (app, remaining, label); + err = pincb (pincb_arg, prompt, &pinvalue); + xfree (prompt); + prompt = NULL; + if (err) + { + log_info (_("PIN callback returned error: %s\n"), gpg_strerror (err)); + return err; + } + + pinlen = pinvalue? strlen (pinvalue) : 0; + if (pinlen < minlen) + { + log_error (_("PIN for is too short; minimum length is %d\n"), minlen); + if (pinvalue) + wipememory (pinvalue, pinlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + if (pinlen > maxlen) + { + log_error (_("PIN for is too long; maximum length is %d\n"), maxlen); + wipememory (pinvalue, pinlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + if (onlydigits && strspn (pinvalue, "0123456789") != pinlen) + { + log_error (_("PIN has invalid characters; only digits are allowed\n")); + wipememory (pinvalue, pinlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + + pinbuffer = xtrymalloc_secure (maxlen); + if (!pinbuffer) + { + err = gpg_error_from_syserror (); + wipememory (pinvalue, pinlen); + xfree (pinvalue); + return err; + } + + memcpy (pinbuffer, pinvalue, pinlen); + wipememory (pinvalue, pinlen); + xfree (pinvalue); + if (padding) + { + memset (pinbuffer + pinlen, 0xff, maxlen - pinlen); + pinlen = maxlen; + } + + *r_pin = pinbuffer; + *r_pinlen = pinlen; + + return 0; +} + + +/* Verify the card holder verification identified by KEYREF. This is + * either the Appication PIN or the Global PIN. If FORCE is true a + * verification is always done. */ +static gpg_error_t +verify_chv (app_t app, int keyref, int force, + gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg) +{ + gpg_error_t err; + unsigned char apdu[4]; + unsigned int sw; + int remaining; + char *pin = NULL; + unsigned int pinlen; + + /* First check whether a verify is at all needed. This is done with + * P1 being 0 and no Lc and command data send. */ + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0x00; + apdu[3] = keyref; + if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) + { + if (!force) /* No need to verification. */ + return 0; /* All fine. */ + remaining = -1; + } + else if ((sw & 0xfff0) == 0x63C0) + remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */ + else + remaining = -1; + + err = ask_and_prepare_chv (app, keyref, 0, remaining, pincb, pincb_arg, + &pin, &pinlen); + if (err) + return err; + + err = iso7816_verify (app->slot, keyref, pin, pinlen); + wipememory (pin, pinlen); + xfree (pin); + if (err) + log_error ("CHV %02X verification failed: %s\n", + keyref, gpg_strerror (err)); + + return err; +} + + +/* Handle the PASSWD command. Valid values for PWIDSTR are + * key references related to PINs; in particular: + * PIV.00 - The Global PIN + * PIV.80 - The Application PIN + * PIV.81 - The PIN Unblocking key + * The supported flags are: + * APP_CHANGE_FLAG_CLEAR Clear the PIN verification state. + * APP_CHANGE_FLAG_RESET Reset a PIN using the PUK. Only + * allowed with PIV.80. + */ +static gpg_error_t +do_change_chv (app_t app, ctrl_t ctrl, const char *pwidstr, + unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + int keyref, targetkeyref; + unsigned char apdu[4]; + unsigned int sw; + int remaining; + char *oldpin = NULL; + unsigned int oldpinlen; + char *newpin = NULL; + unsigned int newpinlen; + + (void)ctrl; + + /* Check for unknown flags. */ + if ((flags & ~(APP_CHANGE_FLAG_CLEAR|APP_CHANGE_FLAG_RESET))) + { + err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + goto leave; + } + + /* Parse the keyref. */ + targetkeyref = keyref = parse_chv_keyref (pwidstr); + if (keyref == -1) + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + + /* First see whether the special --clear mode has been requested. */ + if ((flags & APP_CHANGE_FLAG_CLEAR)) + { + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0xff; + apdu[3] = keyref; + err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); + goto leave; + } + + /* Prepare reset mode. */ + if ((flags & APP_CHANGE_FLAG_RESET)) + { + if (keyref == 0x81) + { + err = gpg_error (GPG_ERR_INV_ID); /* Can't reset the PUK. */ + goto leave; + } + /* Set the keyref to the PUK and keep the TARGETKEYREF. */ + keyref = 0x81; + } + + /* Get the remaining tries count. This is done by using the check + * for verified state feature. */ + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0x00; + apdu[3] = keyref; + if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) + remaining = -1; /* Already verified, thus full number of tries. */ + else if ((sw & 0xfff0) == 0x63C0) + remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */ + else + remaining = -1; + + /* Ask for the old pin or puk. */ + err = ask_and_prepare_chv (app, keyref, 0, remaining, pincb, pincb_arg, + &oldpin, &oldpinlen); + if (err) + return err; + + /* Verify the old pin so that we don't prompt for the new pin if the + * old is wrong. This is not possible for the PUK, though. */ + if (keyref != 0x81) + { + err = iso7816_verify (app->slot, keyref, oldpin, oldpinlen); + if (err) + { + log_error ("CHV %02X verification failed: %s\n", + keyref, gpg_strerror (err)); + goto leave; + } + } + + /* Ask for the new pin. */ + err = ask_and_prepare_chv (app, targetkeyref, 1, -1, pincb, pincb_arg, + &newpin, &newpinlen); + if (err) + return err; + + if ((flags & APP_CHANGE_FLAG_RESET)) + { + char *buf = xtrymalloc_secure (oldpinlen + newpinlen); + if (!buf) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (buf, oldpin, oldpinlen); + memcpy (buf+oldpinlen, newpin, newpinlen); + err = iso7816_reset_retry_counter_with_rc (app->slot, targetkeyref, + buf, oldpinlen+newpinlen); + xfree (buf); + if (err) + log_error ("resetting CHV %02X using CHV %02X failed: %s\n", + targetkeyref, keyref, gpg_strerror (err)); + } + else + { + err = iso7816_change_reference_data (app->slot, keyref, + oldpin, oldpinlen, + newpin, newpinlen); + if (err) + log_error ("CHV %02X changing PIN failed: %s\n", + keyref, gpg_strerror (err)); + } + + leave: + xfree (oldpin); + xfree (newpin); + return err; +} + + +/* Perform a simple verify operation for the PIN specified by PWIDSTR. + * For valid values see do_change_chv. */ +static gpg_error_t +do_check_chv (app_t app, const char *pwidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int keyref; + + keyref = parse_chv_keyref (pwidstr); + if (keyref == -1) + return gpg_error (GPG_ERR_INV_ID); + + return verify_chv (app, keyref, 0, pincb, pincb_arg); +} + + +/* Compute a digital signature using the GENERAL AUTHENTICATE command + * on INDATA which is expected to be the raw message digest. The + * KEYIDSTR has the key reference or its OID (e.g. "PIV.9A"). The + * result is stored at (R_OUTDATA,R_OUTDATALEN); on error (NULL,0) 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 + * and S are left extended with zeroes to make sure they have an equal + * 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_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, + unsigned char **r_outdata, size_t *r_outdatalen) +{ + const unsigned char *indata = indata_arg; + gpg_error_t err; + data_object_t dobj; + unsigned char oidbuf[64]; + size_t oidbuflen; + unsigned char *outdata = NULL; + size_t outdatalen; + const unsigned char *s; + size_t n; + int keyref, mechanism; + unsigned char *indata_buffer = NULL; /* Malloced helper. */ + unsigned char *apdudata = NULL; + size_t apdudatalen; + int force_verify; + + 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; + } + + /* According to table 4b of SP800-73-4 the signing key always + * requires a verify. */ + switch (keyref) + { + case 0x9c: force_verify = 1; break; + default: force_verify = 0; break; + } + + + err = get_key_algorithm_by_dobj (app, dobj, &mechanism); + if (err) + goto leave; + + /* 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) + { + int need_algo, need_digestlen; + + if (mechanism == PIV_ALGORITHM_ECC_P256) + { + 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 > need_digestlen) + { + 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; + } + } + else if (mechanism == PIV_ALGORITHM_RSA) + { + /* PIV requires 2048 bit RSA. */ + unsigned int framelen = 2048 / 8; + unsigned char *frame; + int i; + + oidbuflen = sizeof oidbuf; + if (!hashalgo) + { + /* 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; + } + else + { + 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; + } + 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 mechanism %d while signing\n", mechanism); + goto leave; + } + + /* Now verify the Application PIN. */ + err = verify_chv (app, 0x80, force_verify, pincb, pincb_arg); + if (err) + return err; + + /* Build the Dynamic Authentication Template. */ + err = concat_tlv_list (0, &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, + 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))) + { + if (mechanism == PIV_ALGORITHM_RSA) + { + 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; + } + } + else + { + bad_der: + 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; + } + 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); +} + + +/* 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, 0, pincb, pincb_arg); + if (err) + return err; + + /* Build the Dynamic Authentication Template. */ + err = concat_tlv_list (0, &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 + * 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 (_("key already exists\n")); + return gpg_error (GPG_ERR_EEXIST); + } + + if (found) + log_info (_("existing key will be replaced\n")); + else if (generating) + log_info (_("generating new key\n")); + else + log_info (_("writing new key\n")); + return 0; +} + + +/* Helper for do_writekey; here the RSA part. BUF, BUFLEN, and DEPTH + * are the current parser state of the S-expression with the key. */ +static gpg_error_t +writekey_rsa (app_t app, data_object_t dobj, int keyref, + const unsigned char *buf, size_t buflen, int depth) +{ + gpg_error_t err; + const unsigned char *tok; + size_t toklen; + int last_depth1, last_depth2; + const unsigned char *rsa_n = NULL; + const unsigned char *rsa_e = NULL; + const unsigned char *rsa_p = NULL; + const unsigned char *rsa_q = NULL; + unsigned char *rsa_dpm1 = NULL; + unsigned char *rsa_dqm1 = NULL; + unsigned char *rsa_qinv = NULL; + size_t rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len; + size_t rsa_dpm1_len, rsa_dqm1_len, rsa_qinv_len; + unsigned char *apdudata = NULL; + size_t apdudatalen; + unsigned char tmpl[1]; + + last_depth1 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth1) + { + if (tok) + { + err = gpg_error (GPG_ERR_UNKNOWN_SEXP); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + if (tok && toklen == 1) + { + const unsigned char **mpi; + size_t *mpi_len; + + switch (*tok) + { + case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break; + case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break; + case 'p': mpi = &rsa_p; mpi_len = &rsa_p_len; break; + case 'q': mpi = &rsa_q; mpi_len = &rsa_q_len; break; + default: mpi = NULL; mpi_len = NULL; break; + } + if (mpi && *mpi) + { + err = gpg_error (GPG_ERR_DUP_VALUE); + goto leave; + } + + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (tok && mpi) + { + /* Strip off leading zero bytes and save. */ + for (;toklen && !*tok; toklen--, tok++) + ; + *mpi = tok; + *mpi_len = toklen; + } + } + /* Skip until end of list. */ + last_depth2 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth2) + ; + if (err) + goto leave; + } + + /* Check that we have all parameters. */ + if (!rsa_n || !rsa_e || !rsa_p || !rsa_q) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + /* Fixme: Shall we check whether n == pq ? */ + + if (opt.verbose) + log_info ("RSA private key size is %u bytes\n", (unsigned int)rsa_n_len); + + /* Compute the dp, dq and u components. */ + { + gcry_mpi_t mpi_e, mpi_p, mpi_q; + gcry_mpi_t mpi_dpm1 = gcry_mpi_snew (0); + gcry_mpi_t mpi_dqm1 = gcry_mpi_snew (0); + gcry_mpi_t mpi_qinv = gcry_mpi_snew (0); + gcry_mpi_t mpi_tmp = gcry_mpi_snew (0); + + gcry_mpi_scan (&mpi_e, GCRYMPI_FMT_USG, rsa_e, rsa_e_len, NULL); + gcry_mpi_scan (&mpi_p, GCRYMPI_FMT_USG, rsa_p, rsa_p_len, NULL); + gcry_mpi_scan (&mpi_q, GCRYMPI_FMT_USG, rsa_q, rsa_q_len, NULL); + + gcry_mpi_sub_ui (mpi_tmp, mpi_p, 1); + gcry_mpi_invm (mpi_dpm1, mpi_e, mpi_tmp); + + gcry_mpi_sub_ui (mpi_tmp, mpi_q, 1); + gcry_mpi_invm (mpi_dqm1, mpi_e, mpi_tmp); + + gcry_mpi_invm (mpi_qinv, mpi_q, mpi_p); + + gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dpm1, &rsa_dpm1_len, mpi_dpm1); + gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dqm1, &rsa_dqm1_len, mpi_dqm1); + gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_qinv, &rsa_qinv_len, mpi_qinv); + + gcry_mpi_release (mpi_e); + gcry_mpi_release (mpi_p); + gcry_mpi_release (mpi_q); + gcry_mpi_release (mpi_dpm1); + gcry_mpi_release (mpi_dqm1); + gcry_mpi_release (mpi_qinv); + gcry_mpi_release (mpi_tmp); + } + + err = concat_tlv_list (1, &apdudata, &apdudatalen, + (int)0x01, (size_t)rsa_p_len, rsa_p, + (int)0x02, (size_t)rsa_q_len, rsa_q, + (int)0x03, (size_t)rsa_dpm1_len, rsa_dpm1, + (int)0x04, (size_t)rsa_dqm1_len, rsa_dqm1, + (int)0x05, (size_t)rsa_qinv_len, rsa_qinv, + (int)0, (size_t)0, NULL); + if (err) + goto leave; + + err = iso7816_send_apdu (app->slot, + -1, /* Use command chaining. */ + 0, /* Class */ + 0xfe, /* Ins: Yubikey Import Asym. Key. */ + PIV_ALGORITHM_RSA, /* P1 */ + keyref, /* P2 */ + apdudatalen,/* Lc */ + apdudata, /* data */ + NULL, NULL, NULL); + if (err) + goto leave; + + /* Write the public key to the cert object. */ + xfree (apdudata); + err = concat_tlv_list (0, &apdudata, &apdudatalen, + (int)0x81, (size_t)rsa_n_len, rsa_n, + (int)0x82, (size_t)rsa_e_len, rsa_e, + (int)0, (size_t)0, NULL); + + if (err) + goto leave; + tmpl[0] = PIV_ALGORITHM_RSA; + err = put_data (app->slot, dobj->tag, + (int)0x80, (size_t)1, tmpl, + (int)0x7f49, (size_t)apdudatalen, apdudata, + (int)0, (size_t)0, NULL); + + leave: + xfree (rsa_dpm1); + xfree (rsa_dqm1); + xfree (rsa_qinv); + xfree (apdudata); + return err; +} + + +/* Helper for do_writekey; here the ECC part. BUF, BUFLEN, and DEPTH + * are the current parser state of the S-expression with the key. */ +static gpg_error_t +writekey_ecc (app_t app, data_object_t dobj, int keyref, + const unsigned char *buf, size_t buflen, int depth) +{ + gpg_error_t err; + const unsigned char *tok; + size_t toklen; + int last_depth1, last_depth2; + int mechanism = 0; + const unsigned char *ecc_q = NULL; + const unsigned char *ecc_d = NULL; + size_t ecc_q_len, ecc_d_len; + unsigned char *apdudata = NULL; + size_t apdudatalen; + unsigned char tmpl[1]; + + last_depth1 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth1) + { + if (tok) + { + err = gpg_error (GPG_ERR_UNKNOWN_SEXP); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + if (tok && toklen == 5 && !memcmp (tok, "curve", 5)) + { + char *name; + const char *xname; + + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + name = xtrymalloc (toklen+1); + if (!name) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (name, tok, toklen); + name[toklen] = 0; + /* Canonicalize the curve name. We use the openpgp + * functions here because Libgcrypt has no generic curve + * alias lookup feature and the PIV suppotred curves alre + * also supported by OpenPGP. */ + xname = openpgp_oid_to_curve (openpgp_curve_to_oid (name, NULL), 0); + xfree (name); + + if (xname && !strcmp (xname, "nistp256")) + mechanism = PIV_ALGORITHM_ECC_P256; + else if (xname && !strcmp (xname, "nistp384")) + mechanism = PIV_ALGORITHM_ECC_P384; + else + { + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + goto leave; + } + } + else if (tok && toklen == 1) + { + const unsigned char **mpi; + size_t *mpi_len; + + switch (*tok) + { + case 'q': mpi = &ecc_q; mpi_len = &ecc_q_len; break; + case 'd': mpi = &ecc_d; mpi_len = &ecc_d_len; break; + default: mpi = NULL; mpi_len = NULL; break; + } + if (mpi && *mpi) + { + err = gpg_error (GPG_ERR_DUP_VALUE); + goto leave; + } + + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (tok && mpi) + { + /* Strip off leading zero bytes and save. */ + for (;toklen && !*tok; toklen--, tok++) + ; + *mpi = tok; + *mpi_len = toklen; + } + } + /* Skip until end of list. */ + last_depth2 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth2) + ; + if (err) + goto leave; + } + + /* Check that we have all parameters. */ + if (!mechanism || !ecc_q || !ecc_d) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + if (opt.verbose) + log_info ("ECC private key size is %u bytes\n", (unsigned int)ecc_d_len); + + err = concat_tlv_list (1, &apdudata, &apdudatalen, + (int)0x06, (size_t)ecc_d_len, ecc_d, + (int)0, (size_t)0, NULL); + if (err) + goto leave; + + err = iso7816_send_apdu (app->slot, + -1, /* Use command chaining. */ + 0, /* Class */ + 0xfe, /* Ins: Yubikey Import Asym. Key. */ + mechanism, /* P1 */ + keyref, /* P2 */ + apdudatalen,/* Lc */ + apdudata, /* data */ + NULL, NULL, NULL); + if (err) + goto leave; + + /* Write the public key to the cert object. */ + xfree (apdudata); + err = concat_tlv_list (0, &apdudata, &apdudatalen, + (int)0x86, (size_t)ecc_q_len, ecc_q, + (int)0, (size_t)0, NULL); + + if (err) + goto leave; + tmpl[0] = mechanism; + err = put_data (app->slot, dobj->tag, + (int)0x80, (size_t)1, tmpl, + (int)0x7f49, (size_t)apdudatalen, apdudata, + (int)0, (size_t)0, NULL); + + + leave: + xfree (apdudata); + return err; +} + + +/* Write a key to a slot. This command requires proprietary + * extensions of the PIV specification and is thus only implemnted for + * supported card types. The input is a canonical encoded + * S-expression with the secret key in KEYDATA and its length (for + * assertion) in KEYDATALEN. KEYREFSTR needs to be the usual 2 + * hexdigit slot number prefixed with "PIV." PINCB and PINCB_ARG are + * not used for PIV cards. + * + * Supported FLAGS are: + * APP_WRITEKEY_FLAG_FORCE Overwrite existing key. + */ +static gpg_error_t +do_writekey (app_t app, ctrl_t ctrl, + const char *keyrefstr, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *keydata, size_t keydatalen) +{ + gpg_error_t err; + int force = !!(flags & APP_WRITEKEY_FLAG_FORCE); + data_object_t dobj; + int keyref; + const unsigned char *buf, *tok; + size_t buflen, toklen; + int depth; + + (void)ctrl; + (void)pincb; + (void)pincb_arg; + + if (!app->app_local->flags.yubikey) + { + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + /* Check keyref and test 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, 0, force); + if (err) + goto leave; + + /* Parse the S-expression with the key. */ + buf = keydata; + buflen = keydatalen; + depth = 0; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen)) + { + if (!tok) + ; + else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen)) + log_info ("protected-private-key passed to writekey\n"); + else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen)) + log_info ("shadowed-private-key passed to writekey\n"); + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + /* First clear an existing key. We do this by writing an empty 7f49 + * tag. This will return GPG_ERR_NO_PUBKEY on a later read. */ + flush_cached_data (app, dobj->tag); + err = put_data (app->slot, dobj->tag, + (int)0x7f49, (size_t)0, "", + (int)0, (size_t)0, NULL); + if (err) + { + log_error ("piv: failed to clear the cert DO %s: %s\n", + dobj->keyref, gpg_strerror (err)); + goto leave; + } + + /* Divert to the algo specific implementation. */ + if (tok && toklen == 3 && memcmp ("rsa", tok, toklen) == 0) + err = writekey_rsa (app, dobj, keyref, buf, buflen, depth); + else if (tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0) + err = writekey_ecc (app, dobj, keyref, buf, buflen, depth); + else + err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + + if (err) + { + /* A PIN is not required, thus use a better error code. */ + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_NO_AUTH); + log_error (_("failed to store the key: %s\n"), gpg_strerror (err)); + } + + leave: + return err; +} + + +/* Parse an RSA response object, consisting of the content of tag + * 0x7f49, into a gcrypt s-expression 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; +} + + +/* Parse an ECC response object, consisting of the content of tag + * 0x7f49, into a gcrypt s-expression object and store that R_SEXP. + * On error NULL is stored at R_SEXP. MECHANISM specifies the + * curve. */ +static gpg_error_t +genkey_parse_ecc (const unsigned char *data, size_t datalen, int mechanism, + gcry_sexp_t *r_sexp) +{ + gpg_error_t err; + const unsigned char *ecc_q; + size_t ecc_qlen; + const char *curve; + + *r_sexp = NULL; + + ecc_q = find_tlv (data, datalen, 0x0086, &ecc_qlen); + if (!ecc_q) + { + log_error (_("response does not contain the EC public key\n")); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + + if (mechanism == PIV_ALGORITHM_ECC_P256) + curve = "nistp256"; + else if (mechanism == PIV_ALGORITHM_ECC_P384) + curve = "nistp384"; + else + { + err = gpg_error (GPG_ERR_BUG); /* Call with wrong parameters. */ + goto leave; + } + + + err = gcry_sexp_build (r_sexp, NULL, "(public-key(ecc(curve%s)(q%b)))", + curve, (int)ecc_qlen, ecc_q); + + leave: + 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; + + (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; + + + /* 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) + { + /* A PIN is not required, thus use a better error code. */ + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_NO_AUTH); + log_error (_("generating key failed\n")); + return err; + } + + { + 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; + } + + tmpl[0] = mechanism; + flush_cached_data (app, dobj->tag); + err = put_data (app->slot, dobj->tag, + (int)0x80, (size_t)1, tmpl, + (int)0x7f49, (size_t)keydatalen, keydata, + (int)0, (size_t)0, NULL); + if (err) + { + log_error ("piv: failed to write key to the cert DO %s: %s\n", + dobj->keyref, gpg_strerror (err)); + goto leave; + } + + leave: + xfree (buffer); + return err; +} + + +/* Write the certificate (CERT,CERTLEN) to the card at CERTREFSTR. + * CERTREFSTR is either the OID of the certificate's container data + * object or of the form "PIV.<two_hexdigit_keyref>". */ +static gpg_error_t +do_writecert (app_t app, ctrl_t ctrl, + const char *certrefstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *cert, size_t certlen) +{ + gpg_error_t err; + data_object_t dobj; + unsigned char *pk = NULL; + unsigned char *orig_pk = NULL; + size_t pklen, orig_pklen; + + (void)ctrl; + (void)pincb; /* Not used; instead authentication is needed. */ + (void)pincb_arg; + + if (!certlen) + return gpg_error (GPG_ERR_INV_CERT_OBJ); + + dobj = find_dobj_by_keyref (app, certrefstr); + if (!dobj || !*dobj->keyref) + return gpg_error (GPG_ERR_INV_ID); + + flush_cached_data (app, dobj->tag); + + /* Check that the public key parameters from the certificate match + * an already stored key. Note that we do not allow writing a + * certificate if no key has yet been created (GPG_ERR_NOT_FOUND) or + * if there is a problem reading the public key from the certificate + * GPG_ERR_NO_PUBKEY). We enforce this because otherwise the only + * way to detect whether a key exists is by trying to use that + * key. */ + err = do_readkey (app, certrefstr, &orig_pk, &orig_pklen); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = gpg_error (GPG_ERR_NO_SECKEY); /* Use a better error code. */ + goto leave; + } + + /* Compare pubkeys. */ + err = app_help_pubkey_from_cert (cert, certlen, &pk, &pklen); + if (err) + goto leave; /* No public key in new certificate. */ + if (orig_pklen != pklen || memcmp (orig_pk, pk, pklen)) + { + err = gpg_error (GPG_ERR_CONFLICT); + goto leave; + } + + err = put_data (app->slot, dobj->tag, + (int)0x70, (size_t)certlen, cert,/* Certificate */ + (int)0x71, (size_t)1, "", /* No compress */ + (int)0xfe, (size_t)0, "", /* Empty LRC. */ + (int)0, (size_t)0, NULL); + /* A PIN is not required, thus use a better error code. */ + if (gpg_err_code (err) == GPG_ERR_BAD_PIN) + err = gpg_error (GPG_ERR_NO_AUTH); + if (err) + log_error ("piv: failed to write cert to %s: %s\n", + dobj->keyref, gpg_strerror (err)); + + leave: + xfree (pk); + xfree (orig_pk); + 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 +app_select_piv (app_t app) +{ + static char const aid[] = { 0xA0, 0x00, 0x00, 0x03, 0x08, /* RID=NIST */ + 0x00, 0x00, 0x10, 0x00 /* PIX=PIV */ }; + int slot = app->slot; + gpg_error_t err; + unsigned char *apt = NULL; + size_t aptlen; + const unsigned char *s; + size_t n; + + /* Note that we select using the AID without the 2 octet version + * number. This allows for better reporting of future specs. We + * need to use the use-zero-for-P2-flag. */ + err = iso7816_select_application_ext (slot, aid, sizeof aid, 0x0001, + &apt, &aptlen); + if (err) + goto leave; + + app->apptype = "PIV"; + app->did_chv1 = 0; + app->did_chv2 = 0; + app->did_chv3 = 0; + app->app_local = NULL; + + /* Check the Application Property Template. */ + if (opt.verbose) + { + /* We use a separate log_info to avoid the "DBG:" prefix. */ + log_info ("piv: APT="); + log_printhex (apt, aptlen, ""); + } + + s = find_tlv (apt, aptlen, 0x4F, &n); + if (!s || n != 6 || memcmp (s, aid+5, 4)) + { + /* The PIX does not match. */ + log_error ("piv: missing or invalid DO 0x4F in APT\n"); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + if (s[4] != 1 || s[5] != 0) + { + log_error ("piv: unknown PIV version %u.%u\n", s[4], s[5]); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + app->appversion = ((s[4] << 8) | s[5]); + + s = find_tlv (apt, aptlen, 0x79, &n); + if (!s || n < 7) + { + log_error ("piv: missing or invalid DO 0x79 in APT\n"); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + s = find_tlv (s, n, 0x4F, &n); + if (!s || n != 5 || memcmp (s, aid, 5)) + { + /* The RID does not match. */ + log_error ("piv: missing or invalid DO 0x79.4F in APT\n"); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + + app->app_local = xtrycalloc (1, sizeof *app->app_local); + if (!app->app_local) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (app->cardtype && !strcmp (app->cardtype, "yubikey")) + app->app_local->flags.yubikey = 1; + + + /* FIXME: Parse the optional and conditional DOs in the APT. */ + + if (opt.verbose) + dump_all_do (slot); + + app->fnc.deinit = do_deinit; + app->fnc.learn_status = do_learn_status; + app->fnc.readcert = do_readcert; + 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.sign = do_sign; + app->fnc.auth = do_auth; + app->fnc.decipher = do_decipher; + app->fnc.change_pin = do_change_chv; + app->fnc.check_pin = do_check_chv; + + +leave: + xfree (apt); + if (err) + do_deinit (app); + return err; +} @@ -67,6 +67,7 @@ lock_app (app_t app, ctrl_t ctrl) } apdu_set_progress_cb (app->slot, print_progress_line, ctrl); + apdu_set_prompt_cb (app->slot, popup_prompt, ctrl); return 0; } @@ -76,6 +77,7 @@ static void unlock_app (app_t app) { apdu_set_progress_cb (app->slot, NULL, NULL); + apdu_set_prompt_cb (app->slot, NULL, NULL); if (npth_mutex_unlock (&app->lock)) { @@ -119,6 +121,9 @@ check_conflict (app_t app, const char *name) if (!app || !name || (app->apptype && !ascii_strcasecmp (app->apptype, name))) return 0; + if (app->apptype && !strcmp (app->apptype, "UNDEFINED")) + return 0; + log_info ("application '%s' in use - can't switch\n", app->apptype? app->apptype : "<null>"); @@ -209,6 +214,64 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, if (!want_undefined) { err = iso7816_select_file (slot, 0x3F00, 1); + if (gpg_err_code (err) == GPG_ERR_CARD) + { + /* Might be SW==0x7D00. Let's test whether it is a Yubikey + * by selecting its manager application and then reading the + * config. */ + static char const yk_aid[] = + { 0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }; /*MGR*/ + unsigned char *buf; + size_t buflen; + const unsigned char *s0, *s1; + size_t n; + + if (!iso7816_select_application (slot, yk_aid, sizeof yk_aid, + 0x0001) + && !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0, + NULL, &buf, &buflen)) + { + app->cardtype = "yubikey"; + if (opt.verbose) + { + log_info ("Yubico: config="); + log_printhex (buf, buflen, ""); + } + + /* We skip the first byte which seems to be the total + * length of the config data. */ + if (buflen > 1) + { + s0 = find_tlv (buf+1, buflen-1, 0x04, &n); /* Form factor */ + if (s0 && n == 1) + { + s1 = find_tlv (buf+1, buflen-1, 0x02, &n); /* Serial */ + if (s1 && n >= 4) + { + app->serialno = xtrymalloc (3 + 1 + n); + if (app->serialno) + { + app->serialnolen = 3 + 1 + n; + app->serialno[0] = 0xff; + app->serialno[1] = 0x02; + app->serialno[2] = 0x0; + app->serialno[3] = *s0; + memcpy (app->serialno + 4, s1, n); + /* Note that we do not clear the error + * so that no further serial number + * testing is done. After all we just + * set the serial number. */ + } + } + s1 = find_tlv (buf+1, buflen-1, 0x05, &n); /* version */ + if (s1 && n == 3) + app->cardversion = ((s1[0]<<16)|(s1[1]<<8)|s1[2]); + } + } + xfree (buf); + } + } + if (!err) err = iso7816_select_file (slot, 0x2F02, 0); if (!err) @@ -249,25 +312,33 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, } } - /* For certain error codes, there is no need to try more. */ - if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT - || gpg_err_code (err) == GPG_ERR_ENODEV) - goto leave; - /* Figure out the application to use. */ if (want_undefined) { /* We switch to the "undefined" application only if explicitly requested. */ app->apptype = "UNDEFINED"; + /* Clear the error so that we don't run through the application + * selection chain. */ err = 0; } else - err = gpg_error (GPG_ERR_NOT_FOUND); + { + /* For certain error codes, there is no need to try more. */ + if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT + || gpg_err_code (err) == GPG_ERR_ENODEV) + goto leave; + + /* Set a default error so that we run through the application + * selecion chain. */ + err = gpg_error (GPG_ERR_NOT_FOUND); + } if (err && is_app_allowed ("openpgp") && (!name || !strcmp (name, "openpgp"))) err = app_select_openpgp (app); + if (err && is_app_allowed ("piv") && (!name || !strcmp (name, "piv"))) + err = app_select_piv (app); if (err && is_app_allowed ("nks") && (!name || !strcmp (name, "nks"))) err = app_select_nks (app); if (err && is_app_allowed ("p15") && (!name || !strcmp (name, "p15"))) @@ -297,11 +368,8 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, } app->periodical_check_needed = periodical_check_needed; - - npth_mutex_lock (&app_list_lock); app->next = app_top; app_top = app; - npth_mutex_unlock (&app_list_lock); unlock_app (app); return 0; } @@ -320,6 +388,8 @@ select_application (ctrl_t ctrl, const char *name, app_t *r_app, *r_app = NULL; + npth_mutex_lock (&app_list_lock); + if (scan || !app_top) { struct dev_list *l; @@ -328,7 +398,10 @@ select_application (ctrl_t ctrl, const char *name, app_t *r_app, /* Scan the devices to find new device(s). */ err = apdu_dev_list_start (opt.reader_port, &l); if (err) - return err; + { + npth_mutex_unlock (&app_list_lock); + return err; + } while (1) { @@ -363,7 +436,6 @@ select_application (ctrl_t ctrl, const char *name, app_t *r_app, scd_kick_the_loop (); } - npth_mutex_lock (&app_list_lock); for (a = app_top; a; a = a->next) { lock_app (a, ctrl); @@ -406,6 +478,7 @@ get_supported_applications (void) { const char *list[] = { "openpgp", + "piv", "nks", "p15", "geldkarte", @@ -506,6 +579,7 @@ release_application (app_t app, int locked_already) FF 00 00 = For serial numbers starting with an FF FF 01 00 = Some german p15 cards return an empty serial number so the serial number from the EF(TokenInfo) is used instead. + FF 02 00 = Serial number from Yubikey config FF 7F 00 = No serialno. All other serial number not starting with FF are used as they are. @@ -561,7 +635,7 @@ app_get_serialno (app_t app) } -/* Write out the application specifig status lines for the LEARN +/* Write out the application specific status lines for the LEARN command. */ gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) @@ -573,9 +647,19 @@ app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) if (!app->fnc.learn_status) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - /* We do not send APPTYPE if only keypairinfo is requested. */ - if (app->apptype && !(flags & 1)) - send_status_direct (ctrl, "APPTYPE", app->apptype); + /* We do not send CARD and APPTYPE if only keypairinfo is requested. */ + if (!(flags &1)) + { + if (app->cardtype) + send_status_direct (ctrl, "CARDTYPE", app->cardtype); + if (app->cardversion) + send_status_printf (ctrl, "CARDVERSION", "%X", app->cardversion); + if (app->apptype) + send_status_direct (ctrl, "APPTYPE", app->apptype); + if (app->appversion) + send_status_printf (ctrl, "APPVERSION", "%X", app->appversion); + } + err = lock_app (app, ctrl); if (err) return err; @@ -618,7 +702,7 @@ app_readcert (app_t app, ctrl_t ctrl, const char *certid, This function might not be supported by all applications. */ gpg_error_t -app_readkey (app_t app, ctrl_t ctrl, int advanced, const char *keyid, +app_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned char **pk, size_t *pklen) { gpg_error_t err; @@ -637,7 +721,7 @@ app_readkey (app_t app, ctrl_t ctrl, int advanced, const char *keyid, err = lock_app (app, ctrl); if (err) return err; - err= app->fnc.readkey (app, advanced, keyid, pk, pklen); + err= app->fnc.readkey (app, keyid, pk, pklen); unlock_app (app); return err; } @@ -654,6 +738,11 @@ app_getattr (app_t app, ctrl_t ctrl, const char *name) if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (app->cardtype && name && !strcmp (name, "CARDTYPE")) + { + send_status_direct (ctrl, "CARDTYPE", app->cardtype); + return 0; + } if (app->apptype && name && !strcmp (name, "APPTYPE")) { send_status_direct (ctrl, "APPTYPE", app->apptype); @@ -677,7 +766,7 @@ app_getattr (app_t app, ctrl_t ctrl, const char *name) err = lock_app (app, ctrl); if (err) return err; - err = app->fnc.getattr (app, ctrl, name); + err = app->fnc.getattr (app, ctrl, name); unlock_app (app); return err; } @@ -863,8 +952,8 @@ app_writekey (app_t app, ctrl_t ctrl, /* Perform a SETATTR operation. */ gpg_error_t -app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, - time_t createtime, +app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, + const char *keytype, unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { @@ -879,7 +968,7 @@ app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, err = lock_app (app, ctrl); if (err) return err; - err = app->fnc.genkey (app, ctrl, keynostr, flags, + err = app->fnc.genkey (app, ctrl, keynostr, keytype, flags, createtime, pincb, pincb_arg); unlock_app (app); if (opt.verbose) @@ -912,7 +1001,8 @@ app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer) /* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */ gpg_error_t -app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, +app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, + unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { @@ -927,8 +1017,7 @@ app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, err = lock_app (app, ctrl); if (err) return err; - err = app->fnc.change_pin (app, ctrl, chvnostr, reset_mode, - pincb, pincb_arg); + err = app->fnc.change_pin (app, ctrl, chvnostr, flags, pincb, pincb_arg); unlock_app (app); if (opt.verbose) log_info ("operation change_pin result: %s\n", gpg_strerror (err)); @@ -936,7 +1025,7 @@ app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, } -/* Perform a VERIFY operation without doing anything lese. This may +/* Perform a VERIFY operation without doing anything else. This may be used to initialize a the PIN cache for long lasting other operations. Its use is highly application dependent. */ gpg_error_t diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c index ae40f0118..69df17355 100644 --- a/scd/ccid-driver.c +++ b/scd/ccid-driver.c @@ -255,6 +255,9 @@ struct ccid_driver_s void (*progress_cb)(void *, const char *, int, int, int); void *progress_cb_arg; + void (*prompt_cb)(void *, int); + void *prompt_cb_arg; + unsigned char intr_buf[64]; struct libusb_transfer *transfer; }; @@ -1467,8 +1470,7 @@ intr_cb (struct libusb_transfer *transfer) DEBUGOUT_1 ("CCID: interrupt callback %d\n", transfer->status); - if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT - || transfer->status == LIBUSB_TRANSFER_NO_DEVICE) + if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT) { int err; @@ -1651,15 +1653,13 @@ ccid_open_usb_reader (const char *spec_reader_name, goto leave; } - if (set_no != 0) + /* Submit SET_INTERFACE control transfer which can reset the device. */ + rc = libusb_set_interface_alt_setting (idev, ifc_no, set_no); + if (rc) { - rc = libusb_set_interface_alt_setting (idev, ifc_no, set_no); - if (rc) - { - DEBUGOUT_1 ("usb_set_interface_alt_setting failed: %d\n", rc); - rc = CCID_DRIVER_ERR_CARD_IO_ERROR; - goto leave; - } + DEBUGOUT_1 ("usb_set_interface_alt_setting failed: %d\n", rc); + rc = CCID_DRIVER_ERR_CARD_IO_ERROR; + goto leave; } rc = ccid_vendor_specific_init (*handle); @@ -1802,6 +1802,19 @@ ccid_set_progress_cb (ccid_driver_t handle, } +int +ccid_set_prompt_cb (ccid_driver_t handle, + void (*cb)(void *, int), void *cb_arg) +{ + if (!handle) + return CCID_DRIVER_ERR_INV_VALUE; + + handle->prompt_cb = cb; + handle->prompt_cb_arg = cb_arg; + return 0; +} + + /* Close the reader HANDLE. */ int ccid_close_reader (ccid_driver_t handle) @@ -1921,7 +1934,7 @@ bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen, is the sequence number used to send the request and EXPECTED_TYPE the type of message we expect. Does checks on the ccid header. TIMEOUT is the timeout value in ms. NO_DEBUG may be set to - avoid debug messages in case of no error; this can be overriden + avoid debug messages in case of no error; this can be overridden with a glibal debug level of at least 3. Returns 0 on success. */ static int bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, @@ -1930,6 +1943,7 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, { int rc; int msglen; + int notified = 0; /* Fixme: The next line for the current Valgrind without support for USB IOCTLs. */ @@ -1982,14 +1996,25 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, we got the expected message type. This is in particular required for the Cherry keyboard which sends a time extension request for each key hit. */ - if ( !(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80) + if (!(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80) { /* Card present and active, time extension requested. */ DEBUGOUT_2 ("time extension requested (%02X,%02X)\n", buffer[7], buffer[8]); + + /* Gnuk enhancement to prompt user input by ack button */ + if (buffer[8] == 0xff && !notified) + { + notified = 1; + handle->prompt_cb (handle->prompt_cb_arg, 1); + } + goto retry; } + if (notified) + handle->prompt_cb (handle->prompt_cb_arg, 0); + if (buffer[0] != expected_type && buffer[0] != RDR_to_PC_SlotStatus) { DEBUGOUT_1 ("unexpected bulk-in msg type (%02x)\n", buffer[0]); @@ -2928,7 +2953,7 @@ ccid_transceive_apdu_level (ccid_driver_t handle, bit 3 unused bit 2..0 Source Node Address (SAD) - If node adresses are not used, SAD and DAD should be set to 0 on + If node addresses are not used, SAD and DAD should be set to 0 on the first block sent to the card. If they are used they should have different values (0 for one is okay); that first block sets up the addresses of the nodes. @@ -3270,7 +3295,7 @@ ccid_transceive (ccid_driver_t handle, /* Wait time extension request. */ unsigned char bwi = tpdu[3]; - /* Check if it's unsual value which can't be expressed in ATR. */ + /* Check if it's unusual value which can't be expressed in ATR. */ if (bwi > 15) wait_more = 1; diff --git a/scd/ccid-driver.h b/scd/ccid-driver.h index c31c25fa7..1550b3eba 100644 --- a/scd/ccid-driver.h +++ b/scd/ccid-driver.h @@ -126,6 +126,8 @@ int ccid_open_reader (const char *spec_reader_name, int ccid_set_progress_cb (ccid_driver_t handle, void (*cb)(void *, const char *, int, int, int), void *cb_arg); +int ccid_set_prompt_cb (ccid_driver_t handle, void (*cb)(void *, int), + void *cb_arg); int ccid_shutdown_reader (ccid_driver_t handle); int ccid_close_reader (ccid_driver_t handle); int ccid_get_atr (ccid_driver_t handle, diff --git a/scd/command.c b/scd/command.c index 66d9fb971..0d1a5cd3f 100644 --- a/scd/command.c +++ b/scd/command.c @@ -55,6 +55,9 @@ /* Maximum allowed size of certificate data as used in inquiries. */ #define MAXLEN_CERTDATA 16384 +/* Maximum allowed size for "SETATTR --inquire". */ +#define MAXLEN_SETATTRDATA 16384 + #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) @@ -333,7 +336,7 @@ static const char hlp_learn[] = "or a \"CANCEL\" to force the function to terminate with a Cancel\n" "error message.\n" "\n" - "With the option --keypairinfo only KEYPARIINFO lstatus lines are\n" + "With the option --keypairinfo only KEYPARIINFO status lines are\n" "returned.\n" "\n" "The response of this command is a list of status lines formatted as\n" @@ -346,11 +349,12 @@ static const char hlp_learn[] = " P15 = PKCS-15 structure used\n" " DINSIG = DIN SIG\n" " OPENPGP = OpenPGP card\n" + " PIV = PIV card\n" " NKS = NetKey card\n" "\n" "are implemented. These strings are aliases for the AID\n" "\n" - " S KEYPAIRINFO <hexstring_with_keygrip> <hexstring_with_id>\n" + " S KEYPAIRINFO <hexstring_with_keygrip> <hexstring_with_id> [<usage>]\n" "\n" "If there is no certificate yet stored on the card a single 'X' is\n" "returned as the keygrip. In addition to the keypair info, information\n" @@ -465,7 +469,7 @@ cmd_learn (assuan_context_t ctx, char *line) static const char hlp_readcert[] = - "READCERT <hexified_certid>|<keyid>\n" + "READCERT <hexified_certid>|<keyid>|<oid>\n" "\n" "Note, that this function may even be used on a locked card."; static gpg_error_t @@ -498,7 +502,7 @@ cmd_readcert (assuan_context_t ctx, char *line) static const char hlp_readkey[] = - "READKEY [--advanced] <keyid>\n" + "READKEY [--advanced] <keyid>|<oid>\n" "\n" "Return the public key for the given cert or key ID as a standard\n" "S-expression.\n" @@ -512,11 +516,8 @@ cmd_readkey (assuan_context_t ctx, char *line) int rc; int advanced = 0; unsigned char *cert = NULL; - size_t ncert, n; - ksba_cert_t kc = NULL; - ksba_sexp_t p; - unsigned char *pk; - size_t pklen; + unsigned char *pk = NULL; + size_t ncert, pklen; if ((rc = open_card (ctrl))) return rc; @@ -525,59 +526,68 @@ cmd_readkey (assuan_context_t ctx, char *line) advanced = 1; line = skip_options (line); - line = xstrdup (line); /* Need a copy of the line. */ + /* If the application supports the READKEY function we use that. Otherwise we use the old way by extracting it from the certificate. */ - rc = app_readkey (ctrl->app_ctx, ctrl, advanced, line, &pk, &pklen); + rc = app_readkey (ctrl->app_ctx, ctrl, line, &pk, &pklen); if (!rc) - { /* Yeah, got that key - send it back. */ - rc = assuan_send_data (ctx, pk, pklen); - xfree (pk); - xfree (line); - line = NULL; - goto leave; - } - - if (gpg_err_code (rc) != GPG_ERR_UNSUPPORTED_OPERATION) - log_error ("app_readkey failed: %s\n", gpg_strerror (rc)); - else + ; /* Okay, got that key. */ + else if (gpg_err_code (rc) == GPG_ERR_UNSUPPORTED_OPERATION + || gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { + /* Fall back to certificate reading. */ rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert); if (rc) - log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); + { + log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); + goto leave; + } + rc = app_help_pubkey_from_cert (cert, ncert, &pk, &pklen); + if (rc) + { + log_error ("failed to parse the certificate: %s\n", + gpg_strerror (rc)); + goto leave; + } } - xfree (line); - line = NULL; - if (rc) - goto leave; - - rc = ksba_cert_new (&kc); - if (rc) - goto leave; - - rc = ksba_cert_init_from_mem (kc, cert, ncert); - if (rc) + else { - log_error ("failed to parse the certificate: %s\n", gpg_strerror (rc)); + log_error ("app_readkey failed: %s\n", gpg_strerror (rc)); goto leave; } - p = ksba_cert_get_public_key (kc); - if (!p) + if (advanced) { - rc = gpg_error (GPG_ERR_NO_PUBKEY); - goto leave; - } + gcry_sexp_t s_key; + unsigned char *pkadv; + size_t pkadvlen; - n = gcry_sexp_canon_len (p, 0, NULL, NULL); - rc = assuan_send_data (ctx, p, n); - xfree (p); + rc = gcry_sexp_new (&s_key, pk, pklen, 0); + if (rc) + goto leave; + pkadvlen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0); + pkadv = xtrymalloc (pkadvlen); + if (!pkadv) + { + rc = gpg_error_from_syserror (); + goto leave; + } + log_assert (pkadvlen); + + gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, pkadv, pkadvlen); + gcry_sexp_release (s_key); + /* (One less to adjust for the trailing '\0') */ + rc = assuan_send_data (ctx, pkadv, pkadvlen-1); + xfree (pkadv); + } + else + rc = assuan_send_data (ctx, pk, pklen); leave: - ksba_cert_release (kc); + xfree (pk); xfree (cert); return rc; } @@ -902,7 +912,7 @@ cmd_getattr (assuan_context_t ctx, char *line) static const char hlp_setattr[] = - "SETATTR <name> <value> \n" + "SETATTR [--inquire] <name> <value> \n" "\n" "This command is used to store data on a smartcard. The allowed\n" "names and values are depend on the currently selected smartcard\n" @@ -911,6 +921,10 @@ static const char hlp_setattr[] = "However, the current implementation assumes that NAME is not\n" "escaped; this works as long as no one uses arbitrary escaping.\n" "\n" + "If the option --inquire is used, VALUE shall not be given; instead\n" + "an inquiry using the keyword \"VALUE\" is used to retrieve it. The\n" + "value is in this case considered to be confidential and not logged.\n" + "\n" "A PIN will be requested for most NAMEs. See the corresponding\n" "setattr function of the actually used application (app-*.c) for\n" "details."; @@ -918,14 +932,18 @@ static gpg_error_t cmd_setattr (assuan_context_t ctx, char *orig_line) { ctrl_t ctrl = assuan_get_pointer (ctx); - int rc; + gpg_error_t err; char *keyword; int keywordlen; size_t nbytes; char *line, *linebuf; + int opt_inquire; - if ((rc = open_card (ctrl))) - return rc; + opt_inquire = has_option (orig_line, "--inquire"); + orig_line = skip_options (orig_line); + + if ((err = open_card (ctrl))) + return err; /* We need to use a copy of LINE, because PIN_CB uses the same context and thus reuses the Assuan provided LINE. */ @@ -940,20 +958,38 @@ cmd_setattr (assuan_context_t ctx, char *orig_line) *line++ = 0; while (spacep (line)) line++; - nbytes = percent_plus_unescape_inplace (line, 0); + if (opt_inquire) + { + unsigned char *value; - rc = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, - (const unsigned char*)line, nbytes); - xfree (linebuf); + assuan_begin_confidential (ctx); + err = assuan_inquire (ctx, "VALUE", &value, &nbytes, MAXLEN_SETATTRDATA); + assuan_end_confidential (ctx); + if (!err) + { + err = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, + value, nbytes); + wipememory (value, nbytes); + xfree (value); + } - return rc; + } + else + { + nbytes = percent_plus_unescape_inplace (line, 0); + err = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, + (const unsigned char*)line, nbytes); + } + + xfree (linebuf); + return err; } static const char hlp_writecert[] = "WRITECERT <hexified_certid>\n" "\n" - "This command is used to store a certifciate on a smartcard. The\n" + "This command is used to store a certificate on a smartcard. The\n" "allowed certids depend on the currently selected smartcard\n" "application. The actual certifciate is requested using the inquiry\n" "\"CERTDATA\" and needs to be provided in its raw (e.g. DER) form.\n" @@ -1072,10 +1108,10 @@ cmd_writekey (assuan_context_t ctx, char *line) static const char hlp_genkey[] = - "GENKEY [--force] [--timestamp=<isodate>] <no>\n" + "GENKEY [--force] [--timestamp=<isodate>] <keyref>\n" "\n" - "Generate a key on-card identified by NO, which is application\n" - "specific. Return values are application specific. For OpenPGP\n" + "Generate a key on-card identified by <keyref>, which is application\n" + "specific. Return values are also application specific. For OpenPGP\n" "cards 3 status lines are returned:\n" "\n" " S KEY-FPR <hexstring>\n" @@ -1084,7 +1120,7 @@ static const char hlp_genkey[] = "\n" " 'p' and 'n' are the names of the RSA parameters; '-' is used to\n" " indicate that HEXDATA is the first chunk of a parameter given\n" - " by the next KEY-DATA.\n" + " by the next KEY-DATA. Only used by GnuPG version < 2.1.\n" "\n" "--force is required to overwrite an already existing key. The\n" "KEY-CREATED-AT is required for further processing because it is\n" @@ -1100,10 +1136,12 @@ static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); - int rc; - char *keyno; + gpg_error_t err; + char *keyref_buffer = NULL; + char *keyref; int force; const char *s; + char *opt_algo = NULL; time_t timestamp; force = has_option (line, "--force"); @@ -1119,29 +1157,38 @@ cmd_genkey (assuan_context_t ctx, char *line) else timestamp = 0; + err = get_option_value (line, "--algo", &opt_algo); + if (err) + goto leave; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no key number given"); - keyno = line; + keyref = line; while (*line && !spacep (line)) line++; *line = 0; - if ((rc = open_card (ctrl))) - return rc; + if ((err = open_card (ctrl))) + goto leave; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - keyno = xtrystrdup (keyno); - if (!keyno) - return out_of_core (); - rc = app_genkey (ctrl->app_ctx, ctrl, keyno, force? 1:0, - timestamp, pin_cb, ctx); - xfree (keyno); + keyref = keyref_buffer = xtrystrdup (keyref); + if (!keyref) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = app_genkey (ctrl->app_ctx, ctrl, keyref, opt_algo, + force? APP_GENKEY_FLAG_FORCE : 0, + timestamp, pin_cb, ctx); - return rc; + leave: + xfree (keyref_buffer); + xfree (opt_algo); + return err; } @@ -1191,12 +1238,13 @@ cmd_random (assuan_context_t ctx, char *line) static const char hlp_passwd[] = - "PASSWD [--reset] [--nullpin] <chvno>\n" + "PASSWD [--reset] [--nullpin] [--clear] <chvno>\n" "\n" "Change the PIN or, if --reset is given, reset the retry counter of\n" "the card holder verification vector CHVNO. The option --nullpin is\n" - "used for TCOS cards to set the initial PIN. The format of CHVNO\n" - "depends on the card application."; + "used for TCOS cards to set the initial PIN. The option --clear clears\n" + "the security status associated with the PIN so that the PIN needs to\n" + "be presented again. The format of CHVNO depends on the card application."; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { @@ -1209,6 +1257,8 @@ cmd_passwd (assuan_context_t ctx, char *line) flags |= APP_CHANGE_FLAG_RESET; if (has_option (line, "--nullpin")) flags |= APP_CHANGE_FLAG_NULLPIN; + if (has_option (line, "--clear")) + flags |= APP_CHANGE_FLAG_CLEAR; line = skip_options (line); @@ -1219,6 +1269,11 @@ cmd_passwd (assuan_context_t ctx, char *line) line++; *line = 0; + /* Do not allow other flags aside of --clear. */ + if ((flags & APP_CHANGE_FLAG_CLEAR) && (flags & ~APP_CHANGE_FLAG_CLEAR)) + return set_error (GPG_ERR_UNSUPPORTED_OPERATION, + "--clear used with other options"); + if ((rc = open_card (ctrl))) return rc; @@ -1640,7 +1695,7 @@ cmd_apdu (assuan_context_t ctx, char *line) rc = apdu_send_direct (app->slot, exlen, apdu, apdulen, handle_more, - &result, &resultlen); + NULL, &result, &resultlen); if (rc) log_error ("apdu_send_direct failed: %s\n", gpg_strerror (rc)); else @@ -1898,6 +1953,54 @@ send_status_direct (ctrl_t ctrl, const char *keyword, const char *args) } +/* This status functions expects a printf style format string. No + * filtering of the data is done instead the orintf formatted data is + * send using assuan_send_status. */ +gpg_error_t +send_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...) +{ + gpg_error_t err; + va_list arg_ptr; + assuan_context_t ctx; + + if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx)) + return 0; + + va_start (arg_ptr, format); + err = vprint_assuan_status (ctx, keyword, format, arg_ptr); + va_end (arg_ptr); + return err; +} + + +void +popup_prompt (void *opaque, int on) +{ + ctrl_t ctrl = opaque; + + if (ctrl) + { + assuan_context_t ctx = ctrl->server_local->assuan_ctx; + + if (ctx) + { + const char *cmd; + gpg_error_t err; + unsigned char *value; + size_t valuelen; + + if (on) + cmd = "POPUPPINPADPROMPT --ack"; + else + cmd = "DISMISSPINPADPROMPT"; + err = assuan_inquire (ctx, cmd, &value, &valuelen, 100); + if (!err) + xfree (value); + } + } +} + + /* Helper to send the clients a status change notification. */ void send_client_notifications (app_t app, int removal) diff --git a/scd/iso7816.c b/scd/iso7816.c index 29208c254..d9f3336c7 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -50,6 +50,7 @@ #define CMD_PUT_DATA 0xDA #define CMD_MSE 0x22 #define CMD_PSO 0x2A +#define CMD_GENERAL_AUTHENTICATE 0x87 #define CMD_INTERNAL_AUTHENTICATE 0x88 #define CMD_GENERATE_KEYPAIR 0x47 #define CMD_GET_CHALLENGE 0x84 @@ -66,6 +67,7 @@ map_sw (int sw) case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break; case SW_TERM_STATE: ec = GPG_ERR_OBJ_TERM_STATE; break; case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break; + case SW_ACK_TIMEOUT: ec = GPG_ERR_TIMEOUT; break; case SW_SM_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; case SW_CC_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; case SW_CHV_WRONG: ec = GPG_ERR_BAD_PIN; break; @@ -138,6 +140,21 @@ iso7816_select_application (int slot, const char *aid, size_t aidlen, } +/* This is the same as iso7816_select_application but may return data + * at RESULT,RESULTLEN). */ +gpg_error_t +iso7816_select_application_ext (int slot, const char *aid, size_t aidlen, + unsigned int flags, + unsigned char **result, size_t *resultlen) +{ + int sw; + sw = apdu_send (slot, 0, 0x00, CMD_SELECT_FILE, 4, + (flags&1)? 0:0x0c, aidlen, aid, + result, resultlen); + return map_sw (sw); +} + + gpg_error_t iso7816_select_file (int slot, int tag, int is_dir) { @@ -205,29 +222,66 @@ iso7816_list_directory (int slot, int list_dirs, } +/* Wrapper around apdu_send. RESULT can be NULL if no result is + * expected. In addition to an gpg-error return code the actual + * status word is stored at R_SW unless that is NULL. */ +gpg_error_t +iso7816_send_apdu (int slot, int extended_mode, + int class, int ins, int p0, int p1, + int lc, const void *data, + unsigned int *r_sw, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (result) + { + *result = NULL; + *resultlen = 0; + } + + sw = apdu_send (slot, extended_mode, class, ins, p0, p1, lc, data, + result, resultlen); + if (sw != SW_SUCCESS && result) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + } + if (r_sw) + *r_sw = sw; + return map_sw (sw); +} + + /* This function sends an already formatted APDU to the card. With HANDLE_MORE set to true a MORE DATA status will be handled internally. The return value is a gpg error code (i.e. a mapped status word). This is basically the same as apdu_send_direct but it maps the status word and does not return it in the result - buffer. */ + buffer. However, it R_SW is not NULL the status word is stored + R_SW for closer inspection. */ gpg_error_t iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, - int handle_more, + int handle_more, unsigned int *r_sw, unsigned char **result, size_t *resultlen) { - int sw; + int sw, sw2; - if (!result || !resultlen) - return gpg_error (GPG_ERR_INV_VALUE); - *result = NULL; - *resultlen = 0; + if (result) + { + *result = NULL; + *resultlen = 0; + } sw = apdu_send_direct (slot, 0, apdudata, apdudatalen, handle_more, - result, resultlen); + &sw2, result, resultlen); if (!sw) { - if (*resultlen < 2) + if (!result) + sw = sw2; + else if (*resultlen < 2) sw = SW_HOST_GENERAL_ERROR; else { @@ -236,13 +290,15 @@ iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, (*resultlen)--; } } - if (sw != SW_SUCCESS) + if (sw != SW_SUCCESS && result) { /* Make sure that pending buffers are released. */ xfree (*result); *result = NULL; *resultlen = 0; } + if (r_sw) + *r_sw = sw; return map_sw (sw); } @@ -324,6 +380,7 @@ iso7816_change_reference_data (int slot, int chvno, sw = apdu_send_simple (slot, 0, 0x00, CMD_CHANGE_REFERENCE_DATA, oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); + wipememory (buf, oldchvlen+newchvlen); xfree (buf); return map_sw (sw); @@ -396,6 +453,70 @@ iso7816_get_data (int slot, int extended_mode, int tag, } +/* Perform a GET DATA command requesting TAG and storing the result in + * a newly allocated buffer at the address passed by RESULT. Return + * the length of this data at the address of RESULTLEN. This variant + * is needed for long (3 octet) tags. */ +gpg_error_t +iso7816_get_data_odd (int slot, int extended_mode, unsigned int tag, + unsigned char **result, size_t *resultlen) +{ + int sw; + int le; + int datalen; + unsigned char data[5]; + + if (!result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + if (extended_mode > 0 && extended_mode < 256) + le = 65534; /* Not 65535 in case it is used as some special flag. */ + else if (extended_mode > 0) + le = extended_mode; + else + le = 256; + + data[0] = 0x5c; + if (tag <= 0xff) + { + data[1] = 1; + data[2] = tag; + datalen = 3; + } + else if (tag <= 0xffff) + { + data[1] = 2; + data[2] = (tag >> 8); + data[3] = tag; + datalen = 4; + } + else + { + data[1] = 3; + data[2] = (tag >> 16); + data[3] = (tag >> 8); + data[4] = tag; + datalen = 5; + } + + sw = apdu_send_le (slot, extended_mode, 0x00, CMD_GET_DATA + 1, + 0x3f, 0xff, datalen, data, le, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + /* Perform a PUT DATA command on card in SLOT. Write DATA of length DATALEN to TAG. EXTENDED_MODE controls whether extended length headers or command chaining is used instead of single length @@ -427,7 +548,7 @@ iso7816_put_data_odd (int slot, int extended_mode, int tag, /* Manage Security Environment. This is a weird operation and there is no easy abstraction for it. Furthermore, some card seem to have - a different interpreation of 7816-8 and thus we resort to let the + a different interpretation of 7816-8 and thus we resort to let the caller decide what to do. */ gpg_error_t iso7816_manage_security_env (int slot, int p1, int p2, @@ -445,7 +566,7 @@ iso7816_manage_security_env (int slot, int p1, int p2, /* Perform the security operation COMPUTE DIGITAL SIGANTURE. On - success 0 is returned and the data is availavle in a newly + success 0 is returned and the data is available in a newly allocated buffer stored at RESULT with its length stored at RESULTLEN. For LE see do_generate_keypair. */ gpg_error_t @@ -542,7 +663,7 @@ iso7816_decipher (int slot, int extended_mode, } -/* For LE see do_generate_keypair. */ +/* For LE see do_generate_keypair. */ gpg_error_t iso7816_internal_authenticate (int slot, int extended_mode, const unsigned char *data, size_t datalen, @@ -579,12 +700,50 @@ iso7816_internal_authenticate (int slot, int extended_mode, } +/* For LE see do_generate_keypair. */ +gpg_error_t +iso7816_general_authenticate (int slot, int extended_mode, + int algoref, int keyref, + const unsigned char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + if (!extended_mode) + le = 256; /* Ignore provided Le and use what apdu_send uses. */ + else if (le >= 0 && le < 256) + le = 256; + + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_GENERAL_AUTHENTICATE, algoref, keyref, + datalen, (const char*)data, + le, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + /* LE is the expected return length. This is usually 0 except if extended length mode is used and more than 256 byte will be returned. In that case a value of -1 uses a large default (e.g. 4096 bytes), a value larger 256 used that value. */ static gpg_error_t -do_generate_keypair (int slot, int extended_mode, int read_only, +do_generate_keypair (int slot, int extended_mode, int p1, int p2, const char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen) { @@ -596,7 +755,7 @@ do_generate_keypair (int slot, int extended_mode, int read_only, *resultlen = 0; sw = apdu_send_le (slot, extended_mode, - 0x00, CMD_GENERATE_KEYPAIR, read_only? 0x81:0x80, 0, + 0x00, CMD_GENERATE_KEYPAIR, p1, p2, datalen, data, le >= 0 && le < 256? 256:le, result, resultlen); @@ -614,12 +773,12 @@ do_generate_keypair (int slot, int extended_mode, int read_only, gpg_error_t -iso7816_generate_keypair (int slot, int extended_mode, +iso7816_generate_keypair (int slot, int extended_mode, int p1, int p2, const char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen) { - return do_generate_keypair (slot, extended_mode, 0, + return do_generate_keypair (slot, extended_mode, p1, p2, data, datalen, le, result, resultlen); } @@ -630,7 +789,7 @@ iso7816_read_public_key (int slot, int extended_mode, int le, unsigned char **result, size_t *resultlen) { - return do_generate_keypair (slot, extended_mode, 1, + return do_generate_keypair (slot, extended_mode, 0x81, 0, data, datalen, le, result, resultlen); } diff --git a/scd/iso7816.h b/scd/iso7816.h index 4c71bbd50..c1940ad8d 100644 --- a/scd/iso7816.h +++ b/scd/iso7816.h @@ -51,14 +51,24 @@ gpg_error_t iso7816_map_sw (int sw); gpg_error_t iso7816_select_application (int slot, const char *aid, size_t aidlen, unsigned int flags); +gpg_error_t iso7816_select_application_ext (int slot, + const char *aid, size_t aidlen, + unsigned int flags, + unsigned char **result, + size_t *resultlen); gpg_error_t iso7816_select_file (int slot, int tag, int is_dir); gpg_error_t iso7816_select_path (int slot, const unsigned short *path, size_t pathlen); gpg_error_t iso7816_list_directory (int slot, int list_dirs, unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_send_apdu (int slot, int extended_mode, + int class, int ins, int p0, int p1, + int lc, const void *data, + unsigned int *r_sw, + unsigned char **result, size_t *resultlen); gpg_error_t iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, - int handle_more, + int handle_more, unsigned int *r_sw, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_check_pinpad (int slot, int command, pininfo_t *pininfo); @@ -78,6 +88,8 @@ gpg_error_t iso7816_reset_retry_counter_with_rc (int slot, int chvno, size_t datalen); gpg_error_t iso7816_get_data (int slot, int extended_mode, int tag, unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_get_data_odd (int slot, int extended_mode, unsigned int tag, + unsigned char **result, size_t *resultlen); gpg_error_t iso7816_put_data (int slot, int extended_mode, int tag, const void *data, size_t datalen); gpg_error_t iso7816_put_data_odd (int slot, int extended_mode, int tag, @@ -97,10 +109,19 @@ gpg_error_t iso7816_internal_authenticate (int slot, int extended_mode, const unsigned char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_general_authenticate (int slot, int extended_mode, + int algoref, int keyref, + const unsigned char *data, + size_t datalen, + int le, + unsigned char **result, + size_t *resultlen); gpg_error_t iso7816_generate_keypair (int slot, int extended_mode, - const char *data, size_t datalen, - int le, - unsigned char **result, size_t *resultlen); + int p1, int p2, + const char *data, size_t datalen, + int le, + unsigned char **result, + size_t *resultlen); gpg_error_t iso7816_read_public_key (int slot, int extended_mode, const char *data, size_t datalen, int le, diff --git a/scd/scdaemon.c b/scd/scdaemon.c index 8f8a02619..507108db0 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -1069,7 +1069,7 @@ handle_signal (int signo) /* Create a name for the socket. We check for valid characters as well as against a maximum allowed length for a unix domain socket is done. The function terminates the process in case of an error. - Retunrs: Pointer to an allcoated string with the absolute name of + Returns: Pointer to an allcoated string with the absolute name of the socket used. */ static char * create_socket_name (char *standard_name) diff --git a/scd/scdaemon.h b/scd/scdaemon.h index 4797f3df0..73589ade8 100644 --- a/scd/scdaemon.h +++ b/scd/scdaemon.h @@ -123,6 +123,10 @@ int scd_command_handler (ctrl_t, int); void send_status_info (ctrl_t ctrl, const char *keyword, ...) GPGRT_ATTR_SENTINEL(1); void send_status_direct (ctrl_t ctrl, const char *keyword, const char *args); +gpg_error_t send_status_printf (ctrl_t ctrl, const char *keyword, + const char *format, ...) GPGRT_ATTR_PRINTF(3,4); + +void popup_prompt (void *opaque, int on); void send_client_notifications (app_t app, int removal); void scd_kick_the_loop (void); int get_active_connection_count (void); diff --git a/sm/call-agent.c b/sm/call-agent.c index 20d879fa4..4f2b83f56 100644 --- a/sm/call-agent.c +++ b/sm/call-agent.c @@ -334,7 +334,7 @@ gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, unsigned char *digest, size_t digestlen, int digestalgo, unsigned char **r_buf, size_t *r_buflen ) { - int rc, i; + int rc, i, pkalgo; char *p, line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; @@ -342,6 +342,7 @@ gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, unsigned char *sigbuf; size_t sigbuflen; struct default_inq_parm_s inq_parm; + gcry_sexp_t sig; (void)desc; @@ -353,6 +354,7 @@ gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, case GCRY_MD_RMD160:hashopt = "--hash=rmd160"; break; case GCRY_MD_MD5: hashopt = "--hash=md5"; break; case GCRY_MD_SHA256:hashopt = "--hash=sha256"; break; + case GCRY_MD_SHA512:hashopt = "--hash=sha512"; break; default: return gpg_error (GPG_ERR_DIGEST_ALGO); } @@ -366,6 +368,23 @@ gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, if (digestlen*2 + 50 > DIM(line)) return gpg_error (GPG_ERR_GENERAL); + /* Get the key type from the scdaemon. */ + snprintf (line, DIM(line), "SCD READKEY %s", keyid); + init_membuf (&data, 1024); + rc = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, NULL, NULL, NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return rc; + } + + p = get_membuf (&data, &len); + pkalgo = get_pk_algo_from_canon_sexp (p, len); + xfree (p); + if (!pkalgo) + return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + p = stpcpy (line, "SCD SETDATA " ); for (i=0; i < digestlen ; i++, p += 2 ) sprintf (p, "%02X", digest[i]); @@ -386,24 +405,37 @@ gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, } sigbuf = get_membuf (&data, &sigbuflen); - /* Create an S-expression from it which is formatted like this: - "(7:sig-val(3:rsa(1:sSIGBUFLEN:SIGBUF)))" Fixme: If a card ever - creates non-RSA keys we need to change things. */ - *r_buflen = 21 + 11 + sigbuflen + 4; - p = xtrymalloc (*r_buflen); - *r_buf = (unsigned char*)p; - if (!p) + switch(pkalgo) { - xfree (sigbuf); - return 0; + case GCRY_PK_RSA: + rc = gcry_sexp_build (&sig, NULL, "(sig-val(rsa(s%b)))", + sigbuflen, sigbuf); + break; + + case GCRY_PK_ECC: + rc = gcry_sexp_build (&sig, NULL, "(sig-val(ecdsa(r%b)(s%b)))", + sigbuflen/2, sigbuf, + sigbuflen/2, sigbuf + sigbuflen/2); + break; + + case GCRY_PK_EDDSA: + rc = gcry_sexp_build (&sig, NULL, "(sig-val(eddsa(r%b)(s%b)))", + sigbuflen/2, sigbuf, + sigbuflen/2, sigbuf + sigbuflen/2); + break; + + default: + rc = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + break; } - p = stpcpy (p, "(7:sig-val(3:rsa(1:s" ); - sprintf (p, "%u:", (unsigned int)sigbuflen); - p += strlen (p); - memcpy (p, sigbuf, sigbuflen); - p += sigbuflen; - strcpy (p, ")))"); xfree (sigbuf); + if (rc) + return rc; + + rc = make_canon_sexp (sig, r_buf, r_buflen); + gcry_sexp_release (sig); + if (rc) + return rc; assert (gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL)); return 0; diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c index 3a38bca50..bff7dd652 100644 --- a/sm/call-dirmngr.c +++ b/sm/call-dirmngr.c @@ -474,7 +474,7 @@ isvalid_status_cb (void *opaque, const char *line) { parm->seen++; if (!*s || !unhexify_fpr (s, parm->fpr)) - parm->seen++; /* Bumb it to indicate an error. */ + parm->seen++; /* Bump it to indicate an error. */ } return 0; } diff --git a/sm/certchain.c b/sm/certchain.c index 4e18caf55..79d98c736 100644 --- a/sm/certchain.c +++ b/sm/certchain.c @@ -669,7 +669,7 @@ find_up (ctrl_t ctrl, KEYDB_HANDLE kh, log_debug (" found via authid and sn+issuer\n"); /* In case of an error, try to get the certificate from the - dirmngr. That is done by trying to put that certifcate + dirmngr. That is done by trying to put that certificate into the ephemeral DB and let the code below do the actual retrieve. Thus there is no error checking. Skipped in find_next mode as usual. */ @@ -908,7 +908,7 @@ is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) return 1; /* Yes. Without a authorityKeyIdentifier this needs - to be the Root certifcate (our trust anchor). */ + to be the Root certificate (our trust anchor). */ log_error ("error getting authorityKeyIdentifier: %s\n", gpg_strerror (err)); return 0; /* Well, it is broken anyway. Return No. */ @@ -1103,7 +1103,7 @@ check_validity_period (ksba_isotime_t current_time, } /* This is a variant of check_validity_period used with the chain - model. The dextra contraint here is that notBefore and notAfter + model. The extra constraint here is that notBefore and notAfter must exists and if the additional argument CHECK_TIME is given this time is used to check the validity period of SUBJECT_CERT. */ static gpg_error_t @@ -1171,7 +1171,7 @@ check_validity_period_cm (ksba_isotime_t current_time, || strcmp (check_time, not_after) > 0)) { /* Note that we don't need a case for the root certificate - because its own consitency has already been checked. */ + because its own consistency has already been checked. */ do_list(opt.ignore_expiration?0:1, listmode, listfp, depth == 0 ? _("signature not created during lifetime of certificate") : @@ -1553,7 +1553,7 @@ do_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime_arg, if (rc) goto leave; - break; /* Okay: a self-signed certicate is an end-point. */ + break; /* Okay: a self-signed certificate is an end-point. */ } /* End is_root. */ diff --git a/sm/certlist.c b/sm/certlist.c index c9e275e9d..12a492518 100644 --- a/sm/certlist.c +++ b/sm/certlist.c @@ -395,7 +395,7 @@ gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret, Further we ignore them if they are due to an identical certificate (which may happen if a - certificate is accidential duplicated in the + certificate is accidentally duplicated in the keybox). */ if (!keydb_get_cert (kh, &cert2)) { @@ -533,7 +533,7 @@ gpgsm_find_cert (ctrl_t ctrl, } /* If we don't have the KEYID filter we need to check for - ambiguous search results. Note, that it is somehwat + ambiguous search results. Note, that it is somewhat reasonable to assume that a specification of a KEYID won't lead to ambiguous names. */ if (!rc && !keyid) diff --git a/sm/certreqgen-ui.c b/sm/certreqgen-ui.c index 4f8a1ac9d..70e5739e8 100644 --- a/sm/certreqgen-ui.c +++ b/sm/certreqgen-ui.c @@ -86,7 +86,7 @@ store_mb_lines (membuf_t *mb, membuf_t *lines) } -/* Chech whether we have a key for the key with HEXGRIP. Returns NULL +/* Check whether we have a key for the key with HEXGRIP. Returns NULL if not or a string describing the type of the key (RSA, ELG, DSA, etc..). */ static const char * @@ -244,7 +244,27 @@ gpgsm_gencertreq_tty (ctrl_t ctrl, estream_t output_stream) { tty_printf (_("Available keys:\n")); for (count=1,sl=keypairlist; sl; sl = sl->next, count++) - tty_printf (" (%d) %s\n", count, sl->d); + { + ksba_sexp_t pkey; + gcry_sexp_t s_pkey; + char *algostr = NULL; + const char *keyref; + + keyref = strchr (sl->d, ' '); + if (keyref) + { + keyref++; + if (!gpgsm_agent_readkey (ctrl, 1, keyref, &pkey)) + { + if (!gcry_sexp_new (&s_pkey, pkey, 0, 0)) + algostr = pubkey_algo_string (s_pkey); + gcry_sexp_release (s_pkey); + } + xfree (pkey); + } + tty_printf (" (%d) %s %s\n", count, sl->d, algostr); + xfree (algostr); + } xfree (answer); answer = tty_get (_("Your selection? ")); tty_kill_prompt (); diff --git a/sm/certreqgen.c b/sm/certreqgen.c index 1d610c1bb..d5c857b08 100644 --- a/sm/certreqgen.c +++ b/sm/certreqgen.c @@ -807,8 +807,10 @@ create_request (ctrl_t ctrl, if (err) return err; - string = get_parameter_value (para, pHASHALGO, 0); - if (string) + len = gcry_sexp_canon_len (public, 0, NULL, NULL); + if (get_pk_algo_from_canon_sexp (public, len) == GCRY_PK_EDDSA) + mdalgo = GCRY_MD_SHA512; + else if ((string = get_parameter_value (para, pHASHALGO, 0))) mdalgo = gcry_md_map_name (string); else mdalgo = GCRY_MD_SHA256; @@ -1312,7 +1314,7 @@ create_request (ctrl_t ctrl, log_info ("about to sign the %s for key: &%s\n", certmode? "certificate":"CSR", hexgrip); - if (carddirect) + if (carddirect && !certmode) rc = gpgsm_scd_pksign (ctrl, carddirect, NULL, gcry_md_read (md, mdalgo), gcry_md_get_algo_dlen (mdalgo), diff --git a/sm/delete.c b/sm/delete.c index 56d5b1f8c..f359cc595 100644 --- a/sm/delete.c +++ b/sm/delete.c @@ -64,8 +64,6 @@ delete_one (ctrl_t ctrl, const char *username) /* If the key is specified in a unique way, include ephemeral keys in the search. */ if ( desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR20 - || desc.mode == KEYDB_SEARCH_MODE_FPR16 || desc.mode == KEYDB_SEARCH_MODE_KEYGRIP ) { is_ephem = 1; diff --git a/sm/export.c b/sm/export.c index 7bea9ccc5..918096e7c 100644 --- a/sm/export.c +++ b/sm/export.c @@ -198,8 +198,6 @@ gpgsm_export (ctrl_t ctrl, strlist_t names, estream_t stream) { for (i=0; (i < ndesc && (desc[i].mode == KEYDB_SEARCH_MODE_FPR - || desc[i].mode == KEYDB_SEARCH_MODE_FPR20 - || desc[i].mode == KEYDB_SEARCH_MODE_FPR16 || desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++) ; if (i == ndesc) diff --git a/sm/gpgsm.c b/sm/gpgsm.c index b0547876a..2f9e5bfd2 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -1571,7 +1571,7 @@ main ( int argc, char **argv) set_debug (); - /* Although we always use gpgsm_exit, we better install a regualr + /* Although we always use gpgsm_exit, we better install a regular exit handler so that at least the secure memory gets wiped out. */ if (atexit (emergency_cleanup)) @@ -1666,7 +1666,7 @@ main ( int argc, char **argv) || cmd == aClearsign, opt.extra_digest_algo)) log_error (_("digest algorithm '%s' may not be used in %s mode\n"), - forced_digest_algo, + extra_digest_algo, gnupg_compliance_option_string (opt.compliance)); if (log_get_errorcount(0)) diff --git a/sm/gpgsm.h b/sm/gpgsm.h index d3fbde515..7a5e4917d 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -76,7 +76,7 @@ struct const char *protect_tool_program; char *outfile; /* name of output file */ - int with_key_data;/* include raw key in the column delimted output */ + int with_key_data;/* include raw key in the column delimited output */ int fingerprint; /* list fingerprints in all key listings */ diff --git a/sm/keydb.c b/sm/keydb.c index d85679a3b..f66a5766d 100644 --- a/sm/keydb.c +++ b/sm/keydb.c @@ -106,7 +106,7 @@ try_make_homedir (const char *fname) /* Handle the creation of a keybox if it does not yet exist. Take - into acount that other processes might have the keybox already + into account that other processes might have the keybox already locked. This lock check does not work if the directory itself is not yet available. If R_CREATED is not NULL it will be set to true if the function created a new keybox. */ @@ -1052,6 +1052,7 @@ keydb_search_fpr (ctrl_t ctrl, KEYDB_HANDLE hd, const byte *fpr) memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FPR; memcpy (desc.u.fpr, fpr, 20); + desc.fprlen = 20; return keydb_search (ctrl, hd, &desc, 1); } diff --git a/sm/keylist.c b/sm/keylist.c index ea2a22093..e242310be 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -85,6 +85,8 @@ struct #define OID_FLAG_SKIP 1 /* The extension is a simple UTF8String and should be printed. */ #define OID_FLAG_UTF8 2 +/* The extension can be trnted as a hex string. */ +#define OID_FLAG_HEX 4 /* A table mapping OIDs to a descriptive string. */ static struct @@ -194,6 +196,12 @@ static struct /* Extensions used by the Bundesnetzagentur. */ { "1.3.6.1.4.1.8301.3.5", "validityModel" }, + /* Yubikey extensions for attestation certificates. */ + { "1.3.6.1.4.1.41482.3.3", "yubikey-firmware-version", OID_FLAG_HEX }, + { "1.3.6.1.4.1.41482.3.7", "yubikey-serial-number", OID_FLAG_HEX }, + { "1.3.6.1.4.1.41482.3.8", "yubikey-pin-touch-policy", OID_FLAG_HEX }, + { "1.3.6.1.4.1.41482.3.9", "yubikey-formfactor", OID_FLAG_HEX }, + { NULL } }; @@ -384,16 +392,21 @@ static void print_compliance_flags (ksba_cert_t cert, int algo, unsigned int nbits, estream_t fp) { - int any = 0; + int indent = 0; + int hashalgo; if (gnupg_pk_is_compliant (CO_DE_VS, algo, NULL, nbits, NULL)) { - es_fputs (gnupg_status_compliance_flag (CO_DE_VS), fp); - any++; + hashalgo = gcry_md_map_name (ksba_cert_get_digest_algo (cert)); + if (gnupg_digest_is_compliant (CO_DE_VS, hashalgo)) + { + es_fputs (gnupg_status_compliance_flag (CO_DE_VS), fp); + indent = 1; + } } if (opt.with_key_screening) - print_pk_screening (cert, 1+any, fp); + print_pk_screening (cert, 1+indent, fp); } @@ -718,6 +731,21 @@ print_utf8_extn (estream_t fp, int indent, } +/* Print the extension described by (DER,DERLEN) in hex. */ +static void +print_hex_extn (estream_t fp, int indent, + const unsigned char *der, size_t derlen) +{ + if (indent < 0) + indent = - indent; + + es_fprintf (fp, "%*s(", indent, ""); + for (; derlen; der++, derlen--) + es_fprintf (fp, "%02X%s", *der, derlen > 1? " ":""); + es_fprintf (fp, ")\n"); +} + + /* List one certificate in raw mode useful to have a closer look at the certificate. This one does no beautification and only minimal output sanitation. It is mainly useful for debugging. */ @@ -1055,16 +1083,27 @@ list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd, if ((flag & OID_FLAG_SKIP)) continue; - es_fprintf (fp, " %s: %s%s%s%s [%d octets]\n", + es_fprintf (fp, " %s: %s%s%s%s", i? "critExtn":" extn", - oid, s?" (":"", s?s:"", s?")":"", (int)len); + oid, s?" (":"", s?s:"", s?")":""); if ((flag & OID_FLAG_UTF8)) { if (!cert_der) cert_der = ksba_cert_get_image (cert, NULL); - assert (cert_der); + log_assert (cert_der); + es_fprintf (fp, "\n"); print_utf8_extn_raw (fp, -15, cert_der+off, len); } + else if ((flag & OID_FLAG_HEX)) + { + if (!cert_der) + cert_der = ksba_cert_get_image (cert, NULL); + log_assert (cert_der); + es_fprintf (fp, "\n"); + print_hex_extn (fp, -15, cert_der+off, len); + } + else + es_fprintf (fp, " [%d octets]\n", (int)len); } @@ -1438,8 +1477,6 @@ list_internal_keys (ctrl_t ctrl, strlist_t names, estream_t fp, for (i=0; (i < ndesc && (desc[i].mode == KEYDB_SEARCH_MODE_FPR - || desc[i].mode == KEYDB_SEARCH_MODE_FPR20 - || desc[i].mode == KEYDB_SEARCH_MODE_FPR16 || desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++) ; if (i == ndesc) diff --git a/sm/minip12.c b/sm/minip12.c index f066892a0..76ce07376 100644 --- a/sm/minip12.c +++ b/sm/minip12.c @@ -853,7 +853,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length, if (ti.class == ASNCONTEXT && ti.tag == 0 && ti.is_constructed && ti.ndef) { /* Mozilla exported certs now come with single byte chunks of - octect strings. (Mozilla Firefox 1.0.4). Arghh. */ + octet strings. (Mozilla Firefox 1.0.4). Arghh. */ where = "cram-rc2or3des-ciphertext"; cram_buffer = cram_octet_string ( p, &n, &consumed); if (!cram_buffer) @@ -1210,7 +1210,7 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, if (ti.is_constructed && ti.ndef) { /* Mozilla exported certs now come with single byte chunks of - octect strings. (Mozilla Firefox 1.0.4). Arghh. */ + octet strings. (Mozilla Firefox 1.0.4). Arghh. */ where = "cram-data.outersegs"; cram_buffer = cram_octet_string ( p, &n, &consumed); if (!cram_buffer) @@ -1550,7 +1550,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, if (ti.is_constructed && ti.ndef) { /* Mozilla exported certs now come with single byte chunks of - octect strings. (Mozilla Firefox 1.0.4). Arghh. */ + octet strings. (Mozilla Firefox 1.0.4). Arghh. */ where = "cram-bags"; cram_buffer = cram_octet_string ( p, &n, NULL); if (!cram_buffer) @@ -109,13 +109,16 @@ transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, gpg_error_t err; const unsigned char *buf, *tok; size_t buflen, toklen; - int depth, last_depth1, last_depth2; + int depth, last_depth1, last_depth2, pkalgo; int is_pubkey = 0; - const unsigned char *rsa_s = NULL; - size_t rsa_s_len = 0; + const unsigned char *rsa_s, *ecc_r, *ecc_s; + size_t rsa_s_len, ecc_r_len, ecc_s_len; const char *oid; gcry_sexp_t sexp; + rsa_s = ecc_r = ecc_s = NULL; + rsa_s_len = ecc_r_len = ecc_s_len = 0; + *r_newsigval = NULL; if (r_newsigvallen) *r_newsigvallen = 0; @@ -137,7 +140,15 @@ transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, return err; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; - if (!tok || toklen != 3 || memcmp ("rsa", tok, toklen)) + if (!tok) + return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + if (toklen == 3 && !memcmp ("rsa", tok, 3)) + pkalgo = GCRY_PK_RSA; + else if (toklen == 5 && !memcmp ("ecdsa", tok, 5)) + pkalgo = GCRY_PK_ECC; + else if (toklen == 5 && !memcmp ("eddsa", tok, 5)) + pkalgo = GCRY_PK_EDDSA; + else return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); last_depth1 = depth; @@ -150,16 +161,27 @@ transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, return err; if (tok && toklen == 1) { - const unsigned char **mpi; - size_t *mpi_len; + const unsigned char **mpi = NULL; + size_t *mpi_len = NULL; switch (*tok) { - case 's': mpi = &rsa_s; mpi_len = &rsa_s_len; break; + case 's': + if (pkalgo == GCRY_PK_RSA) + { + mpi = &rsa_s; + mpi_len = &rsa_s_len; + } + else if (pkalgo == GCRY_PK_ECC || pkalgo == GCRY_PK_EDDSA) + { + mpi = &ecc_s; + mpi_len = &ecc_s_len; + } + break; + + case 'r': mpi = &ecc_r; mpi_len = &ecc_r_len; break; default: mpi = NULL; mpi_len = NULL; break; } - if (mpi && *mpi) - return gpg_error (GPG_ERR_DUP_VALUE); if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; @@ -182,33 +204,59 @@ transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, return err; /* Map the hash algorithm to an OID. */ - switch (mdalgo) + if (mdalgo < 0 || mdalgo > (1<<15) || pkalgo < 0 || pkalgo > (1<<15)) + return gpg_error (GPG_ERR_DIGEST_ALGO); + + switch (mdalgo | (pkalgo << 16)) { - case GCRY_MD_SHA1: + case GCRY_MD_SHA1 | (GCRY_PK_RSA << 16): oid = "1.2.840.113549.1.1.5"; /* sha1WithRSAEncryption */ break; - case GCRY_MD_SHA256: + case GCRY_MD_SHA256 | (GCRY_PK_RSA << 16): oid = "1.2.840.113549.1.1.11"; /* sha256WithRSAEncryption */ break; - case GCRY_MD_SHA384: + case GCRY_MD_SHA384 | (GCRY_PK_RSA << 16): oid = "1.2.840.113549.1.1.12"; /* sha384WithRSAEncryption */ break; - case GCRY_MD_SHA512: + case GCRY_MD_SHA512 | (GCRY_PK_RSA << 16): oid = "1.2.840.113549.1.1.13"; /* sha512WithRSAEncryption */ break; + case GCRY_MD_SHA224 | (GCRY_PK_ECC << 16): + oid = "1.2.840.10045.4.3.1"; /* ecdsa-with-sha224 */ + break; + + case GCRY_MD_SHA256 | (GCRY_PK_ECC << 16): + oid = "1.2.840.10045.4.3.2"; /* ecdsa-with-sha256 */ + break; + + case GCRY_MD_SHA384 | (GCRY_PK_ECC << 16): + oid = "1.2.840.10045.4.3.3"; /* ecdsa-with-sha384 */ + break; + + case GCRY_MD_SHA512 | (GCRY_PK_ECC << 16): + oid = "1.2.840.10045.4.3.4"; /* ecdsa-with-sha512 */ + break; + + case GCRY_MD_SHA512 | (GCRY_PK_EDDSA << 16): + oid = "1.3.101.112"; /* ed25519 */ + break; + default: return gpg_error (GPG_ERR_DIGEST_ALGO); } - if (rsa_s && !is_pubkey) - err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s(s%b)))", - oid, (int)rsa_s_len, rsa_s); - else + if (is_pubkey) err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s))", oid); + else if (pkalgo == GCRY_PK_RSA) + err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s(s%b)))", oid, + (int)rsa_s_len, rsa_s); + else if (pkalgo == GCRY_PK_ECC || pkalgo == GCRY_PK_EDDSA) + err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s(r%b)(s%b)))", oid, + (int)ecc_r_len, ecc_r, (int)ecc_s_len, ecc_s); if (err) return err; err = make_canon_sexp (sexp, r_newsigval, r_newsigvallen); diff --git a/sm/qualified.c b/sm/qualified.c index 6a7b47306..70d03aed3 100644 --- a/sm/qualified.c +++ b/sm/qualified.c @@ -140,7 +140,7 @@ read_list (char *key, char *country, int *lnr) Returns: 0 if the certificate is included. GPG_ERR_NOT_FOUND if it is not in the list or any other error (e.g. if no list of qualified signatures is available. If COUNTRY has not been passed - as NULL a string witha maximum length of 2 will be copied into it; + as NULL a string with a maximum length of 2 will be copied into it; thus the caller needs to provide a buffer of length 3. */ gpg_error_t gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert, char *country) diff --git a/tests/asschk.c b/tests/asschk.c index 65828e5b2..c77fd7c23 100644 --- a/tests/asschk.c +++ b/tests/asschk.c @@ -30,7 +30,7 @@ expanded once and non existing macros expand to the empty string. A macro is dereferenced by prefixing its name with a dollar sign; the end of the name is currently indicated by a white space, a - dollar sign or a slash. To use a dollor sign verbatim, double it. + dollar sign or a slash. To use a dollar sign verbatim, double it. A macro is assigned by prefixing a statement with the macro name and an equal sign. The value is assigned verbatim if it does not @@ -47,7 +47,7 @@ [<name> =] <statement> [<args>] - If NAME is not specifed but the statement returns a value it is + If NAME is not specified but the statement returns a value it is assigned to the name "?" so that it can be referenced using "$?". The following commands are implemented: @@ -274,7 +274,7 @@ writen (int fd, const char *buffer, size_t length) type and store that in recv_type. The function terminates on a communication error. Returns a pointer into the inputline to the first byte of the arguments. The parsing is very strict to match - exaclty what we want to send. */ + exactly what we want to send. */ static char * read_assuan (int fd) { @@ -397,7 +397,7 @@ write_assuan (int fd, const char *line) /* Start the server with path PGMNAME and connect its stdout and strerr to a newly created pipes; the file descriptors are then - store in the gloabl variables SERVER_SEND_FD and + store in the global variables SERVER_SEND_FD and SERVER_RECV_FD. The initial handcheck is performed.*/ static void start_server (const char *pgmname) @@ -468,7 +468,7 @@ start_server (const char *pgmname) -/* Script intepreter. */ +/* Script interpreter. */ static void unset_var (const char *name) diff --git a/tests/gpgme/gpgme-defs.scm b/tests/gpgme/gpgme-defs.scm index 0de589ffe..bc40b3c76 100644 --- a/tests/gpgme/gpgme-defs.scm +++ b/tests/gpgme/gpgme-defs.scm @@ -67,8 +67,7 @@ (create-file "gpg-agent.conf" (string-append "pinentry-program " (tool 'pinentry)) - (string-append "scdaemon-program " (tool 'scdaemon)) - ) + "disable-scdaemon") (start-agent) diff --git a/tests/gpgscm/scheme.c b/tests/gpgscm/scheme.c index 4384841a7..a8191657d 100644 --- a/tests/gpgscm/scheme.c +++ b/tests/gpgscm/scheme.c @@ -872,7 +872,7 @@ gc_reservation_failure(struct scheme *sc) { #ifdef NDEBUG fprintf(stderr, - "insufficient reservation\n") + "insufficient reservation\n"); #else fprintf(stderr, "insufficient %s reservation in line %d\n", @@ -2990,13 +2990,23 @@ _Error_1(scheme *sc, const char *s, pointer a) { /* Define a label OP and emit a case statement for OP. For use in the * dispatch function. The slightly peculiar goto that is never * executed avoids warnings about unused labels. */ +#if __GNUC__ > 6 +#define CASE(OP) OP: __attribute__((unused)); case OP +#else #define CASE(OP) case OP: if (0) goto OP; OP +#endif #else /* USE_THREADED_CODE */ #define s_thread_to(sc, a) s_goto(sc, a) #define CASE(OP) case OP #endif /* USE_THREADED_CODE */ +#if __GNUC__ > 6 +#define FALLTHROUGH __attribute__ ((fallthrough)) +#else +#define FALLTHROUGH /* fallthrough */ +#endif + /* Return to the previous frame on the dump stack, setting the current * value to A. */ #define s_return(sc, a) s_goto(sc, _s_return(sc, a, 0)) @@ -3557,7 +3567,7 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { putstr(sc,"\nEval: "); s_thread_to(sc,OP_P0LIST); } - /* fall through */ + FALLTHROUGH; CASE(OP_REAL_EVAL): #endif if (is_symbol(sc->code)) { /* symbol */ @@ -3635,7 +3645,7 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { free_cons(sc, sc->args, &callsite, &sc->args); sc->code = car(sc->args); sc->args = cdr(sc->args); - /* Fallthrough. */ + FALLTHROUGH; CASE(OP_APPLY): /* apply 'code' to 'args' */ #if USE_TRACING @@ -3646,7 +3656,7 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { putstr(sc,"\nApply to: "); s_thread_to(sc,OP_P0LIST); } - /* fall through */ + FALLTHROUGH; CASE(OP_REAL_APPLY): #endif #if USE_HISTORY @@ -3727,12 +3737,11 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { s_thread_to(sc,OP_APPLY); } } - /* Fallthrough. */ #else CASE(OP_LAMBDA): /* lambda */ sc->value = sc->code; - /* Fallthrough. */ #endif + FALLTHROUGH; CASE(OP_LAMBDA1): gc_disable(sc, 1); @@ -4655,13 +4664,9 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { CASE(OP_NULLP): /* null? */ s_retbool(car(sc->args) == sc->NIL); CASE(OP_NUMEQ): /* = */ - /* Fallthrough. */ CASE(OP_LESS): /* < */ - /* Fallthrough. */ CASE(OP_GRE): /* > */ - /* Fallthrough. */ CASE(OP_LEQ): /* <= */ - /* Fallthrough. */ CASE(OP_GEQ): /* >= */ switch(op) { case OP_NUMEQ: comp_func=num_eq; break; @@ -4750,9 +4755,7 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { s_return(sc,sc->value); CASE(OP_WRITE): /* write */ - /* Fallthrough. */ CASE(OP_DISPLAY): /* display */ - /* Fallthrough. */ CASE(OP_WRITE_CHAR): /* write-char */ if(is_pair(cdr(sc->args))) { if(cadr(sc->args)!=sc->outport) { @@ -4900,9 +4903,7 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { s_return(sc,sc->outport); CASE(OP_OPEN_INFILE): /* open-input-file */ - /* Fallthrough. */ CASE(OP_OPEN_OUTFILE): /* open-output-file */ - /* Fallthrough. */ CASE(OP_OPEN_INOUTFILE): /* open-input-output-file */ { int prop=0; pointer p; @@ -4922,7 +4923,6 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { #if USE_STRING_PORTS CASE(OP_OPEN_INSTRING): /* open-input-string */ - /* Fallthrough. */ CASE(OP_OPEN_INOUTSTRING): /* open-input-output-string */ { int prop=0; pointer p; @@ -5003,7 +5003,6 @@ Eval_Cycle(scheme *sc, enum scheme_opcodes op) { s_thread_to(sc,OP_READ_INTERNAL); CASE(OP_READ_CHAR): /* read-char */ - /* Fallthrough. */ CASE(OP_PEEK_CHAR): /* peek-char */ { int c; if(is_pair(sc->args)) { @@ -5615,7 +5614,9 @@ int scheme_init_custom_alloc(scheme *sc, func_alloc malloc, func_dealloc free) { sc->fcells = 0; sc->inhibit_gc = GC_ENABLED; sc->reserved_cells = 0; +#ifndef NDEBUG sc->reserved_lineno = 0; +#endif sc->no_memory=0; sc->inport=sc->NIL; sc->outport=sc->NIL; diff --git a/tests/gpgsm/gpgsm-defs.scm b/tests/gpgsm/gpgsm-defs.scm index f11864201..848bc7581 100644 --- a/tests/gpgsm/gpgsm-defs.scm +++ b/tests/gpgsm/gpgsm-defs.scm @@ -67,10 +67,7 @@ "faked-system-time 1008241200") (create-file "gpg-agent.conf" (string-append "pinentry-program " (tool 'pinentry)) - (if (assoc "scdaemon" gpg-components) - (string-append "scdaemon-program " (tool 'scdaemon)) - "# No scdaemon available") - ) + "disable-scdaemon") (start-agent) (create-file "trustlist.txt" diff --git a/tests/inittests b/tests/inittests index 6fbccfb0f..9090674e3 100755 --- a/tests/inittests +++ b/tests/inittests @@ -85,6 +85,7 @@ EOF cat > gpg-agent.conf <<EOF no-grab pinentry-program /home/wk/work/pinentry/gtk/pinentry-gtk +disable-scdaemon EOF cat > trustlist.txt <<EOF diff --git a/tests/openpgp/defs.scm b/tests/openpgp/defs.scm index 186efe0ca..85a013366 100644 --- a/tests/openpgp/defs.scm +++ b/tests/openpgp/defs.scm @@ -356,10 +356,7 @@ (if (flag "--extended-key-format" *args*) "enable-extended-key-format" "#enable-extended-key-format") (string-append "pinentry-program " (tool 'pinentry)) - (if (assoc "scdaemon" gpg-components) - (string-append "scdaemon-program " (tool 'scdaemon)) - "# No scdaemon available") - )) + "disable-scdaemon")) ;; Initialize the test environment, install appropriate configuration ;; and start the agent, without any keys. diff --git a/tests/openpgp/ecc.scm b/tests/openpgp/ecc.scm index d7c02a5e2..a63ec45bd 100755 --- a/tests/openpgp/ecc.scm +++ b/tests/openpgp/ecc.scm @@ -175,7 +175,7 @@ Rg== (display "This is one line\n" (fdopen fd "wb"))) (for-each-p - "Checking ECDSA decryption" + "Checking ECDH decryption" (lambda (test) (lettmp (x y) (call-with-output-file diff --git a/tests/openpgp/samplekeys/README b/tests/openpgp/samplekeys/README index 6f2399fd9..f8a7e9ed7 100644 --- a/tests/openpgp/samplekeys/README +++ b/tests/openpgp/samplekeys/README @@ -14,8 +14,20 @@ whats-new-in-2.1.asc Collection of sample keys. e2e-p256-1-clr.asc Google End-end-End test key (no protection) e2e-p256-1-prt.asc Ditto, but protected with passphrase "a". E657FB607BB4F21C90BB6651BC067AF28BC90111.asc Key with subkeys (no protection) +pgp-desktop-skr.asc Secret key with subkeys w/o signatures rsa-rsa-sample-1.asc RSA+RSA sample key (no passphrase) ed25519-cv25519-sample-1.asc Ed25519+CV25519 sample key (no passphrase) silent-running.asc Collection of sample secret keys (no passphrases) rsa-primary-auth-only.pub.asc rsa2408 primary only, usage: cert,auth rsa-primary-auth-only.sec.asc Ditto but the secret keyblock. + + +Notes: + +- pgp-desktop-skr.asc is a secret keyblock without the uid and subkey + binding signatures. When exporting a secret key from PGP desktop + such a file is created which is then directly followed by a separate + armored public key block. To create such a sample concatenate + pgp-desktop-skr.asc and E657FB607BB4F21C90BB6651BC067AF28BC90111.asc +- ecc-sample-2-sec.asc and ecc-sample-3-sec.asc do not have and + binding signatures either. ecc-sample-1-sec.asc has them, though. diff --git a/tests/openpgp/samplekeys/pgp-desktop-skr.asc b/tests/openpgp/samplekeys/pgp-desktop-skr.asc new file mode 100644 index 000000000..58f384caf --- /dev/null +++ b/tests/openpgp/samplekeys/pgp-desktop-skr.asc @@ -0,0 +1,56 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: Made up as if from PGP Encryption Desktop 10.3.2 + +lQHYBFZfWcgBBAC+RQIbTFhpMiRmJPB3XAJQXxpDb5h2sEaNJ/MLIHwPNs+jNgDb +144BrIOD1G56xAYhKYVDphFIg2wCiB019mYq7yNUyn/aZFBHqd5xbg4qR212cAjw +HpBqP9yUEm333RFqFdytcbXd9rSfvZOlFvGZRSxjgpGlsJGbjitH0ABY+wARAQAB +AAP+IvI/yc3C60dXYh9kvzd6AVMGWt5zTVFhE+oDfMaxooW5q0tu6vHzViFeYmcx +B4FbctnSbTNiN0RUIT7oxpGEAAumKRejGAaMwiKZz3bMV05l0LI0Yn10GzXsLtRx ++iKzpUxThZETRU43BJeMqP5/rVqdQAu47pClgTwQWn6bXNkCANe2+XwJgMv9D72p +kMLIi0TmPtfjBFV6f3f190N6m5gCCwstzvKqcNQ7NqNdbLHo/HKCmdGzlzCajbNu +1nLJYoUCAOHNiNhWU/IEC5fRNyxfE5AQAmc7Bm/7d5gVIWDUjWe1ukfwJGQESyNy +GTraOcYQa8X0GskSEktjwZN/dM9yZX8B/02prLprc1+8GjTM7q7ePJJbiOWcvYrB +qcHhqadU/uC/g4lzDAG4RVutIHaqqOPr85J9jPzP/AT6ygsNU5Q5902gYbQjQmFy +cmV0dCBCcm93biA8YmFycmV0dEBleGFtcGxlLm9yZz6dAdgEVl9ZyAEEAMfR5EvR +HsEQXjKwf+LvMD2qXZerKRJYv+Ok6O1nJgYZrxGSXRtGUGrWDb4JERKjmnbIHePa +J42GgpAUibaya0lDkvjKOehX/+dno06Bcn7mbOistFBpvbbyhCcN2mYhjQGeT8r6 +fiX/sSw8L49MRxwI/JRBITkqyKxr6uMsf/p1ABEBAAEAA/wN9hFQZs0SSjV6rzBQ +R8wEEvo1FaVp/b9yhVws8i8K8BJ2VNaiiDgoLsqJA5MozTuGnxbPi7eFwOcwb+7r +T+4E8c8cJlOFiWkYtUyFDAjjo1m0xxFI0GnWuEnl238URxIW+x4k6Bx7g8P/3psH +f5x1ue8pxYzudxEuPTBV8HMp4QIA2p74/ZJafVJAIDcEcbMDoIhTpRgbMyeHaQmR +81gwo2FHd3hlonspwJ37r3LRk2jMgecU+0cK7p7W4HkYD6Xo2QIA6fv/DFn2WVRA +ODQVQQGGxsvO2cM847IFJu96BbbxOLaZJ536RE980c2a9q/9B4hOYzKV4B4NI03u +5/BqoOY8/QH/ZIvWN1fksXhQMypVTLg8R81igqS3GXKmQ+KrVEfTIHnXKxH7tyfD +eJSS6nfpfARhAe2mP3TIrbjX+9PR+Qmkg6GqnQHYBFZfWusBBACoJjGH5zSYYpWQ +1EuJJ7X2tJs6AtUlwvp0fUSdrA7qSXLKkhusOibsM01OWntMyXBD5SwpuZPyPCRT +Tz9rCDpb1arksIAFRK1itVzAkmV/eniUGu7QFJGVoq4iyWmTk+jB+PaU9dfqjV5E +eyfGT0VMP4wZxaSF8v0cX5Gry89yJwARAQABAAP+NPUmd199hJrT8TOzgIRlvkfe +dZRLziNM3yBO2nvEjMxKH3uJxKHh/VUg/VLo72On/HIyiQeeDVYcuLJGTm7edegk +/9C85hT5K4VUF9+LXXDX1Vz/jQdZxq+JwUE/AdlAEC9fkFQzc0ftI832mgjROASw +MVphqYUQERz00ve+NDUCAMmgeUzmQB+ZDcdCzKQfZChafEDqZNpqIKfhcg8SytcK +LA5uLBYGPcj7DY5NZuh3PFaV3EGxpjJUIzdspHp6V0UCANV+jbkookz+pUHAKp6D +wt+yxOj1HqKIRdOYVaEaLTpvv7CHL3u2a4FQbxCxK6umVPH1HglEKDHNs7UBB8gv +tHsB/Rzk2o5+LyWT18v6ubDVoUO6WQx3iXJakorJrSML7gld8DAEDCFK/jlk5Rhz +gmGvBZwZ+z2xOk3rxnQxBPAkHHyb8p0B2ARWX1uVAQQA5Hj2C3jzD8OGtLaw7+P2 +gYdAfR4s4YS3/AK+kYYtbm5EX4srysyUbylbQDQXUvRzw4FPkjXbboF6KjHw8icN +WHCazwSfPTfCDvi0JIildkfNqwBzCmNDRn++X8rvAeDCEJ/BtfcgfgmDTElSJOd+ +3B4XwnmtnBW54KlR42PLobsAEQEAAQAD+gJL6SGioplwMH9xtZtZ5fixAynaOeYK +LK8vF06EGpL3Xl8lHqwpKZU0tbmsfLJjkFL6yD2L750Ge0vcLj5YtxVh+pfzvtgo +HbYvfcU0j3iUQXgrn9r792wILv9LcgfDGYEUTPY+TSQnhju6OA8EYFJC1l9vkeae +HWiNi0VH5leBAgDr3h6mXHrLT4qSNexzz8BAvK/PlHMAMAJcy76lXSkl92+c4Bcp +jFDr5Vpaq/VXoLC4L/IlnEEqY967pAycdLH7AgD3+UB/qEnh0dDq7HkEfwMiarY6 +Nb6wre0jPN/p+lWQ+MO6o5iJ2b44vZIUIlrKZJZ1WraBBhuRx1Fd0YpUlYNBAf41 +4cEsS5z9Vf6HeJ87WPIyWH72dmwcuRDNTKLYeetcnbbhyO+BzfXbC+0FAxTIsBFa +4S4xUwDBah+Nf4ZlcvPSop+dAdgEVl9unAEEAOnl30hwc47rLL9QH6g0TX1BEPdW +MV4Ou6+rQOErIMAr1AOlUzpjwJllvQqf2OHnQWaTr9kbNLn7XUEUhjkH3uHDYMHM +dyAb7YJrk3ECDqnmr34VV/F/H5BH7D6AiFktl1SpUTczPxBxvPNlJ4joPmTm+ahf +g+zL+4pVu6tIhM0LABEBAAEAA/9fHMTxVhkHswZdPZ3B7pLcLktR6NDmaKNVyhP1 +/G2y95+dY+s2QT4eosp+uYWeR0XHCqNla7TDND41qrzyEAtHiAF3OoydMK4lb0lq +fKORRI4tr017wgMxRBLs82Gk5ehtI7AwSca7WvaoAJwKZp42th4MOeykeGRRMagJ +I420QQIA8zdj89HUQ9tIRyhenoqgGWGmYZgO6SlrloxwlVzvbOsxn59A7PpE0CZb +TsVPwFCwEzf3316k7V0oqa8TVL8J2wIA9jEY4AFhxY6kmffl5KiKwHThC06BPk6k +CX90tt5on5iH0q2tjrAt/+ZfTcWAT5huQh9OZ4Hq0N/hFhtcJjIokQH+OcGoGiG3 +pNBeU0bZqnVZNcHhJP9F13chv5jSAOJf6rfyx3HbgTeOqh2BCpyocgzAgQ8JUkX+ +OeRRvDotcfiTGKBc +=VlTT +-----END PGP PRIVATE KEY BLOCK----- diff --git a/tests/pkits/inittests b/tests/pkits/inittests index 4bff0a8b8..deb185439 100755 --- a/tests/pkits/inittests +++ b/tests/pkits/inittests @@ -94,6 +94,7 @@ EOF # Fixme: we need to write a dummy pinentry program cat > gpg-agent.conf <<EOF no-grab +disable-scdaemon EOF # Mark the root CA trusted diff --git a/tools/Makefile.am b/tools/Makefile.am index 0c828a7bd..fb37c05e7 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -20,25 +20,23 @@ EXTRA_DIST = \ Manifest watchgnupg.c no-libgcrypt.c \ addgnupghome applygnupgdefaults \ lspgpot mail-signed-keys convert-from-106 sockprox.c \ - ccidmon.c ChangeLog-2011 gpg-connect-agent-w32info.rc - + ccidmon.c ChangeLog-2011 \ + gpg-connect-agent-w32info.rc \ + gpg-card-w32info.rc AM_CPPFLAGS = include $(top_srcdir)/am/cmacros.am if HAVE_W32_SYSTEM -resource_objs += gpg-connect-agent-w32info.o +gpg_connect_agent_rc_objs = gpg-connect-agent-w32info.o +gpg_card_tool_rc_objs = gpg-card-w32info.o +resource_objs += $(gpg_connect_agent_rc_objs) $(gpg_card_tool_rc_objs) endif AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(LIBASSUAN_CFLAGS) sbin_SCRIPTS = addgnupghome applygnupgdefaults -if HAVE_USTAR -# bin_SCRIPTS += gpg-zip -noinst_SCRIPTS = gpg-zip -endif - if BUILD_SYMCRYPTRUN symcryptrun = symcryptrun else @@ -51,9 +49,9 @@ else gpg_wks_server = endif -libexec_PROGRAMS = gpg-wks-client +libexec_PROGRAMS = gpg-wks-client gpg-pair-tool -bin_PROGRAMS = gpgconf gpg-connect-agent ${symcryptrun} +bin_PROGRAMS = gpgconf gpg-connect-agent gpg-card ${symcryptrun} if !HAVE_W32_SYSTEM bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server} endif @@ -123,7 +121,23 @@ gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ $(GPG_ERROR_LIBS) \ $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ - $(resource_objs) + $(gpg_connect_agent_rc_objs) + + +gpg_card_SOURCES = \ + gpg-card.c \ + gpg-card.h \ + card-call-scd.c \ + card-keys.c \ + card-yubikey.c \ + card-misc.c + +gpg_card_LDADD = \ + ../common/libgpgrl.a $(common_libs) \ + $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ + $(GPG_ERROR_LIBS) \ + $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ + $(gpg_card_tool_rc_objs) if !DISABLE_REGEX @@ -173,6 +187,14 @@ gpg_wks_client_LDADD = $(libcommon) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) +gpg_pair_tool_SOURCES = \ + gpg-pair-tool.c + +gpg_pair_tool_CFLAGS = $(GPG_ERROR_CFLAGS) $(INCICONV) +gpg_pair_tool_LDADD = $(libcommon) \ + $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(LIBICONV) $(W32SOCKLIBS) + # Make sure that all libs are build before we use them. This is # important for things like make -j2. diff --git a/tools/addgnupghome b/tools/addgnupghome index e13c3cd01..718b2226c 100755 --- a/tools/addgnupghome +++ b/tools/addgnupghome @@ -107,7 +107,7 @@ if [ ! -d /etc/skel/.gnupg ]; then exit 1 fi cd "/etc/skel/.gnupg" || (error "error cd-ing to \`/etc/skel/.gnupg'"; exit 1) -filelist=$(find . \( -type f -or -type d \) -not -name '*~' -not -name . -print) +filelist=$(find . \( -type f -o -type d \) '!' -name '*~' '!' -name . -print) if ! umask 0077 ; then diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c new file mode 100644 index 000000000..0a01bf5ca --- /dev/null +++ b/tools/card-call-scd.c @@ -0,0 +1,1550 @@ +/* card-call-scd.c - IPC calls to scdaemon. + * Copyright (C) 2019 g10 Code GmbH + * Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc. + * Copyright (C) 2013-2015 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif + +#include "../common/util.h" +#include "../common/membuf.h" +#include "../common/i18n.h" +#include "../common/asshelp.h" +#include "../common/sysutils.h" +#include "../common/status.h" +#include "../common/host2net.h" +#include "../common/openpgpdefs.h" +#include "gpg-card.h" + +#define CONTROL_D ('D' - 'A' + 1) + +#define START_AGENT_NO_STARTUP_CMDS 1 +#define START_AGENT_SUPPRESS_ERRORS 2 + +struct default_inq_parm_s +{ + assuan_context_t ctx; + struct { + u32 *keyid; + u32 *mainkeyid; + int pubkey_algo; + } keyinfo; +}; + +struct cipher_parm_s +{ + struct default_inq_parm_s *dflt; + assuan_context_t ctx; + unsigned char *ciphertext; + size_t ciphertextlen; +}; + +struct writecert_parm_s +{ + struct default_inq_parm_s *dflt; + const unsigned char *certdata; + size_t certdatalen; +}; + +struct writekey_parm_s +{ + struct default_inq_parm_s *dflt; + const unsigned char *keydata; + size_t keydatalen; +}; + +struct genkey_parm_s +{ + struct default_inq_parm_s *dflt; + const char *keyparms; + const char *passphrase; +}; + +struct card_cardlist_parm_s +{ + gpg_error_t error; + strlist_t list; +}; + +struct import_key_parm_s +{ + struct default_inq_parm_s *dflt; + const void *key; + size_t keylen; +}; + + +struct cache_nonce_parm_s +{ + char **cache_nonce_addr; + char **passwd_nonce_addr; +}; + + + +/* + * File local variables + */ + +/* The established context to the agent. Note that all calls to + * scdaemon are routed via the agent and thus we only need to care + * about the IPC with the agent. */ +static assuan_context_t agent_ctx; + + + +/* + * Local prototypes + */ +static gpg_error_t learn_status_cb (void *opaque, const char *line); + + + + +/* Release the card info structure INFO. */ +void +release_card_info (card_info_t info) +{ + int i; + + + if (!info) + return; + + xfree (info->reader); info->reader = NULL; + xfree (info->cardtype); info->cardtype = NULL; + xfree (info->serialno); info->serialno = NULL; + xfree (info->dispserialno); info->dispserialno = NULL; + xfree (info->apptypestr); info->apptypestr = NULL; + info->apptype = APP_TYPE_NONE; + xfree (info->disp_name); info->disp_name = NULL; + xfree (info->disp_lang); info->disp_lang = NULL; + xfree (info->pubkey_url); info->pubkey_url = NULL; + xfree (info->login_data); info->login_data = NULL; + info->cafpr1len = info->cafpr2len = info->cafpr3len = 0; + for (i=0; i < DIM(info->private_do); i++) + { + xfree (info->private_do[i]); + info->private_do[i] = NULL; + } + while (info->kinfo) + { + key_info_t kinfo = info->kinfo->next; + xfree (info->kinfo); + info->kinfo = kinfo; + } + info->chvusage[0] = info->chvusage[1] = 0; +} + + +/* Map an application type string to an integer. */ +static app_type_t +map_apptypestr (const char *string) +{ + app_type_t result; + + if (!string) + result = APP_TYPE_NONE; + else if (!ascii_strcasecmp (string, "OPENPGP")) + result = APP_TYPE_OPENPGP; + else if (!ascii_strcasecmp (string, "NKS")) + result = APP_TYPE_NKS; + else if (!ascii_strcasecmp (string, "DINSIG")) + result = APP_TYPE_DINSIG; + else if (!ascii_strcasecmp (string, "P15")) + result = APP_TYPE_P15; + else if (!ascii_strcasecmp (string, "GELDKARTE")) + result = APP_TYPE_GELDKARTE; + else if (!ascii_strcasecmp (string, "SC-HSM")) + result = APP_TYPE_SC_HSM; + else if (!ascii_strcasecmp (string, "PIV")) + result = APP_TYPE_PIV; + else + result = APP_TYPE_UNKNOWN; + + return result; +} + + +/* Return a string representation of the application type. */ +const char * +app_type_string (app_type_t app_type) +{ + const char *result = "?"; + switch (app_type) + { + case APP_TYPE_NONE: result = "None"; break; + case APP_TYPE_OPENPGP: result = "OpenPGP"; break; + case APP_TYPE_NKS: result = "NetKey"; break; + case APP_TYPE_DINSIG: result = "DINSIG"; break; + case APP_TYPE_P15: result = "P15"; break; + case APP_TYPE_GELDKARTE: result = "Geldkarte"; break; + case APP_TYPE_SC_HSM: result = "SC-HSM"; break; + case APP_TYPE_PIV: result = "PIV"; break; + case APP_TYPE_UNKNOWN: result = "Unknown"; break; + } + return result; +} + + + +/* If RC is not 0, write an appropriate status message. */ +static gpg_error_t +status_sc_op_failure (gpg_error_t err) +{ + switch (gpg_err_code (err)) + { + case 0: + break; + case GPG_ERR_CANCELED: + case GPG_ERR_FULLY_CANCELED: + gnupg_status_printf (STATUS_SC_OP_FAILURE, "1"); + break; + case GPG_ERR_BAD_PIN: + gnupg_status_printf (STATUS_SC_OP_FAILURE, "2"); + break; + default: + gnupg_status_printf (STATUS_SC_OP_FAILURE, NULL); + break; + } + return err; +} + + +/* This is the default inquiry callback. It mainly handles the + Pinentry notifications. */ +static gpg_error_t +default_inq_cb (void *opaque, const char *line) +{ + gpg_error_t err = 0; + struct default_inq_parm_s *parm = opaque; + + (void)parm; + + if (has_leading_keyword (line, "PINENTRY_LAUNCHED")) + { + /* err = gpg_proxy_pinentry_notify (parm->ctrl, line); */ + /* if (err) */ + /* log_error (_("failed to proxy %s inquiry to client\n"), */ + /* "PINENTRY_LAUNCHED"); */ + /* We do not pass errors to avoid breaking other code. */ + } + else + log_debug ("ignoring gpg-agent inquiry '%s'\n", line); + + return err; +} + + +/* Print a warning if the server's version number is less than our + version number. Returns an error code on a connection problem. */ +static gpg_error_t +warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode) +{ + gpg_error_t err; + char *serverversion; + const char *myversion = strusage (13); + + err = get_assuan_server_version (ctx, mode, &serverversion); + if (err) + log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED? + GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR, + _("error getting version from '%s': %s\n"), + servername, gpg_strerror (err)); + else if (compare_version_strings (serverversion, myversion) < 0) + { + char *warn; + + warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), + servername, serverversion, myversion); + if (!warn) + err = gpg_error_from_syserror (); + else + { + log_info (_("WARNING: %s\n"), warn); + if (!opt.quiet) + { + log_info (_("Note: Outdated servers may lack important" + " security fixes.\n")); + log_info (_("Note: Use the command \"%s\" to restart them.\n"), + "gpgconf --kill all"); + } + gnupg_status_printf (STATUS_WARNING, "server_version_mismatch 0 %s", + warn); + xfree (warn); + } + } + xfree (serverversion); + return err; +} + + +/* Try to connect to the agent via socket or fork it off and work by + * pipes. Handle the server's initial greeting. */ +static gpg_error_t +start_agent (unsigned int flags) +{ + gpg_error_t err; + + if (agent_ctx) + err = 0; + else + { + err = start_new_gpg_agent (&agent_ctx, + GPG_ERR_SOURCE_DEFAULT, + opt.agent_program, + opt.lc_ctype, opt.lc_messages, + opt.session_env, + opt.autostart, opt.verbose, DBG_IPC, + NULL, NULL); + if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_AGENT) + { + static int shown; + + if (!shown) + { + shown = 1; + log_info (_("no gpg-agent running in this session\n")); + } + } + else if (!err + && !(err = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0))) + { + /* Tell the agent that we support Pinentry notifications. + No error checking so that it will work also with older + agents. */ + assuan_transact (agent_ctx, "OPTION allow-pinentry-notify", + NULL, NULL, NULL, NULL, NULL, NULL); + /* Tell the agent about what version we are aware. This is + here used to indirectly enable GPG_ERR_FULLY_CANCELED. */ + assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0", + NULL, NULL, NULL, NULL, NULL, NULL); + } + } + + if (!err && !(flags & START_AGENT_NO_STARTUP_CMDS)) + { + /* Request the serial number of the card for an early test. */ + struct card_info_s info; + + memset (&info, 0, sizeof info); + + if (!(flags & START_AGENT_SUPPRESS_ERRORS)) + err = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2); + + if (!err) + err = assuan_transact (agent_ctx, "SCD SERIALNO", + NULL, NULL, NULL, NULL, + learn_status_cb, &info); + if (err && !(flags & START_AGENT_SUPPRESS_ERRORS)) + { + switch (gpg_err_code (err)) + { + case GPG_ERR_NOT_SUPPORTED: + case GPG_ERR_NO_SCDAEMON: + gnupg_status_printf (STATUS_CARDCTRL, "6"); /* No card support. */ + break; + case GPG_ERR_OBJ_TERM_STATE: + /* Card is in termination state. */ + gnupg_status_printf (STATUS_CARDCTRL, "7"); + break; + default: + gnupg_status_printf (STATUS_CARDCTRL, "4"); /* No card. */ + break; + } + } + + if (!err && info.serialno) + gnupg_status_printf (STATUS_CARDCTRL, "3 %s", info.serialno); + + release_card_info (&info); + } + + return err; +} + + +/* Return a new malloced string by unescaping the string S. Escaping + * is percent escaping and '+'/space mapping. A binary nul will + * silently be replaced by a 0xFF. Function returns NULL to indicate + * an out of memory status. */ +static char * +unescape_status_string (const unsigned char *s) +{ + return percent_plus_unescape (s, 0xff); +} + + +/* Take a 20 or 32 byte hexencoded string and put it into the provided + * FPRLEN byte long buffer FPR in binary format. Returns the actual + * used length of the FPR buffer or 0 on error. */ +static unsigned int +unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen) +{ + const char *s; + int n; + + for (s=hexstr, n=0; hexdigitp (s); s++, n++) + ; + if ((*s && *s != ' ') || !(n == 40 || n == 64)) + return 0; /* no fingerprint (invalid or wrong length). */ + for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++) + fpr[n] = xtoi_2 (s); + + return (n == 20 || n == 32)? n : 0; +} + + +/* Take the serial number from LINE and return it verbatim in a newly + * allocated string. We make sure that only hex characters are + * returned. Returns NULL on error. */ +static char * +store_serialno (const char *line) +{ + const char *s; + char *p; + + for (s=line; hexdigitp (s); s++) + ; + p = xtrymalloc (s + 1 - line); + if (p) + { + memcpy (p, line, s-line); + p[s-line] = 0; + } + return p; +} + + + +/* Send an APDU to the current card. On success the status word is + * stored at R_SW inless R_SW is NULL. With HEXAPDU being NULL only a + * RESET command is send to scd. With HEXAPDU being the string + * "undefined" the command "SERIALNO undefined" is send to scd. If + * R_DATA is not NULL the data is without the status code is stored + * there. Caller must release it. */ +gpg_error_t +scd_apdu (const char *hexapdu, unsigned int *r_sw, + unsigned char **r_data, size_t *r_datalen) +{ + gpg_error_t err; + + if (r_data) + *r_data = NULL; + if (r_datalen) + *r_datalen = 0; + + err = start_agent (START_AGENT_NO_STARTUP_CMDS); + if (err) + return err; + + if (!hexapdu) + { + err = assuan_transact (agent_ctx, "SCD RESET", + NULL, NULL, NULL, NULL, NULL, NULL); + + } + else if (!strcmp (hexapdu, "undefined")) + { + err = assuan_transact (agent_ctx, "SCD SERIALNO undefined", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else + { + char line[ASSUAN_LINELENGTH]; + membuf_t mb; + unsigned char *data; + size_t datalen; + + init_membuf (&mb, 256); + + snprintf (line, DIM(line), "SCD APDU %s", hexapdu); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &mb, NULL, NULL, NULL, NULL); + if (!err) + { + data = get_membuf (&mb, &datalen); + if (!data) + err = gpg_error_from_syserror (); + else if (datalen < 2) /* Ooops */ + err = gpg_error (GPG_ERR_CARD); + else + { + if (r_sw) + *r_sw = buf16_to_uint (data+datalen-2); + if (r_data && r_datalen) + { + *r_data = data; + *r_datalen = datalen - 2; + data = NULL; + } + } + xfree (data); + } + } + + return err; +} + + +/* This is a dummy data line callback. */ +static gpg_error_t +dummy_data_cb (void *opaque, const void *buffer, size_t length) +{ + (void)opaque; + (void)buffer; + (void)length; + return 0; +} + +/* A simple callback used to return the serialnumber of a card. */ +static gpg_error_t +get_serialno_cb (void *opaque, const char *line) +{ + char **serialno = opaque; + const char *keyword = line; + const char *s; + int keywordlen, n; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + /* FIXME: Should we use has_leading_keyword? */ + if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + if (*serialno) + return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */ + for (n=0,s=line; hexdigitp (s); s++, n++) + ; + if (!n || (n&1)|| !(spacep (s) || !*s) ) + return gpg_error (GPG_ERR_ASS_PARAMETER); + *serialno = xtrymalloc (n+1); + if (!*serialno) + return out_of_core (); + memcpy (*serialno, line, n); + (*serialno)[n] = 0; + } + + return 0; +} + + + +/* For historical reasons OpenPGP cards simply use the numbers 1 to 3 + * for the <keyref>. Other cards and future versions of + * scd/app-openpgp.c may print the full keyref; i.e. "OpenPGP.1" + * instead of "1". This is a helper to cope with that. */ +static const char * +parse_keyref_helper (const char *string) +{ + if (*string == '1' && spacep (string+1)) + return "OPENPGP.1"; + else if (*string == '2' && spacep (string+1)) + return "OPENPGP.2"; + else if (*string == '3' && spacep (string+1)) + return "OPENPGP.3"; + else + return string; +} + + +/* Create a new key info object with KEYREF. All fields but the + * keyref are zeroed out. Never returns NULL. The created object is + * appended to the list at INFO. */ +static key_info_t +create_kinfo (card_info_t info, const char *keyref) +{ + key_info_t kinfo, ki; + + kinfo = xcalloc (1, sizeof *kinfo + strlen (keyref)); + strcpy (kinfo->keyref, keyref); + + if (!info->kinfo) + info->kinfo = kinfo; + else + { + for (ki=info->kinfo; ki->next; ki = ki->next) + ; + ki->next = kinfo; + } + return kinfo; +} + + +/* The status callback to handle the LEARN and GETATTR commands. */ +static gpg_error_t +learn_status_cb (void *opaque, const char *line) +{ + struct card_info_s *parm = opaque; + const char *keyword = line; + int keywordlen; + char *line_buffer = NULL; /* In case we need a copy. */ + char *pline; + key_info_t kinfo; + const char *keyref; + int i; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + switch (keywordlen) + { + case 3: + if (!memcmp (keyword, "KDF", 3)) + { + parm->kdf_do_enabled = 1; + } + break; + + case 5: + if (!memcmp (keyword, "UIF-", 4) + && strchr("123", keyword[4])) + { + unsigned char *data; + int no = keyword[4] - '1'; + + log_assert (no >= 0 && no <= 2); + data = unescape_status_string (line); + parm->uif[no] = (data[0] != 0xff); + xfree (data); + } + break; + + case 6: + if (!memcmp (keyword, "READER", keywordlen)) + { + xfree (parm->reader); + parm->reader = unescape_status_string (line); + } + else if (!memcmp (keyword, "EXTCAP", keywordlen)) + { + char *p, *p2, *buf; + int abool; + + buf = p = unescape_status_string (line); + if (buf) + { + for (p = strtok (buf, " "); p; p = strtok (NULL, " ")) + { + p2 = strchr (p, '='); + if (p2) + { + *p2++ = 0; + abool = (*p2 == '1'); + if (!strcmp (p, "ki")) + parm->extcap.ki = abool; + else if (!strcmp (p, "aac")) + parm->extcap.aac = abool; + else if (!strcmp (p, "bt")) + parm->extcap.bt = abool; + else if (!strcmp (p, "kdf")) + parm->extcap.kdf = abool; + else if (!strcmp (p, "si")) + parm->status_indicator = strtoul (p2, NULL, 10); + } + } + xfree (buf); + } + } + else if (!memcmp (keyword, "CA-FPR", keywordlen)) + { + int no = atoi (line); + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (no == 1) + parm->cafpr1len = unhexify_fpr (line, parm->cafpr1, + sizeof parm->cafpr1); + else if (no == 2) + parm->cafpr2len = unhexify_fpr (line, parm->cafpr2, + sizeof parm->cafpr2); + else if (no == 3) + parm->cafpr3len = unhexify_fpr (line, parm->cafpr3, + sizeof parm->cafpr3); + } + break; + + case 7: + if (!memcmp (keyword, "APPTYPE", keywordlen)) + { + xfree (parm->apptypestr); + parm->apptypestr = unescape_status_string (line); + parm->apptype = map_apptypestr (parm->apptypestr); + } + else if (!memcmp (keyword, "KEY-FPR", keywordlen)) + { + /* The format of such a line is: + * KEY-FPR <keyref> <fingerprintinhex> + */ + const char *fpr; + + line_buffer = pline = xstrdup (line); + + keyref = parse_keyref_helper (pline); + while (*pline && !spacep (pline)) + pline++; + if (*pline) + *pline++ = 0; /* Terminate keyref. */ + while (spacep (pline)) /* Skip to the fingerprint. */ + pline++; + fpr = pline; + + /* Check whether we already have an item for the keyref. */ + kinfo = find_kinfo (parm, keyref); + if (!kinfo) /* No: new entry. */ + kinfo = create_kinfo (parm, keyref); + else /* Existing entry - clear the fpr. */ + memset (kinfo->fpr, 0, sizeof kinfo->fpr); + + /* Set or update or the fingerprint. */ + kinfo->fprlen = unhexify_fpr (fpr, kinfo->fpr, sizeof kinfo->fpr); + } + break; + + case 8: + if (!memcmp (keyword, "SERIALNO", keywordlen)) + { + xfree (parm->serialno); + parm->serialno = store_serialno (line); + parm->is_v2 = (strlen (parm->serialno) >= 16 + && xtoi_2 (parm->serialno+12) >= 2 ); + } + else if (!memcmp (keyword, "CARDTYPE", keywordlen)) + { + xfree (parm->cardtype); + parm->cardtype = unescape_status_string (line); + } + else if (!memcmp (keyword, "DISP-SEX", keywordlen)) + { + parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; + } + else if (!memcmp (keyword, "KEY-TIME", keywordlen)) + { + /* The format of such a line is: + * KEY-TIME <keyref> <timestamp> + */ + const char *timestamp; + + line_buffer = pline = xstrdup (line); + + keyref = parse_keyref_helper (pline); + while (*pline && !spacep (pline)) + pline++; + if (*pline) + *pline++ = 0; /* Terminate keyref. */ + while (spacep (pline)) /* Skip to the timestamp. */ + pline++; + timestamp = pline; + + /* Check whether we already have an item for the keyref. */ + kinfo = find_kinfo (parm, keyref); + if (!kinfo) /* No: new entry. */ + kinfo = create_kinfo (parm, keyref); + + kinfo->created = strtoul (timestamp, NULL, 10); + } + else if (!memcmp (keyword, "KEY-ATTR", keywordlen)) + { + int keyno = 0; + int algo = GCRY_PK_RSA; + int n = 0; + + sscanf (line, "%d %d %n", &keyno, &algo, &n); + keyno--; + if (keyno < 0 || keyno >= DIM (parm->key_attr)) + ; /* Out of range - ignore. */ + else + { + parm->key_attr[keyno].algo = algo; + if (algo == PUBKEY_ALGO_RSA) + parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10); + else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA) + { + parm->key_attr[keyno].curve = + openpgp_is_curve_supported (line + n, NULL, NULL); + } + } + } + break; + + case 9: + if (!memcmp (keyword, "DISP-NAME", keywordlen)) + { + xfree (parm->disp_name); + parm->disp_name = unescape_status_string (line); + } + else if (!memcmp (keyword, "DISP-LANG", keywordlen)) + { + xfree (parm->disp_lang); + parm->disp_lang = unescape_status_string (line); + } + else if (!memcmp (keyword, "CHV-USAGE", keywordlen)) + { + unsigned int byte1, byte2; + + byte1 = byte2 = 0; + sscanf (line, "%x %x", &byte1, &byte2); + parm->chvusage[0] = byte1; + parm->chvusage[1] = byte2; + } + break; + + case 10: + if (!memcmp (keyword, "PUBKEY-URL", keywordlen)) + { + xfree (parm->pubkey_url); + parm->pubkey_url = unescape_status_string (line); + } + else if (!memcmp (keyword, "LOGIN-DATA", keywordlen)) + { + xfree (parm->login_data); + parm->login_data = unescape_status_string (line); + } + else if (!memcmp (keyword, "CHV-STATUS", keywordlen)) + { + char *p, *buf; + + buf = p = unescape_status_string (line); + if (buf) + while (spacep (p)) + p++; + + if (!buf) + ; + else if (parm->apptype == APP_TYPE_OPENPGP) + { + parm->chv1_cached = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + for (i=0; *p && i < 3; i++) + { + parm->chvmaxlen[i] = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + } + for (i=0; *p && i < 3; i++) + { + parm->chvinfo[i] = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + } + } + else if (parm->apptype == APP_TYPE_PIV) + { + for (i=0; *p && i < DIM (parm->chvinfo); i++) + { + parm->chvinfo[i] = atoi (p); + while (*p && !spacep (p)) + p++; + while (spacep (p)) + p++; + } + } + + xfree (buf); + } + else if (!memcmp (keyword, "APPVERSION", keywordlen)) + { + unsigned int val = 0; + + sscanf (line, "%x", &val); + parm->appversion = val; + } + break; + + case 11: + if (!memcmp (keyword, "SIG-COUNTER", keywordlen)) + { + parm->sig_counter = strtoul (line, NULL, 0); + } + else if (!memcmp (keyword, "KEYPAIRINFO", keywordlen)) + { + /* The format of such a line is: + * KEYPAIRINFO <hexgrip> <keyref> [usage] + */ + char *hexgrp, *usage; + + line_buffer = pline = xstrdup (line); + + hexgrp = pline; + while (*pline && !spacep (pline)) + pline++; + while (spacep (pline)) + pline++; + + keyref = pline; + while (*pline && !spacep (pline)) + pline++; + if (*pline) + { + *pline++ = 0; + while (spacep (pline)) + pline++; + usage = pline; + while (*pline && !spacep (pline)) + pline++; + *pline = 0; + } + else + usage = ""; + + /* Check whether we already have an item for the keyref. */ + kinfo = find_kinfo (parm, keyref); + if (!kinfo) /* New entry. */ + kinfo = create_kinfo (parm, keyref); + else /* Existing entry - clear grip and usage */ + { + memset (kinfo->grip, 0, sizeof kinfo->grip); + kinfo->usage = 0; + } + + /* Set or update the grip. Note that due to the + * calloc/memset an erroneous too short grip will be nul + * padded on the right. */ + unhexify_fpr (hexgrp, kinfo->grip, sizeof kinfo->grip); + /* Parse and set the usage. */ + for (; *usage; usage++) + { + switch (*usage) + { + case 's': kinfo->usage |= GCRY_PK_USAGE_SIGN; break; + case 'c': kinfo->usage |= GCRY_PK_USAGE_CERT; break; + case 'a': kinfo->usage |= GCRY_PK_USAGE_AUTH; break; + case 'e': kinfo->usage |= GCRY_PK_USAGE_ENCR; break; + } + } + } + else if (!memcmp (keyword, "CARDVERSION", keywordlen)) + { + unsigned int val = 0; + + sscanf (line, "%x", &val); + parm->cardversion = val; + } + break; + + case 12: + if (!memcmp (keyword, "PRIVATE-DO-", 11) + && strchr("1234", keyword[11])) + { + int no = keyword[11] - '1'; + log_assert (no >= 0 && no <= 3); + xfree (parm->private_do[no]); + parm->private_do[no] = unescape_status_string (line); + } + break; + + case 13: + if (!memcmp (keyword, "$DISPSERIALNO", keywordlen)) + { + xfree (parm->dispserialno); + parm->dispserialno = unescape_status_string (line); + } + break; + + default: + /* Unknown. */ + break; + } + + xfree (line_buffer); + return 0; +} + + +/* Call the scdaemon to learn about a smartcard. This fills INFO + * wioth data from the card. */ +gpg_error_t +scd_learn (card_info_t info) +{ + gpg_error_t err; + struct default_inq_parm_s parm; + struct card_info_s dummyinfo; + + if (!info) + info = &dummyinfo; + + memset (info, 0, sizeof *info); + memset (&parm, 0, sizeof parm); + + err = start_agent (0); + if (err) + return err; + + parm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, "SCD LEARN --force", + dummy_data_cb, NULL, default_inq_cb, &parm, + learn_status_cb, info); + /* Also try to get some other key attributes. */ + if (!err) + { + info->initialized = 1; + + err = scd_getattr ("KEY-ATTR", info); + if (gpg_err_code (err) == GPG_ERR_INV_NAME + || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) + err = 0; /* Not implemented or GETATTR not supported. */ + err = scd_getattr ("$DISPSERIALNO", info); + if (gpg_err_code (err) == GPG_ERR_INV_NAME + || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) + err = 0; /* Not implemented or GETATTR not supported. */ + } + + if (info == &dummyinfo) + release_card_info (info); + + return err; +} + + +/* Call the agent to retrieve a data object. This function returns + * the data in the same structure as used by the learn command. It is + * allowed to update such a structure using this command. */ +gpg_error_t +scd_getattr (const char *name, struct card_info_s *info) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s parm; + + memset (&parm, 0, sizeof parm); + + if (!*name) + return gpg_error (GPG_ERR_INV_VALUE); + + /* We assume that NAME does not need escaping. */ + if (12 + strlen (name) > DIM(line)-1) + return gpg_error (GPG_ERR_TOO_LARGE); + stpcpy (stpcpy (line, "SCD GETATTR "), name); + + err = start_agent (0); + if (err) + return err; + + parm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, + learn_status_cb, info); + + return err; +} + + +/* Send an setattr command to the SCdaemon. */ +gpg_error_t +scd_setattr (const char *name, + const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + char *tmp; + char *line = NULL; + struct default_inq_parm_s parm; + + + if (!*name || !valuelen) + return gpg_error (GPG_ERR_INV_VALUE); + + tmp = strconcat ("SCD SETATTR ", name, " ", NULL); + if (!tmp) + { + err = gpg_error_from_syserror (); + goto leave; + } + line = percent_data_escape (1, tmp, value, valuelen); + xfree (tmp); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (strlen (line) + 10 > ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + err = start_agent (0); + if (err ) + goto leave; + + memset (&parm, 0, sizeof parm); + parm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, &parm, NULL, NULL); + + leave: + xfree (line); + return status_sc_op_failure (err); +} + + + +/* Handle a CERTDATA inquiry. Note, we only send the data, + * assuan_transact takes care of flushing and writing the END + * command. */ +static gpg_error_t +inq_writecert_parms (void *opaque, const char *line) +{ + gpg_error_t err; + struct writecert_parm_s *parm = opaque; + + if (has_leading_keyword (line, "CERTDATA")) + { + err = assuan_send_data (parm->dflt->ctx, + parm->certdata, parm->certdatalen); + } + else + err = default_inq_cb (parm->dflt, line); + + return err; +} + + +/* Send a WRITECERT command to the SCdaemon. */ +gpg_error_t +scd_writecert (const char *certidstr, + const unsigned char *certdata, size_t certdatalen) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct writecert_parm_s parms; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + err = start_agent (0); + if (err) + return err; + + memset (&parms, 0, sizeof parms); + + snprintf (line, sizeof line, "SCD WRITECERT %s", certidstr); + dfltparm.ctx = agent_ctx; + parms.dflt = &dfltparm; + parms.certdata = certdata; + parms.certdatalen = certdatalen; + + err = assuan_transact (agent_ctx, line, NULL, NULL, + inq_writecert_parms, &parms, NULL, NULL); + + return status_sc_op_failure (err); +} + + + +/* Send a WRITEKEY command to the agent (so that the agent can fetch + * the key to write). KEYGRIP is the hexified keygrip of the source + * key which will be written to tye slot KEYREF. FORCE must be true + * to overwrite an existing key. */ +gpg_error_t +scd_writekey (const char *keyref, int force, const char *keygrip) +{ + gpg_error_t err; + struct default_inq_parm_s parm; + char line[ASSUAN_LINELENGTH]; + + memset (&parm, 0, sizeof parm); + + err = start_agent (0); + if (err) + return err; + + /* Note: We don't send the s/n but "-" because gpg-agent has + * currently no use for it. */ + /* FIXME: For OpenPGP we should provide the creation time. */ + snprintf (line, sizeof line, "KEYTOCARD%s %s - %s", + force? " --force":"", keygrip, keyref); + err = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, &parm, NULL, NULL); + + return status_sc_op_failure (err); +} + + + +/* Status callback for the SCD GENKEY command. */ +static gpg_error_t +scd_genkey_cb (void *opaque, const char *line) +{ + u32 *createtime = opaque; + const char *keyword = line; + int keywordlen; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen)) + { + if (createtime) + *createtime = (u32)strtoul (line, NULL, 10); + } + else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen)) + { + gnupg_status_printf (STATUS_PROGRESS, "%s", line); + } + + return 0; +} + +/* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0, + * the value will be passed to SCDAEMON with --timestamp option so that + * the key is created with this. Otherwise, timestamp was generated by + * SCDEAMON. On success, creation time is stored back to + * CREATETIME. */ +gpg_error_t +scd_genkey (const char *keyref, int force, const char *algo, u32 *createtime) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + gnupg_isotime_t tbuf; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + err = start_agent (0); + if (err) + return err; + + if (createtime && *createtime) + epoch2isotime (tbuf, *createtime); + else + *tbuf = 0; + + snprintf (line, sizeof line, "SCD GENKEY %s%s %s %s%s -- %s", + *tbuf? "--timestamp=":"", tbuf, + force? "--force":"", + algo? "--algo=":"", + algo? algo:"", + keyref); + + dfltparm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, line, + NULL, NULL, default_inq_cb, &dfltparm, + scd_genkey_cb, createtime); + + return status_sc_op_failure (err); +} + + + +/* Return the serial number of the card or an appropriate error. The + * serial number is returned as a hexstring. If DEMAND is not NULL + * the reader with the a card of the serilanumber DEMAND is + * requested. */ +gpg_error_t +scd_serialno (char **r_serialno, const char *demand) +{ + int err; + char *serialno = NULL; + char line[ASSUAN_LINELENGTH]; + + err = start_agent (START_AGENT_SUPPRESS_ERRORS); + if (err) + return err; + + if (!demand) + strcpy (line, "SCD SERIALNO"); + else + snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand); + + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, + get_serialno_cb, &serialno); + if (err) + { + xfree (serialno); + return err; + } + + *r_serialno = serialno; + return 0; +} + + + +/* Send a READCERT command to the SCdaemon. */ +gpg_error_t +scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t len; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + *r_buf = NULL; + err = start_agent (0); + if (err) + return err; + + dfltparm.ctx = agent_ctx; + + init_membuf (&data, 2048); + + snprintf (line, sizeof line, "SCD READCERT %s", certidstr); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + + *r_buf = get_membuf (&data, r_buflen); + if (!*r_buf) + return gpg_error_from_syserror (); + + return 0; +} + + + +/* Send a READKEY command to the SCdaemon. On success a new + * s-expression is stored at R_RESULT. */ +gpg_error_t +scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + unsigned char *buf; + size_t len, buflen; + + *r_result = NULL; + err = start_agent (0); + if (err) + return err; + + init_membuf (&data, 1024); + snprintf (line, DIM(line), "SCD READKEY %s", keyrefstr); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + NULL, NULL, + NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + buf = get_membuf (&data, &buflen); + if (!buf) + return gpg_error_from_syserror (); + + err = gcry_sexp_new (r_result, buf, buflen, 0); + xfree (buf); + + return err; +} + + + +/* Callback function for card_cardlist. */ +static gpg_error_t +card_cardlist_cb (void *opaque, const char *line) +{ + struct card_cardlist_parm_s *parm = opaque; + const char *keyword = line; + int keywordlen; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + const char *s; + int n; + + for (n=0,s=line; hexdigitp (s); s++, n++) + ; + + if (!n || (n&1) || *s) + parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); + else + add_to_strlist (&parm->list, line); + } + + return 0; +} + + +/* Return the serial numbers of all cards currently inserted. */ +gpg_error_t +scd_cardlist (strlist_t *result) +{ + gpg_error_t err; + struct card_cardlist_parm_s parm; + + memset (&parm, 0, sizeof parm); + *result = NULL; + + err = start_agent (START_AGENT_SUPPRESS_ERRORS); + if (err) + return err; + + err = assuan_transact (agent_ctx, "SCD GETINFO card_list", + NULL, NULL, NULL, NULL, + card_cardlist_cb, &parm); + if (!err && parm.error) + err = parm.error; + + if (!err) + *result = parm.list; + else + free_strlist (parm.list); + + return err; +} + + + +/* Change the PIN of an OpenPGP card or reset the retry counter. + * CHVNO 1: Change the PIN + * 2: For v1 cards: Same as 1. + * For v2 cards: Reset the PIN using the Reset Code. + * 3: Change the admin PIN + * 101: Set a new PIN and reset the retry counter + * 102: For v1 cars: Same as 101. + * For v2 cards: Set a new Reset Code. + */ +gpg_error_t +scd_change_pin (const char *pinref, int reset_mode) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + err = start_agent (0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + snprintf (line, sizeof line, "SCD PASSWD%s %s", + reset_mode? " --reset":"", pinref); + err = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + + return status_sc_op_failure (err); +} + + +/* Perform a CHECKPIN operation. SERIALNO should be the serial + * number of the card - optionally followed by the fingerprint; + * however the fingerprint is ignored here. */ +gpg_error_t +scd_checkpin (const char *serialno) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + err = start_agent (0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + snprintf (line, sizeof line, "SCD CHECKPIN %s", serialno); + err = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + return status_sc_op_failure (err); +} + + +/* Return the S2K iteration count as computed by gpg-agent. On error + * print a warning and return a default value. */ +unsigned long +agent_get_s2k_count (void) +{ + gpg_error_t err; + membuf_t data; + char *buf; + unsigned long count = 0; + + err = start_agent (0); + if (err) + goto leave; + + init_membuf (&data, 32); + err = assuan_transact (agent_ctx, "GETINFO s2k_count", + put_membuf_cb, &data, + NULL, NULL, NULL, NULL); + if (err) + xfree (get_membuf (&data, NULL)); + else + { + put_membuf (&data, "", 1); + buf = get_membuf (&data, NULL); + if (!buf) + err = gpg_error_from_syserror (); + else + { + count = strtoul (buf, NULL, 10); + xfree (buf); + } + } + + leave: + if (err || count < 65536) + { + /* Don't print an error if an older agent is used. */ + if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER) + log_error (_("problem with the agent: %s\n"), gpg_strerror (err)); + + /* Default to 65536 which was used up to 2.0.13. */ + count = 65536; + } + + return count; +} diff --git a/tools/card-keys.c b/tools/card-keys.c new file mode 100644 index 000000000..ad06f2ff7 --- /dev/null +++ b/tools/card-keys.c @@ -0,0 +1,555 @@ +/* card-keys.c - OpenPGP and CMS related functions for gpg-card + * Copyright (C) 2019 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/util.h" +#include "../common/i18n.h" +#include "../common/ccparray.h" +#include "../common/exectool.h" +#include "../common/openpgpdefs.h" +#include "gpg-card.h" + + +/* It is quite common that all keys of an OpenPGP card belong to the + * the same OpenPGP keyblock. To avoid running several queries + * despite that we already got the information with the previous + * keyblock, we keep a small cache of of previous done queries. */ +static struct +{ + unsigned int lru; + keyblock_t keyblock; +} keyblock_cache[5]; + + + +/* Helper for release_keyblock. */ +static void +do_release_keyblock (keyblock_t keyblock) +{ + pubkey_t pubkey; + userid_t uid; + + while (keyblock) + { + keyblock_t keyblocknext = keyblock->next; + pubkey = keyblock->keys; + while (pubkey) + { + pubkey_t pubkeynext = pubkey->next; + xfree (pubkey); + pubkey = pubkeynext; + } + uid = keyblock->uids; + while (uid) + { + userid_t uidnext = uid->next; + xfree (uid->value); + xfree (uid); + uid = uidnext; + } + xfree (keyblock); + keyblock = keyblocknext; + } +} + + +/* Release a keyblock object. */ +void +release_keyblock (keyblock_t keyblock) +{ + static unsigned int lru_counter; + unsigned int lru; + int i, lru_idx; + + if (!keyblock) + return; + + lru = (unsigned int)(-1); + lru_idx = 0; + for (i=0; i < DIM (keyblock_cache); i++) + { + if (!keyblock_cache[i].keyblock) + { + keyblock_cache[i].keyblock = keyblock; + keyblock_cache[i].lru = ++lru_counter; + goto leave; + } + if (keyblock_cache[i].lru < lru) + { + lru = keyblock_cache[i].lru; + lru_idx = i; + } + } + + /* No free slot. Replace one. */ + do_release_keyblock (keyblock_cache[lru_idx].keyblock); + keyblock_cache[lru_idx].keyblock = keyblock; + keyblock_cache[lru_idx].lru = ++lru_counter; + + leave: + if (!lru_counter) + { + /* Wrapped around. We simply clear the entire cache. */ + flush_keyblock_cache (); + } +} + + +/* Flush the enire keyblock cache. */ +void +flush_keyblock_cache (void) +{ + int i; + + for (i=0; i < DIM (keyblock_cache); i++) + { + do_release_keyblock (keyblock_cache[i].keyblock); + keyblock_cache[i].keyblock = NULL; + } +} + + + +/* Object to communicate with the status_cb. */ +struct status_cb_s +{ + const char *pgm; /* Name of the program for debug purposes. */ + int no_pubkey; /* Result flag. */ +}; + + +/* Status callback helper for the exec functions. */ +static void +status_cb (void *opaque, const char *keyword, char *args) +{ + struct status_cb_s *c = opaque; + const char *s; + + if (DBG_EXTPROG) + log_debug ("%s: status: %s %s\n", c->pgm, keyword, args); + + if (!strcmp (keyword, "ERROR") + && (s=has_leading_keyword (args, "keylist.getkey")) + && gpg_err_code (atoi (s)) == GPG_ERR_NO_PUBKEY) + { + /* No public key was found. gpg terminates with an error in + * this case and we can't change that behaviour. Instead we + * detect this status and carry that error forward. */ + c->no_pubkey = 1; + } + +} + + +/* Helper for get_matching_keys to parse "pub" style records. */ +static gpg_error_t +parse_key_record (char **fields, int nfields, pubkey_t *r_pubkey) +{ + pubkey_t pubkey; + + (void)fields; /* Not yet used. */ + (void)nfields; + + pubkey = xtrycalloc (1, sizeof *pubkey); + if (!pubkey) + return gpg_error_from_syserror (); + *r_pubkey = pubkey; + return 0; +} + + +/* Run gpg or gpgsm to get a list of all keys matching the 20 byte + * KEYGRIP. PROTOCOL is one of or a combination of + * GNUPG_PROTOCOL_OPENPGP and GNUPG_PROTOCOL_CMS. On success a new + * keyblock is stored at R_KEYBLOCK; on error NULL is stored there. */ +gpg_error_t +get_matching_keys (const unsigned char *keygrip, int protocol, + keyblock_t *r_keyblock) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv; + estream_t listing; + char hexgrip[1 + (2*KEYGRIP_LEN) + 1]; + char *line = NULL; + size_t length_of_line = 0; + size_t maxlen; + ssize_t len; + char **fields = NULL; + int nfields; + int first_seen; + int i; + keyblock_t keyblock_head, *keyblock_tail, kb; + pubkey_t pubkey, pk; + size_t n; + struct status_cb_s status_cb_parm; + + *r_keyblock = NULL; + + keyblock_head = NULL; + keyblock_tail = &keyblock_head; + kb = NULL; + + /* Shortcut to run a listing on both protocols. */ + if ((protocol & GNUPG_PROTOCOL_OPENPGP) && (protocol & GNUPG_PROTOCOL_CMS)) + { + err = get_matching_keys (keygrip, GNUPG_PROTOCOL_OPENPGP, &kb); + if (!err || gpg_err_code (err) == GPG_ERR_NO_PUBKEY) + { + if (!err) + { + *keyblock_tail = kb; + keyblock_tail = &kb->next; + kb = NULL; + } + err = get_matching_keys (keygrip, GNUPG_PROTOCOL_CMS, &kb); + if (!err) + { + *keyblock_tail = kb; + keyblock_tail = &kb->next; + kb = NULL; + } + else if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) + err = 0; + } + if (err) + release_keyblock (keyblock_head); + else + *r_keyblock = keyblock_head; + return err; + } + + /* Check that we have only one protocol. */ + if (protocol != GNUPG_PROTOCOL_OPENPGP && protocol != GNUPG_PROTOCOL_CMS) + return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); + + /* Try to get it from our cache. */ + for (i=0; i < DIM (keyblock_cache); i++) + for (kb = keyblock_cache[i].keyblock; kb; kb = kb->next) + if (kb->protocol == protocol) + for (pk = kb->keys; pk; pk = pk->next) + if (pk->grip_valid && !memcmp (pk->grip, keygrip, KEYGRIP_LEN)) + { + *r_keyblock = keyblock_cache[i].keyblock; + keyblock_cache[i].keyblock = NULL; + return 0; + } + + /* Open a memory stream. */ + listing = es_fopenmem (0, "w+b"); + if (!listing) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + return err; + } + + status_cb_parm.pgm = protocol == GNUPG_PROTOCOL_OPENPGP? "gpg":"gpgsm"; + status_cb_parm.no_pubkey = 0; + + hexgrip[0] = '&'; + bin2hex (keygrip, KEYGRIP_LEN, hexgrip+1); + + ccparray_init (&ccp, 0); + + if (opt.verbose > 1 || DBG_EXTPROG) + ccparray_put (&ccp, "--verbose"); + else + ccparray_put (&ccp, "--quiet"); + ccparray_put (&ccp, "--no-options"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--status-fd=2"); + ccparray_put (&ccp, "--with-colons"); + ccparray_put (&ccp, "--with-keygrip"); + ccparray_put (&ccp, "--list-keys"); + ccparray_put (&ccp, hexgrip); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (protocol == GNUPG_PROTOCOL_OPENPGP? + opt.gpg_program : opt.gpgsm_program, + argv, NULL, NULL, listing, status_cb, + &status_cb_parm); + if (err) + { + if (status_cb_parm.no_pubkey) + err = gpg_error (GPG_ERR_NO_PUBKEY); + else if (gpg_err_code (err) != GPG_ERR_GENERAL) + log_error ("key listing failed: %s\n", gpg_strerror (err)); + goto leave; + } + + es_rewind (listing); + first_seen = 0; + maxlen = 8192; /* Set limit large enough for all escaped UIDs. */ + while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0) + { + if (!maxlen) + { + log_error ("received line too long\n"); + err = gpg_error (GPG_ERR_LINE_TOO_LONG); + goto leave; + } + /* Strip newline and carriage return, if present. */ + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) + line[--len] = '\0'; + + xfree (fields); + fields = strtokenize (line, ":"); + if (!fields) + { + err = gpg_error_from_syserror (); + log_error ("strtokenize failed: %s\n", gpg_strerror (err)); + goto leave; + } + for (nfields = 0; fields[nfields]; nfields++) + ; + if (!nfields) + { + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + + /* Skip over all records until we reach a pub or sec. */ + if (!first_seen + && (!strcmp (fields[0], "pub") || !strcmp (fields[0], "sec") + || !strcmp (fields[0], "crt") || !strcmp (fields[0], "crs"))) + first_seen = 1; + if (!first_seen) + continue; + + if (!strcmp (fields[0], "pub") || !strcmp (fields[0], "sec") + || !strcmp (fields[0], "crt") || !strcmp (fields[0], "crs")) + { + if (kb) /* Finish the current keyblock. */ + { + *keyblock_tail = kb; + keyblock_tail = &kb->next; + } + kb = xtrycalloc (1, sizeof *kb); + if (!kb) + { + err = gpg_error_from_syserror (); + goto leave; + } + kb->protocol = protocol; + err = parse_key_record (fields, nfields, &pubkey); + if (err) + goto leave; + kb->keys = pubkey; + pubkey = NULL; + } + else if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb")) + { + log_assert (kb && kb->keys); + err = parse_key_record (fields, nfields, &pubkey); + if (err) + goto leave; + for (pk = kb->keys; pk->next; pk = pk->next) + ; + pk->next = pubkey; + pubkey = NULL; + } + else if (!strcmp (fields[0], "fpr") && nfields > 9) + { + log_assert (kb && kb->keys); + n = strlen (fields[9]); + if (n != 64 && n != 40 && n != 32) + { + log_debug ("bad length (%zu) in fpr record\n", n); + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + n /= 2; + + for (pk = kb->keys; pk->next; pk = pk->next) + ; + if (pk->fprlen) + { + log_debug ("too many fpr records\n"); + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + log_assert (n <= sizeof pk->fpr); + pk->fprlen = n; + if (hex2bin (fields[9], pk->fpr, n) < 0) + { + log_debug ("bad chars in fpr record\n"); + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + } + else if (!strcmp (fields[0], "grp") && nfields > 9) + { + log_assert (kb && kb->keys); + n = strlen (fields[9]); + if (n != 2*KEYGRIP_LEN) + { + log_debug ("bad length (%zu) in grp record\n", n); + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + n /= 2; + + for (pk = kb->keys; pk->next; pk = pk->next) + ; + if (pk->grip_valid) + { + log_debug ("too many grp records\n"); + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + if (hex2bin (fields[9], pk->grip, KEYGRIP_LEN) < 0) + { + log_debug ("bad chars in fpr record\n"); + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + pk->grip_valid = 1; + if (!memcmp (pk->grip, keygrip, KEYGRIP_LEN)) + pk->requested = 1; + } + else if (!strcmp (fields[0], "uid") && nfields > 9) + { + userid_t uid, u; + + uid = xtrycalloc (1, sizeof *uid); + if (!uid) + { + err = gpg_error_from_syserror (); + goto leave; + } + uid->value = decode_c_string (fields[9]); + if (!uid->value) + { + err = gpg_error_from_syserror (); + xfree (uid); + goto leave; + } + if (!kb->uids) + kb->uids = uid; + else + { + for (u = kb->uids; u->next; u = u->next) + ; + u->next = uid; + } + } + } + if (len < 0 || es_ferror (listing)) + { + err = gpg_error_from_syserror (); + log_error ("error reading memory stream\n"); + goto leave; + } + + if (kb) /* Finish the current keyblock. */ + { + *keyblock_tail = kb; + keyblock_tail = &kb->next; + kb = NULL; + } + + if (!keyblock_head) + err = gpg_error (GPG_ERR_NO_PUBKEY); + + leave: + if (err) + release_keyblock (keyblock_head); + else + *r_keyblock = keyblock_head; + xfree (kb); + xfree (fields); + es_free (line); + xfree (argv); + es_fclose (listing); + return err; +} + + +void +dump_keyblock (keyblock_t keyblock) +{ + keyblock_t kb; + pubkey_t pubkey; + userid_t uid; + + for (kb = keyblock; kb; kb = kb->next) + { + log_info ("%s key:\n", + kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP":"X.509"); + for (pubkey = kb->keys; pubkey; pubkey = pubkey->next) + { + log_info (" grip: "); + if (pubkey->grip_valid) + log_printhex (pubkey->grip, KEYGRIP_LEN, NULL); + log_printf ("%s\n", pubkey->requested? " (*)":""); + + log_info (" fpr: "); + log_printhex (pubkey->fpr, pubkey->fprlen, ""); + } + for (uid = kb->uids; uid; uid = uid->next) + { + log_info (" uid: %s\n", uid->value); + } + } +} + + + +gpg_error_t +test_get_matching_keys (const char *hexgrip) +{ + gpg_error_t err; + unsigned char grip[KEYGRIP_LEN]; + keyblock_t keyblock; + + if (strlen (hexgrip) != 40) + { + log_error ("error: invalid keygrip\n"); + return 0; + } + if (hex2bin (hexgrip, grip, sizeof grip) < 0) + { + log_error ("error: bad kegrip\n"); + return 0; + } + err = get_matching_keys (grip, + (GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS), + &keyblock); + if (err) + { + log_error ("get_matching_keys failed: %s\n", gpg_strerror (err)); + return err; + } + + dump_keyblock (keyblock); + release_keyblock (keyblock); + return 0; +} diff --git a/tools/card-misc.c b/tools/card-misc.c new file mode 100644 index 000000000..bccdbda9d --- /dev/null +++ b/tools/card-misc.c @@ -0,0 +1,113 @@ +/* card-misc.c - Helper functions for gpg-card + * Copyright (C) 2019 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include "../common/util.h" +#include "../common/i18n.h" +#include "../common/openpgpdefs.h" +#include "gpg-card.h" + +/* Return the key info object for the key KEYREF. If it is not found + * NULL is returned. */ +key_info_t +find_kinfo (card_info_t info, const char *keyref) +{ + key_info_t kinfo; + + for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) + if (!strcmp (kinfo->keyref, keyref)) + return kinfo; + return NULL; +} + + +/* Convert STRING into a newly allocated buffer while translating the + * hex numbers. Blanks and colons are allowed to separate pairs of + * hex digits. Returns NULL on error or a newly malloced buffer and + * its length in LENGTH. */ +void * +hex_to_buffer (const char *string, size_t *r_length) +{ + unsigned char *buffer; + const char *s; + size_t n; + + buffer = xtrymalloc (strlen (string)+1); + if (!buffer) + return NULL; + for (s=string, n=0; *s; s++) + { + if (ascii_isspace (*s) || *s == ':') + continue; + if (hexdigitp (s) && hexdigitp (s+1)) + { + buffer[n++] = xtoi_2 (s); + s++; + } + else + { + xfree (buffer); + gpg_err_set_errno (EINVAL); + return NULL; + } + } + *r_length = n; + return buffer; +} + + +/* Direct sending of an hex encoded APDU with error printing. This is + * a simple wrapper around scd_apdu. */ +gpg_error_t +send_apdu (const char *hexapdu, const char *desc, unsigned int ignore, + unsigned char **r_data, size_t *r_datalen) +{ + gpg_error_t err; + unsigned int sw; + + err = scd_apdu (hexapdu, &sw, r_data, r_datalen); + if (err) + log_error ("sending card command %s failed: %s\n", desc, + gpg_strerror (err)); + else if (!hexapdu || !strcmp (hexapdu, "undefined")) + ; + else if (ignore == 0xffff) + ; /* Ignore all status words. */ + else if (sw != 0x9000) + { + switch (sw) + { + case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break; + case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break; + case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break; + default: err = gpg_error (GPG_ERR_CARD); + } + if (!(ignore && ignore == sw)) + log_error ("card command %s failed: %s (0x%04x)\n", desc, + gpg_strerror (err), sw); + } + return err; +} diff --git a/tools/card-yubikey.c b/tools/card-yubikey.c new file mode 100644 index 000000000..f9d130988 --- /dev/null +++ b/tools/card-yubikey.c @@ -0,0 +1,438 @@ +/* card-yubikey.c - Yubikey specific functions. + * Copyright (C) 2019 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include "../common/util.h" +#include "../common/i18n.h" +#include "../common/tlv.h" +#include "../common/ttyio.h" +#include "gpg-card.h" + + +/* Object to describe requested interface options. */ +struct iface_s { + unsigned int usb:1; + unsigned int nfc:1; +}; + + +/* Bit flags as used by the fields in struct ykapps_s. */ +#define YKAPP_USB_SUPPORTED 0x01 +#define YKAPP_USB_ENABLED 0x02 +#define YKAPP_NFC_SUPPORTED 0x04 +#define YKAPP_NFC_ENABLED 0x08 +#define YKAPP_SELECTED 0x80 /* Selected by the command. */ + +/* An object to describe the applications on a Yubikey. Each field + * has 8 bits to hold the above flag values. */ +struct ykapps_s { + unsigned int otp:8; + unsigned int u2f:8; + unsigned int opgp:8; + unsigned int piv:8; + unsigned int oath:8; + unsigned int fido2:8; +}; + + + +/* Helper to parse an unsigned integer config value consisting of bit + * flags. TAG select the config item and MASK is the mask ORed into + * the value for a set bit. The function modifies YK. */ +static gpg_error_t +parse_ul_config_value (struct ykapps_s *yk, + const unsigned char *config, size_t configlen, + int tag, unsigned int mask) +{ + const unsigned char *s; + size_t n; + unsigned long ul = 0; + int i; + + s = find_tlv (config, configlen, tag, &n); + if (s && n) + { + if (n > sizeof ul) + { + log_error ("too large integer in Yubikey config tag %02x detected\n", + tag); + if (opt.verbose) + log_printhex (config, configlen, "config:"); + return gpg_error (GPG_ERR_CARD); + } + for (i=0; i < n; i++) + { + ul <<=8; + ul |= s[i]; + } + if (ul & 0x01) + yk->otp |= mask; + if (ul & 0x02) + yk->u2f |= mask; + if (ul & 0x08) + yk->opgp |= mask; + if (ul & 0x10) + yk->piv |= mask; + if (ul & 0x20) + yk->oath |= mask; + if (ul & 0x200) + yk->fido2 |= mask; + } + return 0; +} + + +/* Create an unsigned integer config value for TAG from the data in YK + * and store it the provided 4 byte buffer RESULT. If ENABLE is true + * the respective APP_SELECTED bit in YK sets the corresponding bit + * flags, it is is false that bit flag is cleared. IF APP_SELECTED is + * not set the bit flag is not changed. */ +static void +set_ul_config_value (struct ykapps_s *yk, + unsigned int bitflag, int tag, unsigned int enable, + unsigned char *result) +{ + unsigned long ul = 0; + + /* First set the current values. */ + if ((yk->otp & bitflag)) + ul |= 0x01; + if ((yk->u2f & bitflag)) + ul |= 0x02; + if ((yk->opgp & bitflag)) + ul |= 0x08; + if ((yk->piv & bitflag)) + ul |= 0x10; + if ((yk->oath & bitflag)) + ul |= 0x20; + if ((yk->fido2 & bitflag)) + ul |= 0x200; + + /* Then enable or disable the bits according to the selection flag. */ + if (enable) + { + if ((yk->otp & YKAPP_SELECTED)) + ul |= 0x01; + if ((yk->u2f & YKAPP_SELECTED)) + ul |= 0x02; + if ((yk->opgp & YKAPP_SELECTED)) + ul |= 0x08; + if ((yk->piv & YKAPP_SELECTED)) + ul |= 0x10; + if ((yk->oath & YKAPP_SELECTED)) + ul |= 0x20; + if ((yk->fido2 & YKAPP_SELECTED)) + ul |= 0x200; + } + else + { + if ((yk->otp & YKAPP_SELECTED)) + ul &= ~0x01; + if ((yk->u2f & YKAPP_SELECTED)) + ul &= ~0x02; + if ((yk->opgp & YKAPP_SELECTED)) + ul &= ~0x08; + if ((yk->piv & YKAPP_SELECTED)) + ul &= ~0x10; + if ((yk->oath & YKAPP_SELECTED)) + ul &= ~0x20; + if ((yk->fido2 & YKAPP_SELECTED)) + ul &= ~0x200; + } + + /* Make sure that we do not disable the CCID transport. Without + * CCID we won't have any way to change the configuration again. We + * would instead need one of the other Yubikey tools to enable an + * application and thus its transport again. */ + if (bitflag == YKAPP_USB_ENABLED && !(ul & (0x08|0x10|0x20))) + { + log_info ("Enabling PIV to have at least one CCID transport\n"); + ul |= 0x10; + } + + result[0] = tag; + result[1] = 2; + result[2] = ul >> 8; + result[3] = ul; +} + + +/* Print the info from YK. */ +static void +yk_list (estream_t fp, struct ykapps_s *yk) +{ + if (opt.interactive) + tty_fprintf (fp, ("Application USB NFC\n" + "-----------------------\n")); + tty_fprintf (fp, "OTP %s %s\n", + (yk->otp & YKAPP_USB_SUPPORTED)? + (yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->otp & YKAPP_NFC_SUPPORTED)? + (yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "U2F %s %s\n", + (yk->otp & YKAPP_USB_SUPPORTED)? + (yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->otp & YKAPP_NFC_SUPPORTED)? + (yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "OPGP %s %s\n", + (yk->opgp & YKAPP_USB_SUPPORTED)? + (yk->opgp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->opgp & YKAPP_NFC_SUPPORTED)? + (yk->opgp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "PIV %s %s\n", + (yk->piv & YKAPP_USB_SUPPORTED)? + (yk->piv & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->piv & YKAPP_NFC_SUPPORTED)? + (yk->piv & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "OATH %s %s\n", + (yk->oath & YKAPP_USB_SUPPORTED)? + (yk->oath & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->oath & YKAPP_NFC_SUPPORTED)? + (yk->oath & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); + tty_fprintf (fp, "FIDO2 %s %s\n", + (yk->fido2 & YKAPP_USB_SUPPORTED)? + (yk->fido2 & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", + (yk->fido2 & YKAPP_NFC_SUPPORTED)? + (yk->fido2 & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); +} + + +/* Enable disable the apps as marked in YK with flag YKAPP_SELECTED. */ +static gpg_error_t +yk_enable_disable (struct ykapps_s *yk, struct iface_s *iface, + const unsigned char *config, size_t configlen, int enable) +{ + gpg_error_t err = 0; + unsigned char apdu[100]; + unsigned int apdulen; + /* const unsigned char *s; */ + /* size_t n; */ + char *hexapdu = NULL; + + apdulen = 0; + apdu[apdulen++] = 0x00; + apdu[apdulen++] = 0x1c; /* Write Config instruction. */ + apdu[apdulen++] = 0x00; + apdu[apdulen++] = 0x00; + apdu[apdulen++] = 0x00; /* Lc will be fixed up later. */ + apdu[apdulen++] = 0x00; /* Length of data will also be fixed up later. */ + + /* The ykman tool has no way to set NFC and USB flags in one go. + * Reasoning about the Yubikey's firmware it seems plausible that + * combining should work. Let's try it here if the user called for + * setting both interfaces. */ + if (iface->nfc) + { + set_ul_config_value (yk, YKAPP_NFC_ENABLED, 0x0e, enable, apdu+apdulen); + apdulen += 4; + } + if (iface->usb) + { + set_ul_config_value (yk, YKAPP_USB_ENABLED, 0x03, enable, apdu+apdulen); + apdulen += 4; + /* Yubikey's ykman copies parts of the config data when writing + * the config for USB. Below is a commented example on how that + * can be done. */ + (void)config; + (void)configlen; + /* Copy the device flags. */ + /* s = find_tlv (config, configlen, 0x08, &n); */ + /* if (s && n) */ + /* { */ + /* s -= 2; */ + /* n += 2; */ + /* if (apdulen + n > sizeof apdu) */ + /* { */ + /* err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); */ + /* goto leave; */ + /* } */ + /* memcpy (apdu+apdulen, s, n); */ + /* apdulen += n; */ + /* } */ + } + if (iface->nfc || iface->usb) + { + if (apdulen + 2 > sizeof apdu) + { + err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); + goto leave; + } + /* Disable the next two lines to let the card reboot. Not doing + * this is however more convenient for this tool because further + * commands don't end up with an error. It seems to be better + * that a "reset" command from gpg-card-tool is run at the + * user's discretion. */ + /* apdu[apdulen++] = 0x0c; /\* Reboot tag *\/ */ + /* apdu[apdulen++] = 0; /\* No data for reboot. *\/ */ + /* Fixup the lngth bytes. */ + apdu[4] = apdulen - 6 + 1; + apdu[5] = apdulen - 6; + + hexapdu = bin2hex (apdu, apdulen, NULL); + if (!hexapdu) + err = gpg_error_from_syserror (); + else + err = send_apdu (hexapdu, "YK.write_config", 0, NULL, NULL); + } + + leave: + xfree (hexapdu); + return err; +} + + +/* Implementation part of cmd_yubikey. ARGV is an array of size ARGc + * with the argumets given to the yubikey command. Note that ARGV has + * no terminating NULL so that ARGC must be considred. FP is the + * stream to output information. This function must only be called on + * Yubikeys. */ +gpg_error_t +yubikey_commands (estream_t fp, int argc, char *argv[]) +{ + gpg_error_t err; + enum {ykLIST, ykENABLE, ykDISABLE } cmd; + struct iface_s iface = {0,0}; + struct ykapps_s ykapps = {0}; + unsigned char *config = NULL; + size_t configlen; + int i; + + if (!argc) + return gpg_error (GPG_ERR_SYNTAX); + + /* Parse command. */ + if (!ascii_strcasecmp (argv[0], "list")) + cmd = ykLIST; + else if (!ascii_strcasecmp (argv[0], "enable")) + cmd = ykENABLE; + else if (!ascii_strcasecmp (argv[0], "disable")) + cmd = ykDISABLE; + else + { + err = gpg_error (GPG_ERR_UNKNOWN_COMMAND); + goto leave; + } + + /* Parse interface if needed. */ + if (cmd == ykLIST) + iface.usb = iface.nfc = 1; + else if (argc < 2) + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + else if (!ascii_strcasecmp (argv[1], "usb")) + iface.usb = 1; + else if (!ascii_strcasecmp (argv[1], "nfc")) + iface.nfc = 1; + else if (!ascii_strcasecmp (argv[1], "all") || !strcmp (argv[1], "*")) + iface.usb = iface.nfc = 1; + else + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + + /* Parse list of applications. */ + for (i=2; i < argc; i++) + { + if (!ascii_strcasecmp (argv[i], "otp")) + ykapps.otp = 0x80; + else if (!ascii_strcasecmp (argv[i], "u2f")) + ykapps.u2f = 0x80; + else if (!ascii_strcasecmp (argv[i], "opgp") + ||!ascii_strcasecmp (argv[i], "openpgp")) + ykapps.opgp = 0x80; + else if (!ascii_strcasecmp (argv[i], "piv")) + ykapps.piv = 0x80; + else if (!ascii_strcasecmp (argv[i], "oath") + || !ascii_strcasecmp (argv[i], "oauth")) + ykapps.oath = 0x80; + else if (!ascii_strcasecmp (argv[i], "fido2")) + ykapps.fido2 = 0x80; + else if (!ascii_strcasecmp (argv[i], "all")|| !strcmp (argv[i], "*")) + { + ykapps.otp = ykapps.u2f = ykapps.opgp = ykapps.piv = ykapps.oath + = ykapps.fido2 = 0x80; + } + else + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + } + + /* Select the Yubikey Manager application. */ + err = send_apdu ("00A4040008a000000527471117", "Select.YK-Manager", 0, + NULL, NULL); + if (err) + goto leave; + /* Send the read config command. */ + err = send_apdu ("001D000000", "YK.read_config", 0, &config, &configlen); + if (err) + goto leave; + if (!configlen || *config > configlen - 1) + { + /* The length byte is shorter than the actual length. */ + log_error ("Yubikey returned improper config data\n"); + log_printhex (config, configlen, "config:"); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + if (configlen-1 > *config) + { + log_info ("Extra config data ignored\n"); + log_printhex (config, configlen, "config:"); + } + configlen = *config; + + err = parse_ul_config_value (&ykapps, config+1, configlen, + 0x01, YKAPP_USB_SUPPORTED); + if (!err) + err = parse_ul_config_value (&ykapps, config+1, configlen, + 0x03, YKAPP_USB_ENABLED); + if (!err) + err = parse_ul_config_value (&ykapps, config+1, configlen, + 0x0d, YKAPP_NFC_SUPPORTED); + if (!err) + err = parse_ul_config_value (&ykapps, config+1, configlen, + 0x0e, YKAPP_NFC_ENABLED); + if (err) + goto leave; + + switch (cmd) + { + case ykLIST: yk_list (fp, &ykapps); break; + case ykENABLE: err = yk_enable_disable (&ykapps, &iface, + config+1, configlen, 1); break; + case ykDISABLE: err = yk_enable_disable (&ykapps, &iface, + config+1, configlen, 0); break; + } + + leave: + xfree (config); + return err; +} diff --git a/tools/gpg-card-w32info.rc b/tools/gpg-card-w32info.rc new file mode 100644 index 000000000..b35ff4ce2 --- /dev/null +++ b/tools/gpg-card-w32info.rc @@ -0,0 +1,51 @@ +/* gpg-card-w32info.rc -*- c -*- + * Copyright (C) 2019 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s card tool \ +to the agent\0" + VALUE "InternalName", "gpg-card\0" + VALUE "OriginalFilename", "gpg-card.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END diff --git a/tools/gpg-card.c b/tools/gpg-card.c new file mode 100644 index 000000000..e2d728dab --- /dev/null +++ b/tools/gpg-card.c @@ -0,0 +1,3497 @@ +/* gpg-card.c - An interactive tool to work with cards. + * Copyright (C) 2019 g10 Code GmbH + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_LIBREADLINE +# define GNUPG_LIBREADLINE_H_INCLUDED +# include <readline/readline.h> +#endif /*HAVE_LIBREADLINE*/ + +#include "../common/util.h" +#include "../common/status.h" +#include "../common/i18n.h" +#include "../common/init.h" +#include "../common/sysutils.h" +#include "../common/asshelp.h" +#include "../common/userids.h" +#include "../common/ccparray.h" +#include "../common/exectool.h" +#include "../common/ttyio.h" +#include "../common/server-help.h" +#include "../common/openpgpdefs.h" + +#include "gpg-card.h" + + +#define CONTROL_D ('D' - 'A' + 1) + +/* Constants to identify the commands and options. */ +enum opt_values + { + aNull = 0, + + oQuiet = 'q', + oVerbose = 'v', + + oDebug = 500, + + oGpgProgram, + oGpgsmProgram, + oStatusFD, + oWithColons, + oNoAutostart, + oAgentProgram, + + oDisplay, + oTTYname, + oTTYtype, + oXauthority, + oLCctype, + oLCmessages, + + oDummy + }; + + +/* The list of commands and options. */ +static ARGPARSE_OPTS opts[] = { + ARGPARSE_group (301, ("@\nOptions:\n ")), + + ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_s (oGpgProgram, "gpg", "@"), + ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"), + ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + ARGPARSE_s_n (oWithColons, "with-colons", "@"), + ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), + ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), + ARGPARSE_s_s (oDisplay, "display", "@"), + ARGPARSE_s_s (oTTYname, "ttyname", "@"), + ARGPARSE_s_s (oTTYtype, "ttytype", "@"), + ARGPARSE_s_s (oXauthority, "xauthority", "@"), + ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), + ARGPARSE_s_s (oLCmessages, "lc-messages","@"), + + ARGPARSE_end () +}; + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_IPC_VALUE , "ipc" }, + { DBG_EXTPROG_VALUE, "extprog" }, + { 0, NULL } + }; + + +/* An object to create lists of labels and keyrefs. */ +struct keyinfolabel_s +{ + const char *label; + const char *keyref; +}; +typedef struct keyinfolabel_s *keyinfolabel_t; + + +/* Limit of size of data we read from a file for certain commands. */ +#define MAX_GET_DATA_FROM_FILE 16384 + +/* Constants for OpenPGP cards. */ +#define OPENPGP_USER_PIN_DEFAULT "123456" +#define OPENPGP_ADMIN_PIN_DEFAULT "12345678" +#define OPENPGP_KDF_DATA_LENGTH_MIN 90 +#define OPENPGP_KDF_DATA_LENGTH_MAX 110 + + + + +/* Local prototypes. */ +static gpg_error_t dispatch_command (card_info_t info, const char *command); +static void interactive_loop (void); +#ifdef HAVE_LIBREADLINE +static char **command_completion (const char *text, int start, int end); +#endif /*HAVE_LIBREADLINE*/ + + + +/* Print usage information and provide strings for help. */ +static const char * +my_strusage( int level ) +{ + const char *p; + + switch (level) + { + case 11: p = "gpg-card"; break; + case 12: p = "@GNUPG@"; break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: + p = ("Usage: gpg-card" + " [options] [{[--] command [args]}] (-h for help)"); + break; + case 41: + p = ("Syntax: gpg-card" + " [options] [command [args] {-- command [args]}]\n\n" + "Tool to manage cards and tokens. With a command an interactive\n" + "mode is used. Use command \"help\" to list all commands."); + break; + + default: p = NULL; break; + } + return p; +} + + +static void +set_opt_session_env (const char *name, const char *value) +{ + gpg_error_t err; + + err = session_env_setenv (opt.session_env, name, value); + if (err) + log_fatal ("error setting session environment: %s\n", + gpg_strerror (err)); +} + + + +/* Command line parsing. */ +static void +parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) +{ + while (optfile_parse (NULL, NULL, NULL, pargs, popts)) + { + switch (pargs->r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oDebug: + if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) + { + pargs->r_opt = ARGPARSE_INVALID_ARG; + pargs->err = ARGPARSE_PRINT_ERROR; + } + break; + + case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; + case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break; + case oAgentProgram: opt.agent_program = pargs->r.ret_str; break; + + case oStatusFD: + gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); + break; + + case oWithColons: opt.with_colons = 1; break; + case oNoAutostart: opt.autostart = 0; break; + + case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break; + case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break; + case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break; + case oXauthority: set_opt_session_env ("XAUTHORITY", + pargs->r.ret_str); break; + case oLCctype: opt.lc_ctype = pargs->r.ret_str; break; + case oLCmessages: opt.lc_messages = pargs->r.ret_str; break; + + default: pargs->err = 2; break; + } + } +} + + + +/* gpg-card main. */ +int +main (int argc, char **argv) +{ + gpg_error_t err; + ARGPARSE_ARGS pargs; + char **command_list = NULL; + int cmdidx; + char *command; + + gnupg_reopen_std ("gpg-card"); + set_strusage (my_strusage); + gnupg_rl_initialize (); + log_set_prefix ("gpg-card", GPGRT_LOG_WITH_PREFIX); + + /* Make sure that our subsystems are ready. */ + i18n_init(); + init_common_subsystems (&argc, &argv); + + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + setup_libassuan_logging (&opt.debug, NULL); + + /* Setup default options. */ + opt.autostart = 1; + opt.session_env = session_env_new (); + if (!opt.session_env) + log_fatal ("error allocating session environment block: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + + + /* Parse the command line. */ + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags = ARGPARSE_FLAG_KEEP; + parse_arguments (&pargs, opts); + + if (log_get_errorcount (0)) + exit (2); + + /* Set defaults for non given options. */ + if (!opt.gpg_program) + opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); + if (!opt.gpgsm_program) + opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM); + + /* Now build the list of commands. We guess the size of the array + * by assuming each item is a complete command. Obviously this will + * be rarely the case, but it is less code to allocate a possible + * too large array. */ + command_list = xcalloc (argc+1, sizeof *command_list); + cmdidx = 0; + command = NULL; + while (argc) + { + for ( ; argc && strcmp (*argv, "--"); argc--, argv++) + { + if (!command) + command = xstrdup (*argv); + else + { + char *tmp = xstrconcat (command, " ", *argv, NULL); + xfree (command); + command = tmp; + } + } + if (argc) + { /* Skip the double dash. */ + argc--; + argv++; + } + if (command) + { + command_list[cmdidx++] = command; + command = NULL; + } + } + opt.interactive = !cmdidx; + + if (opt.interactive) + { + interactive_loop (); + err = 0; + } + else + { + struct card_info_s info_buffer = { 0 }; + card_info_t info = &info_buffer; + + err = 0; + for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++) + { + err = dispatch_command (info, command); + if (err) + break; + } + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; /* This was a "quit". */ + else if (command && !opt.quiet) + log_info ("stopped at command '%s'\n", command); + } + + flush_keyblock_cache (); + if (command_list) + { + for (cmdidx=0; command_list[cmdidx]; cmdidx++) + xfree (command_list[cmdidx]); + xfree (command_list); + } + if (err) + gnupg_status_printf (STATUS_FAILURE, "- %u", err); + else if (log_get_errorcount (0)) + gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); + else + gnupg_status_printf (STATUS_SUCCESS, NULL); + return log_get_errorcount (0)? 1:0; +} + + +/* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters. + * On error return an error code and stores NULL at R_BUFFER; on + * success returns 0 and stores the number of bytes read at R_BUFLEN + * and the address of a newly allocated buffer at R_BUFFER. A + * complementary nul byte is always appended to the data but not + * counted; this allows to pass NULL for R-BUFFER and consider the + * returned data as a string. */ +static gpg_error_t +get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen) +{ + gpg_error_t err; + estream_t fp; + char *data; + int n; + + *r_buffer = NULL; + if (r_buflen) + *r_buflen = 0; + + fp = es_fopen (fname, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); + return err; + } + + data = xtrymalloc (MAX_GET_DATA_FROM_FILE); + if (!data) + { + err = gpg_error_from_syserror (); + log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err)); + es_fclose (fp); + return err; + } + + n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp); + es_fclose (fp); + if (n < 0) + { + err = gpg_error_from_syserror (); + tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); + xfree (data); + return err; + } + data[n] = 0; + + *r_buffer = data; + if (r_buflen) + *r_buflen = n; + return 0; +} + + +/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on + * success. */ +static gpg_error_t +put_data_to_file (const char *fname, const void *buffer, size_t length) +{ + gpg_error_t err; + estream_t fp; + + fp = es_fopen (fname, "wb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err)); + return err; + } + + if (length && es_fwrite (buffer, length, 1, fp) != 1) + { + err = gpg_error_from_syserror (); + log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); + es_fclose (fp); + return err; + } + if (es_fclose (fp)) + { + err = gpg_error_from_syserror (); + log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); + return err; + } + return 0; +} + + + +/* Simply prints TEXT to the output. Returns 0 as a convenience. + * This is a separate fucntion so that it can be extended to run + * less(1) or so. The extra arguments are int values terminated by a + * 0 to indicate card application types supported with this command. + * If none are given (just teh final 0), this is a general + * command. */ +static gpg_error_t +print_help (const char *text, ...) +{ + estream_t fp; + va_list arg_ptr; + int value; + int any = 0; + + fp = opt.interactive? NULL : es_stdout; + tty_fprintf (fp, "%s\n", text); + + va_start (arg_ptr, text); + while ((value = va_arg (arg_ptr, int))) + { + if (!any) + tty_fprintf (fp, "[Supported by: "); + tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value)); + any = 1; + } + if (any) + tty_fprintf (fp, "]\n"); + + va_end (arg_ptr); + return 0; +} + + +/* Return the OpenPGP card manufacturer name. */ +static const char * +get_manufacturer (unsigned int no) +{ + /* Note: Make sure that there is no colon or linefeed in the string. */ + switch (no) + { + case 0x0001: return "PPC Card Systems"; + case 0x0002: return "Prism"; + case 0x0003: return "OpenFortress"; + case 0x0004: return "Wewid"; + case 0x0005: return "ZeitControl"; + case 0x0006: return "Yubico"; + case 0x0007: return "OpenKMS"; + case 0x0008: return "LogoEmail"; + case 0x0009: return "Fidesmo"; + case 0x000A: return "Dangerous Things"; + + case 0x002A: return "Magrathea"; + case 0x0042: return "GnuPG e.V."; + + case 0x1337: return "Warsaw Hackerspace"; + case 0x2342: return "warpzone"; /* hackerspace Muenster. */ + case 0x4354: return "Confidential Technologies"; /* cotech.de */ + case 0x63AF: return "Trustica"; + case 0xBD0E: return "Paranoidlabs"; + case 0xF517: return "FSIJ"; + + /* 0x0000 and 0xFFFF are defined as test cards per spec, + * 0xFF00 to 0xFFFE are assigned for use with randomly created + * serial numbers. */ + case 0x0000: + case 0xffff: return "test card"; + default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown"; + } +} + +/* Print an (OpenPGP) fingerprint. */ +static void +print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen) +{ + int i; + + if (fpr) + { + /* FIXME: Fix formatting for FPRLEN != 20 */ + for (i=0; i < fprlen ; i+=2, fpr += 2 ) + { + if (i == 10 ) + tty_fprintf (fp, " "); + tty_fprintf (fp, " %02X%02X", *fpr, fpr[1]); + } + } + else + tty_fprintf (fp, " [none]"); + tty_fprintf (fp, "\n"); +} + +/* Print the keygrip GRP. */ +static void +print_keygrip (estream_t fp, const unsigned char *grp) +{ + int i; + + for (i=0; i < 20 ; i++, grp++) + tty_fprintf (fp, "%02X", *grp); + tty_fprintf (fp, "\n"); +} + + +/* Print a string but avoid printing control characters. */ +static void +print_string (estream_t fp, const char *text, const char *name) +{ + tty_fprintf (fp, "%s", text); + + /* FIXME: tty_printf_utf8_string2 eats everything after and + including an @ - e.g. when printing an url. */ + if (name && *name) + { + if (fp) + print_utf8_buffer2 (fp, name, strlen (name), '\n'); + else + tty_print_utf8_string2 (NULL, name, strlen (name), 0); + } + else + tty_fprintf (fp, _("[not set]")); + tty_fprintf (fp, "\n"); +} + + +/* Print an ISO formatted name or "[not set]". */ +static void +print_isoname (estream_t fp, const char *name) +{ + if (name && *name) + { + char *p, *given, *buf; + + buf = xstrdup (name); + given = strstr (buf, "<<"); + for (p=buf; *p; p++) + if (*p == '<') + *p = ' '; + if (given && given[2]) + { + *given = 0; + given += 2; + if (fp) + print_utf8_buffer2 (fp, given, strlen (given), '\n'); + else + tty_print_utf8_string2 (NULL, given, strlen (given), 0); + + if (*buf) + tty_fprintf (fp, " "); + } + + if (fp) + print_utf8_buffer2 (fp, buf, strlen (buf), '\n'); + else + tty_print_utf8_string2 (NULL, buf, strlen (buf), 0); + + xfree (buf); + } + else + { + tty_fprintf (fp, _("[not set]")); + } + + tty_fprintf (fp, "\n"); +} + + +/* Return true if the buffer MEM of length memlen consists only of zeroes. */ +static int +mem_is_zero (const char *mem, unsigned int memlen) +{ + int i; + + for (i=0; i < memlen && !mem[i]; i++) + ; + return (i == memlen); +} + + + +/* Helper to list a single keyref. LABEL_KEYREF is a fallback key + * reference if no info is available; it may be NULL. */ +static void +list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, + const char *label_keyref, estream_t fp) +{ + gpg_error_t err; + keyblock_t keyblock = NULL; + keyblock_t kb; + pubkey_t pubkey; + userid_t uid; + key_info_t ki; + const char *s; + gcry_sexp_t s_pkey; + int any; + + if (firstkinfo && kinfo) + { + tty_fprintf (fp, " "); + if (mem_is_zero (kinfo->grip, sizeof kinfo->grip)) + { + tty_fprintf (fp, "[none]\n"); + tty_fprintf (fp, " keyref .....: %s\n", kinfo->keyref); + goto leave; + } + + print_keygrip (fp, kinfo->grip); + tty_fprintf (fp, " keyref .....: %s", kinfo->keyref); + if (kinfo->usage) + { + any = 0; + tty_fprintf (fp, " ("); + if ((kinfo->usage & GCRY_PK_USAGE_SIGN)) + { tty_fprintf (fp, "sign"); any=1; } + if ((kinfo->usage & GCRY_PK_USAGE_CERT)) + { tty_fprintf (fp, "%scert", any?",":""); any=1; } + if ((kinfo->usage & GCRY_PK_USAGE_AUTH)) + { tty_fprintf (fp, "%sauth", any?",":""); any=1; } + if ((kinfo->usage & GCRY_PK_USAGE_ENCR)) + { tty_fprintf (fp, "%sencr", any?",":""); any=1; } + tty_fprintf (fp, ")"); + } + tty_fprintf (fp, "\n"); + + if (!scd_readkey (kinfo->keyref, &s_pkey)) + { + char *tmp = pubkey_algo_string (s_pkey); + tty_fprintf (fp, " algorithm ..: %s\n", tmp); + xfree (tmp); + gcry_sexp_release (s_pkey); + s_pkey = NULL; + } + + if (kinfo->fprlen && kinfo->created) + { + tty_fprintf (fp, " fingerprint :"); + print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen); + tty_fprintf (fp, " created ....: %s\n", + isotimestamp (kinfo->created)); + } + err = get_matching_keys (kinfo->grip, + (GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS), + &keyblock); + if (err) + { + if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY) + tty_fprintf (fp, " error ......: %s\n", gpg_strerror (err)); + goto leave; + } + for (kb = keyblock; kb; kb = kb->next) + { + tty_fprintf (fp, " used for ...: %s\n", + kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP" : + kb->protocol == GNUPG_PROTOCOL_CMS? "X.509" : "?"); + pubkey = kb->keys; + /* If this is not the primary key print the primary key's + * fingerprint or a reference to it. */ + if (kb->protocol == GNUPG_PROTOCOL_OPENPGP) + { + tty_fprintf (fp, " main key .:"); + for (ki=firstkinfo; ki; ki = ki->next) + if (pubkey->grip_valid + && !memcmp (ki->grip, pubkey->grip, KEYGRIP_LEN)) + break; + if (ki) + { + /* Fixme: Replace mapping by a table lookup. */ + if (!memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN)) + s = "this"; + else if (!strcmp (ki->keyref, "OPENPGP.1")) + s = "Signature key"; + else if (!strcmp (ki->keyref, "OPENPGP.2")) + s = "Encryption key"; + else if (!strcmp (ki->keyref, "OPENPGP.3")) + s = "Authentication key"; + else + s = NULL; + if (s) + tty_fprintf (fp, " <%s>\n", s); + else + tty_fprintf (fp, " <Key %s>\n", ki->keyref); + } + else + print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen); + } + for (uid = kb->uids; uid; uid = uid->next) + { + print_string (fp, " user id ..: ", uid->value); + } + + } + } + else + { + tty_fprintf (fp, " [none]\n"); + if (label_keyref) + tty_fprintf (fp, " keyref .....: %s\n", label_keyref); + } + + leave: + release_keyblock (keyblock); +} + + +/* List all keyinfo in INFO using the list of LABELS. */ +static void +list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp) +{ + key_info_t kinfo; + int idx, i; + + /* Print the keyinfo. We first print those we known and then all + * remaining item. */ + for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) + kinfo->xflag = 0; + if (labels) + { + for (idx=0; labels[idx].label; idx++) + { + tty_fprintf (fp, "%s", labels[idx].label); + kinfo = find_kinfo (info, labels[idx].keyref); + list_one_kinfo (info->kinfo, kinfo, labels[idx].keyref, fp); + if (kinfo) + kinfo->xflag = 1; + } + } + for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) + { + if (kinfo->xflag) + continue; + tty_fprintf (fp, "Key %s ", kinfo->keyref); + for (i=5+strlen (kinfo->keyref); i < 18; i++) + tty_fprintf (fp, "."); + tty_fprintf (fp, ":"); + list_one_kinfo (info->kinfo, kinfo, NULL, fp); + } +} + + +/* List OpenPGP card specific data. */ +static void +list_openpgp (card_info_t info, estream_t fp) +{ + static struct keyinfolabel_s keyinfolabels[] = { + { "Signature key ....:", "OPENPGP.1" }, + { "Encryption key....:", "OPENPGP.2" }, + { "Authentication key:", "OPENPGP.3" }, + { NULL, NULL } + }; + int i; + + if (!info->serialno + || strncmp (info->serialno, "D27600012401", 12) + || strlen (info->serialno) != 32 ) + { + tty_fprintf (fp, "invalid OpenPGP card\n"); + return; + } + + tty_fprintf (fp, "Manufacturer .....: %s\n", + get_manufacturer (xtoi_2(info->serialno+16)*256 + + xtoi_2 (info->serialno+18))); + tty_fprintf (fp, "Name of cardholder: "); + print_isoname (fp, info->disp_name); + + print_string (fp, "Language prefs ...: ", info->disp_lang); + tty_fprintf (fp, "Salutation .......: %s\n", + info->disp_sex == 1? _("Mr."): + info->disp_sex == 2? _("Mrs.") : ""); + print_string (fp, "URL of public key : ", info->pubkey_url); + print_string (fp, "Login data .......: ", info->login_data); + if (info->private_do[0]) + print_string (fp, "Private DO 1 .....: ", info->private_do[0]); + if (info->private_do[1]) + print_string (fp, "Private DO 2 .....: ", info->private_do[1]); + if (info->private_do[2]) + print_string (fp, "Private DO 3 .....: ", info->private_do[2]); + if (info->private_do[3]) + print_string (fp, "Private DO 4 .....: ", info->private_do[3]); + if (info->cafpr1len) + { + tty_fprintf (fp, "CA fingerprint %d .:", 1); + print_shax_fpr (fp, info->cafpr1, info->cafpr1len); + } + if (info->cafpr2len) + { + tty_fprintf (fp, "CA fingerprint %d .:", 2); + print_shax_fpr (fp, info->cafpr2, info->cafpr2len); + } + if (info->cafpr3len) + { + tty_fprintf (fp, "CA fingerprint %d .:", 3); + print_shax_fpr (fp, info->cafpr3, info->cafpr3len); + } + tty_fprintf (fp, "Signature PIN ....: %s\n", + info->chv1_cached? _("not forced"): _("forced")); + if (info->key_attr[0].algo) + { + tty_fprintf (fp, "Key attributes ...:"); + for (i=0; i < DIM (info->key_attr); i++) + if (info->key_attr[i].algo == PUBKEY_ALGO_RSA) + tty_fprintf (fp, " rsa%u", info->key_attr[i].nbits); + else if (info->key_attr[i].algo == PUBKEY_ALGO_ECDH + || info->key_attr[i].algo == PUBKEY_ALGO_ECDSA + || info->key_attr[i].algo == PUBKEY_ALGO_EDDSA) + { + const char *curve_for_print = "?"; + const char *oid; + + if (info->key_attr[i].curve + && (oid = openpgp_curve_to_oid (info->key_attr[i].curve, NULL))) + curve_for_print = openpgp_oid_to_curve (oid, 0); + tty_fprintf (fp, " %s", curve_for_print); + } + tty_fprintf (fp, "\n"); + } + tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", + info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]); + tty_fprintf (fp, "PIN retry counter : %d %d %d\n", + info->chvinfo[0], info->chvinfo[1], info->chvinfo[2]); + tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter); + if (info->extcap.kdf) + { + tty_fprintf (fp, "KDF setting ......: %s\n", + info->kdf_do_enabled ? "on" : "off"); + } + if (info->extcap.bt) + { + tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", + info->uif[0] ? "on" : "off", info->uif[1] ? "on" : "off", + info->uif[2] ? "on" : "off"); + } + + list_all_kinfo (info, keyinfolabels, fp); + +} + + +/* List PIV card specific data. */ +static void +list_piv (card_info_t info, estream_t fp) +{ + static struct keyinfolabel_s keyinfolabels[] = { + { "PIV authentication:", "PIV.9A" }, + { "Card authenticat. :", "PIV.9E" }, + { "Digital signature :", "PIV.9C" }, + { "Key management ...:", "PIV.9D" }, + { NULL, NULL } + }; + const char *s; + int i; + + if (info->chvusage[0] || info->chvusage[1]) + { + tty_fprintf (fp, "PIN usage policy .:"); + if ((info->chvusage[0] & 0x40)) + tty_fprintf (fp, " app-pin"); + if ((info->chvusage[0] & 0x20)) + tty_fprintf (fp, " global-pin"); + if ((info->chvusage[0] & 0x10)) + tty_fprintf (fp, " occ"); + if ((info->chvusage[0] & 0x08)) + tty_fprintf (fp, " vci"); + if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04)) + tty_fprintf (fp, " pairing"); + + if (info->chvusage[1] == 0x10) + tty_fprintf (fp, " primary:card"); + else if (info->chvusage[1] == 0x20) + tty_fprintf (fp, " primary:global"); + + tty_fprintf (fp, "\n"); + } + + tty_fprintf (fp, "PIN retry counter :"); + for (i=0; i < DIM (info->chvinfo); i++) + { + if (info->chvinfo[i] > 0) + tty_fprintf (fp, " %d", info->chvinfo[i]); + else + { + switch (info->chvinfo[i]) + { + case -1: s = "[error]"; break; + case -2: s = "-"; break; /* No such PIN or info not available. */ + case -3: s = "[blocked]"; break; + case -5: s = "[verified]"; break; + default: s = "[?]"; break; + } + tty_fprintf (fp, " %s", s); + } + } + tty_fprintf (fp, "\n"); + list_all_kinfo (info, keyinfolabels, fp); + +} + + + +static void +print_a_version (estream_t fp, const char *prefix, unsigned int value) +{ + unsigned int a, b, c, d; + a = ((value >> 24) & 0xff); + b = ((value >> 16) & 0xff); + c = ((value >> 8) & 0xff); + d = ((value ) & 0xff); + + if (a) + tty_fprintf (fp, "%s %u.%u.%u.%u\n", prefix, a, b, c, d); + else if (b) + tty_fprintf (fp, "%s %u.%u.%u\n", prefix, b, c, d); + else + tty_fprintf (fp, "%s %u.%u\n", prefix, c, d); +} + + +/* Print all available information about the current card. */ +static void +list_card (card_info_t info) +{ + estream_t fp = opt.interactive? NULL : es_stdout; + + tty_fprintf (fp, "Reader ...........: %s\n", + info->reader? info->reader : "[none]"); + if (info->cardtype) + tty_fprintf (fp, "Card type ........: %s\n", info->cardtype); + if (info->cardversion) + print_a_version (fp, "Card firmware ....:", info->cardversion); + tty_fprintf (fp, "Serial number ....: %s\n", + info->serialno? info->serialno : "[none]"); + tty_fprintf (fp, "Application type .: %s%s%s%s\n", + app_type_string (info->apptype), + info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"", + info->apptype == APP_TYPE_UNKNOWN && info->apptypestr + ? info->apptypestr:"", + info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":""); + if (info->appversion) + print_a_version (fp, "Version ..........:", info->appversion); + if (info->serialno && info->dispserialno + && strcmp (info->serialno, info->dispserialno)) + tty_fprintf (fp, "Displayed s/n ....: %s\n", info->dispserialno); + + switch (info->apptype) + { + case APP_TYPE_OPENPGP: list_openpgp (info, fp); break; + case APP_TYPE_PIV: list_piv (info, fp); break; + default: break; + } +} + + + +/* The VERIFY command. */ +static gpg_error_t +cmd_verify (card_info_t info, char *argstr) +{ + gpg_error_t err; + const char *pinref; + + if (!info) + return print_help ("verify [chvid]", 0); + + if (*argstr) + pinref = argstr; + else if (info->apptype == APP_TYPE_OPENPGP) + pinref = info->serialno; + else if (info->apptype == APP_TYPE_PIV) + pinref = "PIV.80"; + else + return gpg_error (GPG_ERR_MISSING_VALUE); + + err = scd_checkpin (pinref); + if (err) + log_error ("verify failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + return err; +} + + +static gpg_error_t +cmd_authenticate (card_info_t info, char *argstr) +{ + gpg_error_t err; + int opt_setkey; + int opt_raw; + char *string = NULL; + char *key = NULL; + size_t keylen; + + if (!info) + return print_help + ("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n" + "Perform a mutual autentication either by reading the key\n" + "from FILE or by taking it from the command line. Without\n" + "the option --raw the key is expected to be hex encoded.\n" + "To install a new administration key --setkey is used; this\n" + "requires a prior authentication with the old key.", + APP_TYPE_PIV, 0); + + if (info->apptype != APP_TYPE_PIV) + { + log_info ("Note: This is a PIV only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + opt_setkey = has_leading_option (argstr, "--setkey"); + opt_raw = has_leading_option (argstr, "--raw"); + argstr = skip_options (argstr); + + if (*argstr == '<') /* Read key from a file. */ + { + for (argstr++; spacep (argstr); argstr++) + ; + err = get_data_from_file (argstr, &string, NULL); + if (err) + goto leave; + } + + if (opt_raw) + { + key = string? string : xstrdup (argstr); + string = NULL; + keylen = strlen (key); + } + else + { + key = hex_to_buffer (string? string: argstr, &keylen); + if (!key) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen); + + leave: + if (key) + { + wipememory (key, keylen); + xfree (key); + } + xfree (string); + return err; +} + + +/* Helper for cmd_name to qyery a part of name. */ +static char * +ask_one_name (const char *prompt) +{ + char *name; + int i; + + for (;;) + { + name = tty_get (prompt); + trim_spaces (name); + tty_kill_prompt (); + if (!*name || *name == CONTROL_D) + { + if (*name == CONTROL_D) + tty_fprintf (NULL, "\n"); + xfree (name); + return NULL; + } + for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++) + ; + + /* The name must be in Latin-1 and not UTF-8 - lacking the code + * to ensure this we restrict it to ASCII. */ + if (name[i]) + tty_printf (_("Error: Only plain ASCII is currently allowed.\n")); + else if (strchr (name, '<')) + tty_printf (_("Error: The \"<\" character may not be used.\n")); + else if (strstr (name, " ")) + tty_printf (_("Error: Double spaces are not allowed.\n")); + else + return name; + xfree (name); + } +} + + +/* The NAME command. */ +static gpg_error_t +cmd_name (card_info_t info, const char *argstr) +{ + gpg_error_t err; + char *surname, *givenname; + char *isoname, *p; + + if (!info) + return print_help + ("name [--clear]\n\n" + "Set the name field of an OpenPGP card. With --clear the stored\n" + "name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + again: + if (!strcmp (argstr, "--clear")) + isoname = xstrdup (" "); /* No real way to clear; set to space instead. */ + else + { + surname = ask_one_name (_("Cardholder's surname: ")); + givenname = ask_one_name (_("Cardholder's given name: ")); + if (!surname || !givenname || (!*surname && !*givenname)) + { + xfree (surname); + xfree (givenname); + return gpg_error (GPG_ERR_CANCELED); + } + + isoname = xstrconcat (surname, "<<", givenname, NULL); + xfree (surname); + xfree (givenname); + for (p=isoname; *p; p++) + if (*p == ' ') + *p = '<'; + + if (strlen (isoname) > 39 ) + { + log_info (_("Error: Combined name too long " + "(limit is %d characters).\n"), 39); + xfree (isoname); + goto again; + } + } + + err = scd_setattr ("DISP-NAME", isoname, strlen (isoname)); + + xfree (isoname); + return err; +} + + +static gpg_error_t +cmd_url (card_info_t info, const char *argstr) +{ + gpg_error_t err; + char *url; + + if (!info) + return print_help + ("URL [--clear]\n\n" + "Set the URL data object. That data object can be used by\n" + "the FETCH command to retrieve the full public key. The\n" + "option --clear deletes the content of that data object.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + if (!strcmp (argstr, "--clear")) + url = xstrdup (" "); /* No real way to clear; set to space instead. */ + else + { + url = tty_get (_("URL to retrieve public key: ")); + trim_spaces (url); + tty_kill_prompt (); + if (!*url || *url == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + } + + err = scd_setattr ("PUBKEY-URL", url, strlen (url)); + + leave: + xfree (url); + return err; +} + + +/* Fetch the key from the URL given on the card or try to get it from + * the default keyserver. */ +static gpg_error_t +cmd_fetch (card_info_t info) +{ + gpg_error_t err; + key_info_t kinfo; + + if (!info) + return print_help + ("FETCH\n\n" + "Retrieve a key using the URL data object or if that is missing\n" + "using the fingerprint.", APP_TYPE_OPENPGP, 0); + + if (info->pubkey_url && *info->pubkey_url) + { + /* strlist_t sl = NULL; */ + + /* add_to_strlist (&sl, info.pubkey_url); */ + /* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */ + /* free_strlist (sl); */ + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ + } + else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen) + { + /* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */ + /* opt.keyserver, 0); */ + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ + } + else + err = gpg_error (GPG_ERR_NO_DATA); + + return err; +} + + +static gpg_error_t +cmd_login (card_info_t info, char *argstr) +{ + gpg_error_t err; + char *data; + size_t datalen; + + if (!info) + return print_help + ("LOGIN [--clear] [< FILE]\n\n" + "Set the login data object. If FILE is given the data is\n" + "is read from that file. This allows for binary data.\n" + "The option --clear deletes the login data.", + APP_TYPE_OPENPGP, 0); + + if (!strcmp (argstr, "--clear")) + { + data = xstrdup (" "); /* kludge. */ + datalen = 1; + } + else if (*argstr == '<') /* Read it from a file */ + { + for (argstr++; spacep (argstr); argstr++) + ; + err = get_data_from_file (argstr, &data, &datalen); + if (err) + goto leave; + } + else + { + data = tty_get (_("Login data (account name): ")); + trim_spaces (data); + tty_kill_prompt (); + if (!*data || *data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + datalen = strlen (data); + } + + err = scd_setattr ("LOGIN-DATA", data, datalen); + + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_lang (card_info_t info, const char *argstr) +{ + gpg_error_t err; + char *data, *p; + + if (!info) + return print_help + ("LANG [--clear]\n\n" + "Change the language info for the card. This info can be used\n" + "by applications for a personalized greeting. Up to 4 two-digit\n" + "language identifiers can be entered as a preference. The option\n" + "--clear removes all identifiers. GnuPG does not use this info.", + APP_TYPE_OPENPGP, 0); + + if (!strcmp (argstr, "--clear")) + data = xstrdup (" "); /* Note that we need two spaces here. */ + else + { + again: + data = tty_get (_("Language preferences: ")); + trim_spaces (data); + tty_kill_prompt (); + if (!*data || *data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + if (strlen (data) > 8 || (strlen (data) & 1)) + { + log_info (_("Error: invalid length of preference string.\n")); + xfree (data); + goto again; + } + + for (p=data; *p && *p >= 'a' && *p <= 'z'; p++) + ; + if (*p) + { + log_info (_("Error: invalid characters in preference string.\n")); + xfree (data); + goto again; + } + } + + err = scd_setattr ("DISP-LANG", data, strlen (data)); + + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_salut (card_info_t info, const char *argstr) +{ + gpg_error_t err; + char *data = NULL; + const char *str; + + if (!info) + return print_help + ("SALUT [--clear]\n\n" + "Change the salutation info for the card. This info can be used\n" + "by applications for a personalized greeting. The option --clear\n" + "removes this data object. GnuPG does not use this info.", + APP_TYPE_OPENPGP, 0); + + again: + if (!strcmp (argstr, "--clear")) + str = "9"; + else + { + data = tty_get (_("Salutation (M = Mr., F = Mrs., or space): ")); + trim_spaces (data); + tty_kill_prompt (); + if (*data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + if (!*data) + str = "9"; + else if ((*data == 'M' || *data == 'm') && !data[1]) + str = "1"; + else if ((*data == 'F' || *data == 'f') && !data[1]) + str = "2"; + else + { + tty_printf (_("Error: invalid response.\n")); + xfree (data); + goto again; + } + } + + err = scd_setattr ("DISP-SEX", str, 1); + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_cafpr (card_info_t info, char *argstr) +{ + gpg_error_t err; + char *data = NULL; + const char *s; + int i, c; + unsigned char fpr[32]; + int fprlen; + int fprno; + int opt_clear = 0; + + if (!info) + return print_help + ("CAFPR [--clear] N\n\n" + "Change the CA fingerprint number N. N must be in the\n" + "range 1 to 3. The option --clear clears the specified\n" + "CA fingerprint N or all of them if N is 0 or not given.", + APP_TYPE_OPENPGP, 0); + + + opt_clear = has_leading_option (argstr, "--clear"); + argstr = skip_options (argstr); + + if (digitp (argstr)) + { + fprno = atoi (argstr); + while (digitp (argstr)) + argstr++; + while (spacep (argstr)) + argstr++; + } + else + fprno = 0; + + if (opt_clear && !fprno) + ; /* Okay: clear all fprs. */ + else if (fprno < 1 || fprno > 3) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + again: + if (opt_clear) + { + memset (fpr, 0, 20); + fprlen = 20; + } + else + { + xfree (data); + data = tty_get (_("CA fingerprint: ")); + trim_spaces (data); + tty_kill_prompt (); + if (!*data || *data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + for (i=0, s=data; i < sizeof fpr && *s; ) + { + while (spacep(s)) + s++; + if (*s == ':') + s++; + while (spacep(s)) + s++; + c = hextobyte (s); + if (c == -1) + break; + fpr[i++] = c; + s += 2; + } + fprlen = i; + if ((fprlen != 20 && fprlen != 32) || *s) + { + log_error (_("Error: invalid formatted fingerprint.\n")); + goto again; + } + } + + if (!fprno) + { + log_assert (opt_clear); + err = scd_setattr ("CA-FPR-1", fpr, fprlen); + if (!err) + err = scd_setattr ("CA-FPR-2", fpr, fprlen); + if (!err) + err = scd_setattr ("CA-FPR-3", fpr, fprlen); + } + else + err = scd_setattr (fprno==1?"CA-FPR-1": + fprno==2?"CA-FPR-2": + fprno==3?"CA-FPR-3":"x", fpr, fprlen); + + leave: + xfree (data); + return err; +} + + +static gpg_error_t +cmd_privatedo (card_info_t info, char *argstr) +{ + gpg_error_t err; + int opt_clear; + char *do_name = NULL; + char *data = NULL; + size_t datalen; + int do_no; + + if (!info) + return print_help + ("PRIVATEDO [--clear] N [< FILE]\n\n" + "Change the private data object N. N must be in the\n" + "range 1 to 4. If FILE is given the data is is read\n" + "from that file. The option --clear clears the data.", + APP_TYPE_OPENPGP, 0); + + opt_clear = has_leading_option (argstr, "--clear"); + argstr = skip_options (argstr); + + if (digitp (argstr)) + { + do_no = atoi (argstr); + while (digitp (argstr)) + argstr++; + while (spacep (argstr)) + argstr++; + } + else + do_no = 0; + + if (do_no < 1 || do_no > 4) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + do_name = xasprintf ("PRIVATE-DO-%d", do_no); + + if (opt_clear) + { + data = xstrdup (" "); + datalen = 1; + } + else if (*argstr == '<') /* Read it from a file */ + { + for (argstr++; spacep (argstr); argstr++) + ; + err = get_data_from_file (argstr, &data, &datalen); + if (err) + goto leave; + } + else if (*argstr) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + else + { + data = tty_get (_("Private DO data: ")); + trim_spaces (data); + tty_kill_prompt (); + datalen = strlen (data); + if (!*data || *data == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + } + + err = scd_setattr (do_name, data, datalen); + + leave: + xfree (do_name); + xfree (data); + return err; +} + + +static gpg_error_t +cmd_writecert (card_info_t info, char *argstr) +{ + gpg_error_t err; + int opt_clear; + char *certref_buffer = NULL; + char *certref; + char *data = NULL; + size_t datalen; + + if (!info) + return print_help + ("WRITECERT [--clear] CERTREF < FILE\n\n" + "Write a certificate for key 3. Unless --clear is given\n" + "the file argument is mandatory. The option --clear removes\n" + "the certificate from the card.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + opt_clear = has_leading_option (argstr, "--clear"); + argstr = skip_options (argstr); + + certref = argstr; + if ((argstr = strchr (certref, ' '))) + { + *argstr++ = 0; + trim_spaces (certref); + trim_spaces (argstr); + } + else /* Let argstr point to an empty string. */ + argstr = certref + strlen (certref); + + if (info->apptype == APP_TYPE_OPENPGP) + { + if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3")) + { + err = gpg_error (GPG_ERR_INV_ID); + log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n"); + goto leave; + } + certref = certref_buffer = xstrdup ("OPENPGP.3"); + } + else /* Upcase the certref; prepend cardtype if needed. */ + { + if (!strchr (certref, '.')) + certref_buffer = xstrconcat (app_type_string (info->apptype), ".", + certref, NULL); + else + certref_buffer = xstrdup (certref); + ascii_strupr (certref_buffer); + certref = certref_buffer; + } + + if (opt_clear) + { + data = xstrdup (" "); + datalen = 1; + } + else if (*argstr == '<') /* Read it from a file */ + { + for (argstr++; spacep (argstr); argstr++) + ; + err = get_data_from_file (argstr, &data, &datalen); + if (err) + goto leave; + if (ascii_memistr (data, datalen, "-----BEGIN CERTIFICATE-----") + && ascii_memistr (data, datalen, "-----END CERTIFICATE-----") + && !memchr (data, 0, datalen) && !memchr (data, 1, datalen)) + { + struct b64state b64; + + err = b64dec_start (&b64, ""); + if (!err) + err = b64dec_proc (&b64, data, datalen, &datalen); + if (!err) + err = b64dec_finish (&b64); + if (err) + goto leave; + } + } + else + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + err = scd_writecert (certref, data, datalen); + + leave: + xfree (data); + xfree (certref_buffer); + return err; +} + + +static gpg_error_t +cmd_readcert (card_info_t info, char *argstr) +{ + gpg_error_t err; + char *certref_buffer = NULL; + char *certref; + void *data = NULL; + size_t datalen; + const char *fname; + + if (!info) + return print_help + ("READCERT CERTREF > FILE\n\n" + "Read the certificate for key CERTREF and store it in FILE.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + argstr = skip_options (argstr); + + certref = argstr; + if ((argstr = strchr (certref, ' '))) + { + *argstr++ = 0; + trim_spaces (certref); + trim_spaces (argstr); + } + else /* Let argstr point to an empty string. */ + argstr = certref + strlen (certref); + + if (info->apptype == APP_TYPE_OPENPGP) + { + if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3")) + { + err = gpg_error (GPG_ERR_INV_ID); + log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n"); + goto leave; + } + certref = certref_buffer = xstrdup ("OPENPGP.3"); + } + + if (*argstr == '>') /* Write it to a file */ + { + for (argstr++; spacep (argstr); argstr++) + ; + fname = argstr; + } + else + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + err = scd_readcert (certref, &data, &datalen); + if (err) + goto leave; + + err = put_data_to_file (fname, data, datalen); + + leave: + xfree (data); + xfree (certref_buffer); + return err; +} + + +static gpg_error_t +cmd_writekey (card_info_t info, char *argstr) +{ + gpg_error_t err; + int opt_force; + char *argv[2]; + int argc; + char *keyref_buffer = NULL; + char *keyref; + char *keygrip; + + if (!info) + return print_help + ("WRITEKEY [--force] KEYREF KEYGRIP\n\n" + "Write a private key object identified by KEYGRIP to slot KEYREF.\n" + "Use --force to overwrite an existing key.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + opt_force = has_leading_option (argstr, "--force"); + argstr = skip_options (argstr); + + argc = split_fields (argstr, argv, DIM (argv)); + if (argc < 2) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + /* Upcase the keyref; prepend cardtype if needed. */ + keyref = argv[0]; + if (!strchr (keyref, '.')) + keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", + keyref, NULL); + else + keyref_buffer = xstrdup (keyref); + ascii_strupr (keyref_buffer); + keyref = keyref_buffer; + + /* Get the keygrip. */ + keygrip = argv[1]; + if (strlen (keygrip) != 40 + && !(keygrip[0] == '&' && strlen (keygrip+1) == 40)) + { + log_error (_("Not a valid keygrip (expecting 40 hex digits)\n")); + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + err = scd_writekey (keyref, opt_force, keygrip); + + leave: + xfree (keyref_buffer); + return err; +} + + +static gpg_error_t +cmd_forcesig (card_info_t info) +{ + gpg_error_t err; + int newstate; + + if (!info) + return print_help + ("FORCESIG\n\n" + "Toggle the forcesig flag of an OpenPGP card.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + newstate = !info->chv1_cached; + + err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1); + if (err) + goto leave; + + /* Read it back to be sure we have the right toggle state the next + * time. */ + err = scd_getattr ("CHV-STATUS", info); + + leave: + return err; +} + + + +/* Helper for cmd_generate_openpgp. Noe that either 0 or 1 is stored at + * FORCED_CHV1. */ +static gpg_error_t +check_pin_for_key_operation (card_info_t info, int *forced_chv1) +{ + gpg_error_t err = 0; + + *forced_chv1 = !info->chv1_cached; + if (*forced_chv1) + { /* Switch off the forced mode so that during key generation we + * don't get bothered with PIN queries for each self-signature. */ + err = scd_setattr ("CHV-STATUS-1", "\x01", 1); + if (err) + { + log_error ("error clearing forced signature PIN flag: %s\n", + gpg_strerror (err)); + *forced_chv1 = -1; /* Not changed. */ + goto leave; + } + } + + /* Check the PIN now, so that we won't get asked later for each + * binding signature. */ + err = scd_checkpin (info->serialno); + if (err) + log_error ("error checking the PIN: %s\n", gpg_strerror (err)); + + leave: + return err; +} + + +/* Helper for cmd_generate_openpgp. */ +static void +restore_forced_chv1 (int *forced_chv1) +{ + gpg_error_t err; + + /* Note the possible values stored at FORCED_CHV1: + * 0 - forcesig was not enabled. + * 1 - forcesig was enabled - enable it again. + * -1 - We have not changed anything. */ + if (*forced_chv1 == 1) + { /* Switch back to forced state. */ + err = scd_setattr ("CHV-STATUS-1", "", 1); + if (err) + log_error ("error setting forced signature PIN flag: %s\n", + gpg_strerror (err)); + *forced_chv1 = 0; + } +} + + +/* Implementation of cmd_generate for OpenPGP cards. */ +static gpg_error_t +generate_openpgp (card_info_t info) +{ + gpg_error_t err; + int forced_chv1 = -1; + int want_backup; + char *answer = NULL; + key_info_t kinfo1, kinfo2, kinfo3; + + if (info->extcap.ki) + { + xfree (answer); + answer = tty_get (_("Make off-card backup of encryption key? (Y/n) ")); + want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/); + tty_kill_prompt (); + if (*answer == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + } + else + want_backup = 0; + + kinfo1 = find_kinfo (info, "OPENPGP.1"); + kinfo2 = find_kinfo (info, "OPENPGP.2"); + kinfo3 = find_kinfo (info, "OPENPGP.3"); + + if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen)) + || (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen)) + || (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen)) + ) + { + tty_printf ("\n"); + log_info (_("Note: keys are already stored on the card!\n")); + tty_printf ("\n"); + answer = tty_get (_("Replace existing keys? (y/N) ")); + tty_kill_prompt (); + if (*answer == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + if (!answer_is_yes_no_default (answer, 0/*(default to No)*/)) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + } + + /* If no displayed name has been set, we assume that this is a fresh + * card and print a hint about the default PINs. */ + if (!info->disp_name || !*info->disp_name) + { + tty_printf ("\n"); + tty_printf (_("Please note that the factory settings of the PINs are\n" + " PIN = '%s' Admin PIN = '%s'\n" + "You should change them using the command --change-pin\n"), + OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT); + tty_printf ("\n"); + } + + err = check_pin_for_key_operation (info, &forced_chv1); + if (err) + goto leave; + + /* FIXME: We need to divert to a function which spwans gpg which + * will then create the key. This also requires new features in + * gpg. We might also first create the keys on the card and then + * tell gpg to use them to create the OpenPGP keyblock. */ + /* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */ + (void)want_backup; + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + leave: + restore_forced_chv1 (&forced_chv1); + xfree (answer); + return err; +} + + +/* Generic implementation of cmd_generate. */ +static gpg_error_t +generate_generic (card_info_t info, const char *keyref, int force, + const char *algo) +{ + gpg_error_t err; + + (void)info; + + err = scd_genkey (keyref, force, algo, NULL); + + return err; +} + + +static gpg_error_t +cmd_generate (card_info_t info, char *argstr) +{ + static char * const valid_algos[] = + { "rsa2048", "rsa3072", "rsa4096", + "nistp256", "nistp384", "nistp521", + "ed25519", "cv25519", + NULL + }; + gpg_error_t err; + int opt_force; + char *opt_algo = NULL; /* Malloced. */ + char *keyref_buffer = NULL; /* Malloced. */ + char *keyref; /* Points into argstr or keyref_buffer. */ + int i; + + if (!info) + return print_help + ("GENERATE [--force] [--algo=ALGO] KEYREF\n\n" + "Create a new key on a card. For OpenPGP cards are menu is used\n" + "and KEYREF is ignored. Use --force to overwrite an existing key.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + if (opt.interactive || opt.verbose) + log_info (_("%s card no. %s detected\n"), + app_type_string (info->apptype), + info->dispserialno? info->dispserialno : info->serialno); + + opt_force = has_leading_option (argstr, "--force"); + err = get_option_value (argstr, "--algo", &opt_algo); + if (err) + goto leave; + argstr = skip_options (argstr); + + keyref = argstr; + if ((argstr = strchr (keyref, ' '))) + { + *argstr++ = 0; + trim_spaces (keyref); + trim_spaces (argstr); + } + else /* Let argstr point to an empty string. */ + argstr = keyref + strlen (keyref); + + if (!*keyref) + keyref = NULL; + + if (*argstr) + { + /* Extra arguments found. */ + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + if (opt_algo) + { + for (i=0; valid_algos[i]; i++) + if (!strcmp (valid_algos[i], opt_algo)) + break; + if (!valid_algos[i]) + { + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + log_info ("Invalid algorithm '%s' given. Use one:\n", opt_algo); + for (i=0; valid_algos[i]; i++) + if (!(i%5)) + log_info (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); + else + log_printf (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); + log_info ("Note that the card may not support all of them.\n"); + goto leave; + } + } + + /* Upcase the keyref; if it misses the cardtype, prepend it. */ + if (keyref) + { + if (!strchr (keyref, '.')) + keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", + keyref, NULL); + else + keyref_buffer = xstrdup (keyref); + ascii_strupr (keyref_buffer); + keyref = keyref_buffer; + } + + /* Special checks. */ + if ((info->cardtype && !strcmp (info->cardtype, "yubikey")) + && info->cardversion >= 0x040200 && info->cardversion < 0x040305) + { + log_error ("On-chip key generation on this YubiKey has been blocked.\n"); + log_info ("Please see <https://yubi.co/ysa201701> for details\n"); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + /* Divert to dedicated functions. */ + if (info->apptype == APP_TYPE_OPENPGP) + { + if (opt_force || opt_algo || keyref) + log_info ("Note: Options are ignored for OpenPGP cards.\n"); + err = generate_openpgp (info); + } + else if (!keyref) + err = gpg_error (GPG_ERR_INV_ID); + else + err = generate_generic (info, keyref, opt_force, opt_algo); + + leave: + xfree (opt_algo); + xfree (keyref_buffer); + return err; +} + + + +/* Sub-menu to change a PIN. */ +static gpg_error_t +cmd_passwd (card_info_t info, char *argstr) +{ + gpg_error_t err; + char *answer = NULL; + const char *pinref; + + if (!info) + return print_help + ("PASSWD [PINREF]\n\n" + "Menu to change or unblock the PINs. Note that the\n" + "presented menu options depend on the type of card\n" + "and whether the admin mode is enabled. For OpenPGP\n" + "and PIV cards defaults for PINREF are available.", + 0); + + if (opt.interactive || opt.verbose) + log_info (_("%s card no. %s detected\n"), + app_type_string (info->apptype), + info->dispserialno? info->dispserialno : info->serialno); + + if (!*argstr && info->apptype == APP_TYPE_OPENPGP) + { + /* For an OpenPGP card we present the well known menu if no + * argument is given. */ + for (;;) + { + tty_printf ("\n"); + tty_printf ("1 - change PIN\n" + "2 - unblock and set new PIN\n" + "3 - change Admin PIN\n" + "4 - set the Reset Code\n" + "Q - quit\n"); + tty_printf ("\n"); + + err = 0; + xfree (answer); + answer = tty_get (_("Your selection? ")); + tty_kill_prompt (); + if (*answer == CONTROL_D) + break; /* Quit. */ + if (strlen (answer) != 1) + continue; + if (*answer == 'q' || *answer == 'Q') + break; /* Quit. */ + + if (*answer == '1') + { + /* Change PIN (same as the direct thing in non-admin mode). */ + err = scd_change_pin ("OPENPGP.1", 0); + if (err) + log_error ("Error changing the PIN: %s\n", gpg_strerror (err)); + else + log_info ("PIN changed.\n"); + } + else if (*answer == '2') + { + /* Unblock PIN by setting a new PIN. */ + err = scd_change_pin ("OPENPGP.1", 1); + if (err) + log_error ("Error unblocking the PIN: %s\n", gpg_strerror(err)); + else + log_info ("PIN unblocked and new PIN set.\n"); + } + else if (*answer == '3') + { + /* Change Admin PIN. */ + err = scd_change_pin ("OPENPGP.3", 0); + if (err) + log_error ("Error changing the PIN: %s\n", gpg_strerror (err)); + else + log_info ("PIN changed.\n"); + } + else if (*answer == '4') + { + /* Set a new Reset Code. */ + err = scd_change_pin ("OPENPGP.2", 1); + if (err) + log_error ("Error setting the Reset Code: %s\n", + gpg_strerror (err)); + else + log_info ("Reset Code set.\n"); + } + + } /*end for loop*/ + } + else + { + if (*argstr) + pinref = argstr; + else if (info->apptype == APP_TYPE_PIV) + pinref = "PIV.80"; + else + { + /* Note that we do not have a default value for OpenPGP + * because we want to be mostly compatible to "gpg + * --card-edit" and show a menu in that case (above). */ + err = gpg_error (GPG_ERR_MISSING_VALUE); + goto leave; + } + err = scd_change_pin (pinref, 0); + if (err) + goto leave; + + if (info->apptype == APP_TYPE_PIV + && !ascii_strcasecmp (pinref, "PIV.81")) + log_info ("PUK changed.\n"); + else + log_info ("PIN changed.\n"); + } + + leave: + xfree (answer); + return err; +} + + +static gpg_error_t +cmd_unblock (card_info_t info) +{ + gpg_error_t err = 0; + + if (!info) + return print_help + ("UNBLOCK\n\n" + "Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n" + "cards prior to version 2 can't use this; instead the PASSWD\n" + "command can be used to set a new PIN.", + 0); + + if (opt.interactive || opt.verbose) + log_info (_("%s card no. %s detected\n"), + app_type_string (info->apptype), + info->dispserialno? info->dispserialno : info->serialno); + + if (info->apptype == APP_TYPE_OPENPGP) + { + if (!info->is_v2) + { + log_error (_("This command is only available for version 2 cards\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + } + else if (!info->chvinfo[1]) + { + log_error (_("Reset Code not or not anymore available\n")); + err = gpg_error (GPG_ERR_PIN_BLOCKED); + } + else + { + err = scd_change_pin ("OPENPGP.2", 0); + if (!err) + log_info ("PIN changed.\n"); + } + } + else if (info->apptype == APP_TYPE_PIV) + { + /* Unblock the Application PIN. */ + err = scd_change_pin ("PIV.80", 1); + if (!err) + log_info ("PIN unblocked and changed.\n"); + } + else + { + log_info ("Unblocking not yet supported for '%s'\n", + app_type_string (info->apptype)); + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + + return err; +} + + +/* Note: On successful execution a redisplay should be scheduled. If + * this function fails the card may be in an unknown state. */ +static gpg_error_t +cmd_factoryreset (card_info_t info) +{ + gpg_error_t err; + char *answer = NULL; + int termstate = 0; + int any_apdu = 0; + int is_yubikey = 0; + int i; + + + if (!info) + return print_help + ("FACTORY-RESET\n\n" + "Do a complete reset of some OpenPGP and PIV cards. This\n" + "deletes all data and keys and resets the PINs to their default.\n" + "This is mainly used by developers with scratch cards. Don't\n" + "worry, you need to confirm before the command proceeds.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + /* We support the factory reset for most OpenPGP cards and Yubikeys + * with the PIV application. */ + if (info->apptype == APP_TYPE_OPENPGP) + ; + else if (info->apptype == APP_TYPE_PIV + && info->cardtype && !strcmp (info->cardtype, "yubikey")) + is_yubikey = 1; + else + + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + /* For an OpenPGP card the code below basically does the same what + * this gpg-connect-agent script does: + * + * scd reset + * scd serialno undefined + * scd apdu 00 A4 04 00 06 D2 76 00 01 24 01 + * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 + * scd apdu 00 e6 00 00 + * scd apdu 00 44 00 00 + * scd reset + * /echo Card has been reset to factory defaults + * + * For a PIV application on a Yubikey it merely issues the Yubikey + * specific resset command. + */ + + err = scd_learn (info); + if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE + && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) + termstate = 1; + else if (err) + { + log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err)); + goto leave; + } + + if (opt.interactive || opt.verbose) + log_info (_("%s card no. %s detected\n"), + app_type_string (info->apptype), + info->dispserialno? info->dispserialno : info->serialno); + + if (!termstate || is_yubikey) + { + if (!is_yubikey) + { + if (!(info->status_indicator == 3 || info->status_indicator == 5)) + { + /* Note: We won't see status-indicator 3 here because it + * is not possible to select a card application in + * termination state. */ + log_error (_("This command is not supported by this card\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + } + + tty_printf ("\n"); + log_info + (_("Note: This command destroys all keys stored on the card!\n")); + tty_printf ("\n"); + xfree (answer); + answer = tty_get (_("Continue? (y/N) ")); + tty_kill_prompt (); + trim_spaces (answer); + if (*answer == CONTROL_D + || !answer_is_yes_no_default (answer, 0/*(default to no)*/)) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + xfree (answer); + answer = tty_get (_("Really do a factory reset? (enter \"yes\") ")); + tty_kill_prompt (); + trim_spaces (answer); + if (strcmp (answer, "yes") && strcmp (answer,_("yes"))) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + + + if (is_yubikey) + { + /* The PIV application si already selected, we only need to + * send the special reset APDU after having blocked PIN and + * PUK. Note that blocking the PUK is done using the + * unblock PIN command. */ + any_apdu = 1; + for (i=0; i < 5; i++) + send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff, + NULL, NULL); + for (i=0; i < 5; i++) + send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "RESET RETRY COUNTER", 0xffff, NULL, NULL); + err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL); + if (err) + goto leave; + } + else /* OpenPGP card. */ + { + any_apdu = 1; + /* We need to select a card application before we can send APDUs + * to the card without scdaemon doing anything on its own. */ + err = send_apdu (NULL, "RESET", 0, NULL, NULL); + if (err) + goto leave; + err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL); + if (err) + goto leave; + /* Select the OpenPGP application. */ + err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0, + NULL, NULL); + if (err) + goto leave; + + /* Do some dummy verifies with wrong PINs to set the retry + * counter to zero. We can't easily use the card version 2.1 + * feature of presenting the admin PIN to allow the terminate + * command because there is no machinery in scdaemon to catch + * the verify command and ask for the PIN when the "APDU" + * command is used. + * Here, the length of dummy wrong PIN is 32-byte, also + * supporting authentication with KDF DO. */ + for (i=0; i < 4; i++) + send_apdu ("0020008120" + "40404040404040404040404040404040" + "40404040404040404040404040404040", "VERIFY", 0xffff, + NULL, NULL); + for (i=0; i < 4; i++) + send_apdu ("0020008320" + "40404040404040404040404040404040" + "40404040404040404040404040404040", "VERIFY", 0xffff, + NULL, NULL); + + /* Send terminate datafile command. */ + err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL); + if (err) + goto leave; + } + } + + if (!is_yubikey) + { + any_apdu = 1; + /* Send activate datafile command. This is used without + * confirmation if the card is already in termination state. */ + err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL); + if (err) + goto leave; + } + + /* Finally we reset the card reader once more. */ + err = send_apdu (NULL, "RESET", 0, NULL, NULL); + if (err) + goto leave; + + /* Then, connect the card again (answer used as a dummy). */ + xfree (answer); answer = NULL; + err = scd_serialno (&answer, NULL); + + leave: + if (err && any_apdu && !is_yubikey) + { + log_info ("Due to an error the card might be in an inconsistent state\n" + "You should run the LIST command to check this.\n"); + /* FIXME: We need a better solution in the case that the card is + * in a termination state, i.e. the card was removed before the + * activate was sent. The best solution I found with v2.1 + * Zeitcontrol card was to kill scdaemon and the issue this + * sequence with gpg-connect-agent: + * scd reset + * scd serialno undefined + * scd apdu 00A4040006D27600012401 (returns error) + * scd apdu 00440000 + * Then kill scdaemon again and issue: + * scd reset + * scd serialno openpgp + */ + } + xfree (answer); + return err; +} + + +/* Generate KDF data. This is a helper for cmd_kdfsetup. */ +static gpg_error_t +gen_kdf_data (unsigned char *data, int single_salt) +{ + gpg_error_t err; + const unsigned char h0[] = { 0x81, 0x01, 0x03, + 0x82, 0x01, 0x08, + 0x83, 0x04 }; + const unsigned char h1[] = { 0x84, 0x08 }; + const unsigned char h2[] = { 0x85, 0x08 }; + const unsigned char h3[] = { 0x86, 0x08 }; + const unsigned char h4[] = { 0x87, 0x20 }; + const unsigned char h5[] = { 0x88, 0x20 }; + unsigned char *p, *salt_user, *salt_admin; + unsigned char s2k_char; + unsigned int iterations; + unsigned char count_4byte[4]; + + p = data; + + s2k_char = encode_s2k_iterations (agent_get_s2k_count ()); + iterations = S2K_DECODE_COUNT (s2k_char); + count_4byte[0] = (iterations >> 24) & 0xff; + count_4byte[1] = (iterations >> 16) & 0xff; + count_4byte[2] = (iterations >> 8) & 0xff; + count_4byte[3] = (iterations & 0xff); + + memcpy (p, h0, sizeof h0); + p += sizeof h0; + memcpy (p, count_4byte, sizeof count_4byte); + p += sizeof count_4byte; + memcpy (p, h1, sizeof h1); + salt_user = (p += sizeof h1); + gcry_randomize (p, 8, GCRY_STRONG_RANDOM); + p += 8; + + if (single_salt) + salt_admin = salt_user; + else + { + memcpy (p, h2, sizeof h2); + p += sizeof h2; + gcry_randomize (p, 8, GCRY_STRONG_RANDOM); + p += 8; + memcpy (p, h3, sizeof h3); + salt_admin = (p += sizeof h3); + gcry_randomize (p, 8, GCRY_STRONG_RANDOM); + p += 8; + } + + memcpy (p, h4, sizeof h4); + p += sizeof h4; + err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT, + strlen (OPENPGP_USER_PIN_DEFAULT), + GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, + salt_user, 8, iterations, 32, p); + p += 32; + if (!err) + { + memcpy (p, h5, sizeof h5); + p += sizeof h5; + err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT, + strlen (OPENPGP_ADMIN_PIN_DEFAULT), + GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, + salt_admin, 8, iterations, 32, p); + } + + return err; +} + + +static gpg_error_t +cmd_kdfsetup (card_info_t info, char *argstr) +{ + gpg_error_t err; + unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX]; + int single = (*argstr != 0); + + if (!info) + return print_help + ("KDF-SETUP\n\n" + "Prepare the OpenPGP card KDF feature for this card.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + if (!info->extcap.kdf) + { + log_error (_("This command is not supported by this card\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + err = gen_kdf_data (kdf_data, single); + if (err) + goto leave; + + err = scd_setattr ("KDF", kdf_data, + single ? OPENPGP_KDF_DATA_LENGTH_MIN + /* */ : OPENPGP_KDF_DATA_LENGTH_MAX); + if (err) + goto leave; + + err = scd_getattr ("KDF", info); + + leave: + return err; +} + + + +static void +show_keysize_warning (void) +{ + static int shown; + + if (shown) + return; + shown = 1; + tty_printf + (_("Note: There is no guarantee that the card supports the requested\n" + " key type or size. If the key generation does not succeed,\n" + " please check the documentation of your card to see which\n" + " key types and sizes are supported.\n") + ); +} + + +/* Ask for the size of a card key. NBITS is the current size + * configured for the card. Returns 0 on success and stored the + * chosen key size at R_KEYSIZE; 0 is stored to indicate that the + * default size shall be used. */ +static gpg_error_t +ask_card_rsa_keysize (unsigned int nbits, unsigned int *r_keysize) +{ + unsigned int min_nbits = 1024; + unsigned int max_nbits = 4096; + char*answer; + unsigned int req_nbits; + + for (;;) + { + answer = tty_getf (_("What keysize do you want? (%u) "), nbits); + trim_spaces (answer); + tty_kill_prompt (); + if (*answer == CONTROL_D) + { + xfree (answer); + return gpg_error (GPG_ERR_CANCELED); + } + req_nbits = *answer? atoi (answer): nbits; + xfree (answer); + + if (req_nbits != nbits && (req_nbits % 32) ) + { + req_nbits = ((req_nbits + 31) / 32) * 32; + tty_printf (_("rounded up to %u bits\n"), req_nbits); + } + + if (req_nbits == nbits) + { + /* Use default. */ + *r_keysize = 0; + return 0; + } + + if (req_nbits < min_nbits || req_nbits > max_nbits) + { + tty_printf (_("%s keysizes must be in the range %u-%u\n"), + "RSA", min_nbits, max_nbits); + } + else + { + *r_keysize = req_nbits; + return 0; + } + } +} + + +/* Ask for the key attribute of a card key. CURRENT is the current + * attribute configured for the card. KEYNO is the number of the key + * used to select the prompt. Stores NULL at result to use the + * default attribute or stores the selected attribute structure at + * RESULT. On error an error code is returned. */ +static gpg_error_t +ask_card_keyattr (int keyno, const struct key_attr *current, + struct key_attr **result) +{ + gpg_error_t err; + struct key_attr *key_attr = NULL; + char *answer = NULL; + int selection; + + *result = NULL; + + key_attr = xcalloc (1, sizeof *key_attr); + + tty_printf (_("Changing card key attribute for: ")); + if (keyno == 0) + tty_printf (_("Signature key\n")); + else if (keyno == 1) + tty_printf (_("Encryption key\n")); + else + tty_printf (_("Authentication key\n")); + + tty_printf (_("Please select what kind of key you want:\n")); + tty_printf (_(" (%d) RSA\n"), 1 ); + tty_printf (_(" (%d) ECC\n"), 2 ); + + for (;;) + { + xfree (answer); + answer = tty_get (_("Your selection? ")); + trim_spaces (answer); + tty_kill_prompt (); + if (!*answer || *answer == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + selection = *answer? atoi (answer) : 0; + + if (selection == 1 || selection == 2) + break; + else + tty_printf (_("Invalid selection.\n")); + } + + + if (selection == 1) + { + unsigned int nbits, result_nbits; + + if (current->algo == PUBKEY_ALGO_RSA) + nbits = current->nbits; + else + nbits = 2048; + + err = ask_card_rsa_keysize (nbits, &result_nbits); + if (err) + goto leave; + if (result_nbits == 0) + { + if (current->algo == PUBKEY_ALGO_RSA) + { + xfree (key_attr); + key_attr = NULL; + } + else + result_nbits = nbits; + } + + if (key_attr) + { + key_attr->algo = PUBKEY_ALGO_RSA; + key_attr->nbits = result_nbits; + } + } + else if (selection == 2) + { + const char *curve; + /* const char *oid_str; */ + int algo; + + if (current->algo == PUBKEY_ALGO_RSA) + { + if (keyno == 1) /* Encryption key */ + algo = PUBKEY_ALGO_ECDH; + else /* Signature key or Authentication key */ + algo = PUBKEY_ALGO_ECDSA; + curve = NULL; + } + else + { + algo = current->algo; + curve = current->curve; + } + + (void)curve; + (void)algo; + err = GPG_ERR_NOT_IMPLEMENTED; + goto leave; + /* FIXME: We need to move the ask_cure code out to common or + * provide another sultion. */ + /* curve = ask_curve (&algo, NULL, curve); */ + /* if (curve) */ + /* { */ + /* key_attr->algo = algo; */ + /* oid_str = openpgp_curve_to_oid (curve, NULL); */ + /* key_attr->curve = openpgp_oid_to_curve (oid_str, 0); */ + /* } */ + /* else */ + /* { */ + /* xfree (key_attr); */ + /* key_attr = NULL; */ + /* } */ + } + else + { + err = gpg_error (GPG_ERR_BUG); + goto leave; + } + + /* Tell the user what we are going to do. */ + if (key_attr->algo == PUBKEY_ALGO_RSA) + { + tty_printf (_("The card will now be re-configured" + " to generate a key of %u bits\n"), key_attr->nbits); + } + else if (key_attr->algo == PUBKEY_ALGO_ECDH + || key_attr->algo == PUBKEY_ALGO_ECDSA + || key_attr->algo == PUBKEY_ALGO_EDDSA) + { + tty_printf (_("The card will now be re-configured" + " to generate a key of type: %s\n"), key_attr->curve); + } + show_keysize_warning (); + + *result = key_attr; + key_attr = NULL; + + leave: + xfree (key_attr); + xfree (answer); + return err; +} + + +/* Change the key attribute of key KEYNO (0..2) and show an error + * message if that fails. */ +static gpg_error_t +do_change_keyattr (int keyno, const struct key_attr *key_attr) +{ + gpg_error_t err = 0; + char args[100]; + + if (key_attr->algo == PUBKEY_ALGO_RSA) + snprintf (args, sizeof args, "--force %d 1 rsa%u", keyno+1, + key_attr->nbits); + else if (key_attr->algo == PUBKEY_ALGO_ECDH + || key_attr->algo == PUBKEY_ALGO_ECDSA + || key_attr->algo == PUBKEY_ALGO_EDDSA) + snprintf (args, sizeof args, "--force %d %d %s", + keyno+1, key_attr->algo, key_attr->curve); + else + { + /* FIXME: Above we use openpgp algo names but in the error + * message we use the gcrypt names. We should settle for a + * consistent solution. */ + log_error (_("public key algorithm %d (%s) is not supported\n"), + key_attr->algo, gcry_pk_algo_name (key_attr->algo)); + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + goto leave; + } + + err = scd_setattr ("KEY-ATTR", args, strlen (args)); + if (err) + log_error (_("error changing key attribute for key %d: %s\n"), + keyno+1, gpg_strerror (err)); + leave: + return err; +} + + +static gpg_error_t +cmd_keyattr (card_info_t info, char *argstr) +{ + gpg_error_t err = 0; + int keyno; + struct key_attr *key_attr = NULL; + + (void)argstr; + + if (!info) + return print_help + ("KEY-ATTR\n\n" + "Menu to change the key attributes of an OpenPGP card.", + APP_TYPE_OPENPGP, 0); + + if (info->apptype != APP_TYPE_OPENPGP) + { + log_info ("Note: This is an OpenPGP only command.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + if (!(info->is_v2 && info->extcap.aac)) + { + log_error (_("This command is not supported by this card\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + for (keyno = 0; keyno < DIM (info->key_attr); keyno++) + { + xfree (key_attr); + key_attr = NULL; + err = ask_card_keyattr (keyno, &info->key_attr[keyno], &key_attr); + if (err) + goto leave; + + err = do_change_keyattr (keyno, key_attr); + if (err) + { + /* Error: Better read the default key attribute again. */ + log_debug ("FIXME\n"); + /* Ask again for this key. */ + keyno--; + } + } + + leave: + xfree (key_attr); + return err; +} + + +static gpg_error_t +cmd_uif (card_info_t info, char *argstr) +{ + gpg_error_t err; + int keyno; + + if (!info) + return print_help + ("UIF N [on|off|permanent]\n\n" + "Change the User Interaction Flag. N must in the range 1 to 3.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); + + argstr = skip_options (argstr); + + if (digitp (argstr)) + { + keyno = atoi (argstr); + while (digitp (argstr)) + argstr++; + while (spacep (argstr)) + argstr++; + } + else + keyno = 0; + + if (keyno < 1 || keyno > 3) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + + err = GPG_ERR_NOT_IMPLEMENTED; + + leave: + return err; +} + + +static gpg_error_t +cmd_yubikey (card_info_t info, char *argstr) +{ + gpg_error_t err, err2; + estream_t fp = opt.interactive? NULL : es_stdout; + char *words[20]; + int nwords; + + if (!info) + return print_help + ("YUBIKEY <cmd> args\n\n" + "Various commands pertaining to Yubikey tokens with <cmd> being:\n" + "\n" + " LIST \n" + "\n" + "List supported and enabled applications.\n" + "\n" + " ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" + " DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" + "\n" + "Enable or disable the specified or all applications on the\n" + "given interface.", + 0); + + argstr = skip_options (argstr); + + if (!info->cardtype || strcmp (info->cardtype, "yubikey")) + { + log_info ("This command can only be used with Yubikeys.\n"); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + nwords = split_fields (argstr, words, DIM (words)); + if (nwords < 1) + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + + + /* Note that we always do a learn to get a chance to the card back + * into a usable state. */ + err = yubikey_commands (fp, nwords, words); + err2 = scd_learn (info); + if (err2) + log_error ("Error re-reading card: %s\n", gpg_strerror (err)); + + leave: + return err; +} + + + +/* Data used by the command parser. This needs to be outside of the + * function scope to allow readline based command completion. */ +enum cmdids + { + cmdNOP = 0, + cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, + cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, + cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, + cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP, + cmdKEYATTR, cmdUIF, cmdAUTH, cmdYUBIKEY, + cmdINVCMD + }; + +static struct +{ + const char *name; + enum cmdids id; + const char *desc; +} cmds[] = { + { "quit" , cmdQUIT, N_("quit this menu")}, + { "q" , cmdQUIT, NULL }, + { "help" , cmdHELP, N_("show this help")}, + { "?" , cmdHELP, NULL }, + { "list" , cmdLIST, N_("list all available data")}, + { "l" , cmdLIST, NULL }, + { "name" , cmdNAME, N_("change card holder's name")}, + { "url" , cmdURL, N_("change URL to retrieve key")}, + { "fetch" , cmdFETCH, N_("fetch the key specified in the card URL")}, + { "login" , cmdLOGIN, N_("change the login name")}, + { "lang" , cmdLANG, N_("change the language preferences")}, + { "salutation",cmdSALUT, N_("change card holder's salutation")}, + { "salut" , cmdSALUT, NULL }, + { "cafpr" , cmdCAFPR , N_("change a CA fingerprint")}, + { "forcesig", cmdFORCESIG, N_("toggle the signature force PIN flag")}, + { "generate", cmdGENERATE, N_("generate new keys")}, + { "passwd" , cmdPASSWD, N_("menu to change or unblock the PIN")}, + { "verify" , cmdVERIFY, N_("verify the PIN and list all data")}, + { "unblock" , cmdUNBLOCK, N_("unblock the PIN using a Reset Code")}, + { "authenticate",cmdAUTH, N_("authenticate to the card")}, + { "auth" , cmdAUTH, NULL }, + { "reset" , cmdRESET, N_("send a reset to the card daemon")}, + { "factory-reset",cmdFACTRST, N_("destroy all keys and data")}, + { "kdf-setup", cmdKDFSETUP, N_("setup KDF for PIN authentication")}, + { "key-attr", cmdKEYATTR, N_("change the key attribute")}, + { "uif", cmdUIF, N_("change the User Interaction Flag")}, + { "privatedo", cmdPRIVATEDO, N_("change a private data object")}, + { "readcert", cmdREADCERT, N_("read a certificate from a data object")}, + { "writecert", cmdWRITECERT, N_("store a certificate to a data object")}, + { "writekey", cmdWRITEKEY, N_("store a private key to a data object")}, + { "yubikey", cmdYUBIKEY, N_("Yubikey management commands")}, + { NULL, cmdINVCMD, NULL } +}; + + +/* The command line command dispatcher. */ +static gpg_error_t +dispatch_command (card_info_t info, const char *orig_command) +{ + gpg_error_t err = 0; + enum cmdids cmd; /* The command. */ + char *command; /* A malloced copy of ORIG_COMMAND. */ + char *argstr; /* The argument as a string. */ + int i; + int ignore_error; + + if ((ignore_error = *orig_command == '-')) + orig_command++; + command = xstrdup (orig_command); + argstr = NULL; + if ((argstr = strchr (command, ' '))) + { + *argstr++ = 0; + trim_spaces (command); + trim_spaces (argstr); + } + + for (i=0; cmds[i].name; i++ ) + if (!ascii_strcasecmp (command, cmds[i].name )) + break; + cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */ + + /* Make sure we have valid strings for the args. They are allowed + * to be modified and must thus point to a buffer. */ + if (!argstr) + argstr = command + strlen (command); + + /* For most commands we need to make sure that we have a card. */ + if (!info) + ; /* Help mode */ + else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP + || cmd == cmdINVCMD) + && !info->initialized) + { + err = scd_learn (info); + if (err) + { + log_error ("Error reading card: %s\n", gpg_strerror (err)); + goto leave; + } + } + + switch (cmd) + { + case cmdNOP: + if (!info) + print_help ("NOP\n\n" + "Dummy command.", 0); + break; + + case cmdQUIT: + if (!info) + print_help ("QUIT\n\n" + "Stop processing.", 0); + else + { + err = gpg_error (GPG_ERR_EOF); + goto leave; + } + break; + + case cmdHELP: + if (!info) + print_help ("HELP [command]\n\n" + "Show all commands. With an argument show help\n" + "for that command.", 0); + else if (*argstr) + dispatch_command (NULL, argstr); + else + { + es_printf + ("List of commands (\"help <command>\" for details):\n"); + for (i=0; cmds[i].name; i++ ) + if(cmds[i].desc) + es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); + es_printf ("Prefix a command with a dash to ignore its error.\n"); + } + break; + + case cmdLIST: + if (!info) + print_help ("LIST\n\n" + "Show content of the card.", 0); + else + { + err = scd_learn (info); + if (err) + log_error ("Error reading card: %s\n", gpg_strerror (err)); + else + list_card (info); + } + break; + + case cmdRESET: + if (!info) + print_help ("RESET\n\n" + "Send a RESET to the card daemon.", 0); + else + { + flush_keyblock_cache (); + err = scd_apdu (NULL, NULL, NULL, NULL); + } + break; + + case cmdVERIFY: err = cmd_verify (info, argstr); break; + case cmdAUTH: err = cmd_authenticate (info, argstr); break; + case cmdNAME: err = cmd_name (info, argstr); break; + case cmdURL: err = cmd_url (info, argstr); break; + case cmdFETCH: err = cmd_fetch (info); break; + case cmdLOGIN: err = cmd_login (info, argstr); break; + case cmdLANG: err = cmd_lang (info, argstr); break; + case cmdSALUT: err = cmd_salut (info, argstr); break; + case cmdCAFPR: err = cmd_cafpr (info, argstr); break; + case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; + case cmdWRITECERT: err = cmd_writecert (info, argstr); break; + case cmdREADCERT: err = cmd_readcert (info, argstr); break; + case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; + case cmdFORCESIG: err = cmd_forcesig (info); break; + case cmdGENERATE: err = cmd_generate (info, argstr); break; + case cmdPASSWD: err = cmd_passwd (info, argstr); break; + case cmdUNBLOCK: err = cmd_unblock (info); break; + case cmdFACTRST: err = cmd_factoryreset (info); break; + case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; + case cmdKEYATTR: err = cmd_keyattr (info, argstr); break; + case cmdUIF: err = cmd_uif (info, argstr); break; + case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; + + case cmdINVCMD: + default: + log_error (_("Invalid command (try \"help\")\n")); + break; + } /* End command switch. */ + + + leave: + /* Return GPG_ERR_EOF only if its origin was "quit". */ + es_fflush (es_stdout); + if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT) + err = gpg_error (GPG_ERR_GENERAL); + if (err && gpg_err_code (err) != GPG_ERR_EOF) + { + if (ignore_error) + { + log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err)); + err = 0; + } + else + log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err)); + } + xfree (command); + + return err; +} + + +/* The interactive main loop. */ +static void +interactive_loop (void) +{ + gpg_error_t err; + char *answer = NULL; /* The input line. */ + enum cmdids cmd = cmdNOP; /* The command. */ + char *argstr; /* The argument as a string. */ + int redisplay = 1; /* Whether to redisplay the main info. */ + char *help_arg = NULL; /* Argument of the HELP command. */ + struct card_info_s info_buffer = { 0 }; + card_info_t info = &info_buffer; + char *p; + int i; + + /* In the interactive mode we do not want to print the program prefix. */ + log_set_prefix (NULL, 0); + + for (;;) + { + if (help_arg) + { + /* Clear info to indicate helpmode */ + info = NULL; + } + else if (!info) + { + /* Get out of help. */ + info = &info_buffer; + help_arg = NULL; + redisplay = 0; + } + else if (redisplay) + { + err = scd_learn (info); + if (err) + { + log_error ("Error reading card: %s\n", gpg_strerror (err)); + } + else + { + list_card (info); + tty_printf("\n"); + redisplay = 0; + } + } + + if (!info) + { + /* Copy the pending help arg into our answer. Noe that + * help_arg points into answer. */ + p = xstrdup (help_arg); + help_arg = NULL; + xfree (answer); + answer = p; + } + else + { + do + { + xfree (answer); + tty_enable_completion (command_completion); + answer = tty_get (_("gpg/card> ")); + tty_kill_prompt(); + tty_disable_completion (); + trim_spaces(answer); + } + while ( *answer == '#' ); + } + + argstr = NULL; + if (!*answer) + cmd = cmdLIST; /* We default to the list command */ + else if (*answer == CONTROL_D) + cmd = cmdQUIT; + else + { + if ((argstr = strchr (answer,' '))) + { + *argstr++ = 0; + trim_spaces (answer); + trim_spaces (argstr); + } + + for (i=0; cmds[i].name; i++ ) + if (!ascii_strcasecmp (answer, cmds[i].name )) + break; + + cmd = cmds[i].id; + } + + /* Make sure we have valid strings for the args. They are + * allowed to be modified and must thus point to a buffer. */ + if (!argstr) + argstr = answer + strlen (answer); + + if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP + || cmd == cmdINVCMD)) + { + /* If redisplay is set we know that there was an error reading + * the card. In this case we force a LIST command to retry. */ + if (!info) + ; /* In help mode. */ + else if (redisplay) + { + cmd = cmdLIST; + } + else if (!info->serialno) + { + /* Without a serial number most commands won't work. + * Catch it here. */ + tty_printf ("\n"); + tty_printf ("Serial number missing\n"); + continue; + } + } + + err = 0; + switch (cmd) + { + case cmdNOP: + if (!info) + print_help ("NOP\n\n" + "Dummy command.", 0); + break; + + case cmdQUIT: + if (!info) + print_help ("QUIT\n\n" + "Leave this tool.", 0); + else + { + tty_printf ("\n"); + goto leave; + } + break; + + case cmdHELP: + if (!info) + print_help ("HELP [command]\n\n" + "Show all commands. With an argument show help\n" + "for that command.", 0); + else if (*argstr) + help_arg = argstr; /* Trigger help for a command. */ + else + { + tty_printf + ("List of commands (\"help <command>\" for details):\n"); + for (i=0; cmds[i].name; i++ ) + if(cmds[i].desc) + tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); + } + break; + + case cmdLIST: + if (!info) + print_help ("LIST\n\n" + "Show content of the card.", 0); + else + { + /* Actual work is done by the redisplay code block. */ + redisplay = 1; + } + break; + + case cmdRESET: + if (!info) + print_help ("RESET\n\n" + "Send a RESET to the card daemon.", 0); + else + { + flush_keyblock_cache (); + err = scd_apdu (NULL, NULL, NULL, NULL); + } + break; + + case cmdVERIFY: + err = cmd_verify (info, argstr); + if (!err) + redisplay = 1; + break; + case cmdAUTH: err = cmd_authenticate (info, argstr); break; + case cmdNAME: err = cmd_name (info, argstr); break; + case cmdURL: err = cmd_url (info, argstr); break; + case cmdFETCH: err = cmd_fetch (info); break; + case cmdLOGIN: err = cmd_login (info, argstr); break; + case cmdLANG: err = cmd_lang (info, argstr); break; + case cmdSALUT: err = cmd_salut (info, argstr); break; + case cmdCAFPR: err = cmd_cafpr (info, argstr); break; + case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; + case cmdWRITECERT: err = cmd_writecert (info, argstr); break; + case cmdREADCERT: err = cmd_readcert (info, argstr); break; + case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; + case cmdFORCESIG: err = cmd_forcesig (info); break; + case cmdGENERATE: err = cmd_generate (info, argstr); break; + case cmdPASSWD: err = cmd_passwd (info, argstr); break; + case cmdUNBLOCK: err = cmd_unblock (info); break; + case cmdFACTRST: + err = cmd_factoryreset (info); + if (!err) + redisplay = 1; + break; + case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; + case cmdKEYATTR: err = cmd_keyattr (info, argstr); break; + case cmdUIF: err = cmd_uif (info, argstr); break; + case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; + + case cmdINVCMD: + default: + tty_printf ("\n"); + tty_printf (_("Invalid command (try \"help\")\n")); + break; + } /* End command switch. */ + + if (gpg_err_code (err) == GPG_ERR_CANCELED) + tty_fprintf (NULL, "\n"); + else if (err) + { + const char *s = "?"; + for (i=0; cmds[i].name; i++ ) + if (cmd == cmds[i].id) + { + s = cmds[i].name; + break; + } + log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err)); + } + + } /* End of main menu loop. */ + + leave: + release_card_info (info); + xfree (answer); +} + +#ifdef HAVE_LIBREADLINE +/* Helper function for readline's command completion. */ +static char * +command_generator (const char *text, int state) +{ + static int list_index, len; + const char *name; + + /* If this is a new word to complete, initialize now. This includes + * saving the length of TEXT for efficiency, and initializing the + index variable to 0. */ + if (!state) + { + list_index = 0; + len = strlen(text); + } + + /* Return the next partial match */ + while ((name = cmds[list_index].name)) + { + /* Only complete commands that have help text. */ + if (cmds[list_index++].desc && !strncmp (name, text, len)) + return strdup(name); + } + + return NULL; +} + +/* Second helper function for readline's command completion. */ +static char ** +command_completion (const char *text, int start, int end) +{ + (void)end; + + /* If we are at the start of a line, we try and command-complete. + * If not, just do nothing for now. The support for help completion + * needs to be more smarter. */ + if (!start) + return rl_completion_matches (text, command_generator); + else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5)) + return rl_completion_matches (text, command_generator); + + rl_attempted_completion_over = 1; + + return NULL; +} +#endif /*HAVE_LIBREADLINE*/ diff --git a/tools/gpg-card.h b/tools/gpg-card.h new file mode 100644 index 000000000..099ea5448 --- /dev/null +++ b/tools/gpg-card.h @@ -0,0 +1,230 @@ +/* gpg-card.h - Common definitions for the gpg-card-tool + * Copyright (C) 2019 g10 Code GmbH + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef GNUPG_GPG_CARD_H +#define GNUPG_GPG_CARD_H + +#include "../common/session-env.h" + + +/* We keep all global options in the structure OPT. */ +struct +{ + int interactive; + int verbose; + unsigned int debug; + int quiet; + int with_colons; + const char *gpg_program; + const char *gpgsm_program; + const char *agent_program; + int autostart; + + /* Options passed to the gpg-agent: */ + session_env_t session_env; + char *lc_ctype; + char *lc_messages; + +} opt; + +/* Debug values and macros. */ +#define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ +#define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */ + +#define DBG_IPC (opt.debug & DBG_IPC_VALUE) +#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE) + +/* The maximum length of a binary fingerprint. */ +#define MAX_FINGERPRINT_LEN 32 + + +/* + * Data structures to store keyblocks (aka certificates). + */ +struct pubkey_s +{ + struct pubkey_s *next; /* The next key. */ + unsigned char grip[KEYGRIP_LEN]; + unsigned char fpr[MAX_FINGERPRINT_LEN]; + unsigned char fprlen; /* The used length of a FPR. */ + unsigned int grip_valid:1;/* The grip is valid. */ + unsigned int requested: 1;/* This is the requested grip. */ +}; +typedef struct pubkey_s *pubkey_t; + +struct userid_s +{ + struct userid_s *next; + char *value; /* Malloced. */ +}; +typedef struct userid_s *userid_t; + +struct keyblock_s +{ + struct keyblock_s *next; /* Allow to link several keyblocks. */ + int protocol; /* GPGME_PROTOCOL_OPENPGP or _CMS. */ + pubkey_t keys; /* The key. For OpenPGP primary + list of subkeys. */ + userid_t uids; /* The list of user ids. */ +}; +typedef struct keyblock_s *keyblock_t; + + + +/* Enumeration of the known card application types. */ +typedef enum + { + APP_TYPE_NONE, /* Not yet known or for direct APDU sending. */ + APP_TYPE_OPENPGP, + APP_TYPE_NKS, + APP_TYPE_DINSIG, + APP_TYPE_P15, + APP_TYPE_GELDKARTE, + APP_TYPE_SC_HSM, + APP_TYPE_PIV, + APP_TYPE_UNKNOWN /* Unknown by this tool. */ + } app_type_t; + + +/* OpenPGP card key attributes. */ +struct key_attr +{ + int algo; /* Algorithm identifier. */ + union { + unsigned int nbits; /* Supported keysize. */ + const char *curve; /* Name of curve. */ + }; +}; + +/* An object to store information pertaining to a key pair as stored + * on a card. This is commonly used as a linked list with all keys + * known for the current card. */ +struct key_info_s +{ + struct key_info_s *next; + + unsigned char grip[20];/* The keygrip. */ + + unsigned char xflag; /* Temporary flag to help processing a list. */ + + /* The three next items are mostly useful for OpenPGP cards. */ + unsigned char fprlen; /* Use length of the next item. */ + unsigned char fpr[32]; /* The binary fingerprint of length FPRLEN. */ + u32 created; /* The time the key was created. */ + unsigned int usage; /* Usage flags. (GCRY_PK_USAGE_*) */ + char keyref[1]; /* String with the keyref (e.g. OPENPGP.1). */ +}; +typedef struct key_info_s *key_info_t; + + +/* + * The object used to store information about a card. + */ +struct card_info_s +{ + int initialized; /* True if a learn command was successful. */ + int error; /* private. */ + char *reader; /* Reader information. */ + char *cardtype; /* NULL or type of the card. */ + unsigned int cardversion; /* Firmware version of the card. */ + char *apptypestr; /* Malloced application type string. */ + app_type_t apptype;/* Translated from APPTYPESTR. */ + unsigned int appversion; /* Version of the application. */ + char *serialno; /* malloced hex string. */ + char *dispserialno;/* malloced string. */ + char *disp_name; /* malloced. */ + char *disp_lang; /* malloced. */ + int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */ + char *pubkey_url; /* malloced. */ + char *login_data; /* malloced. */ + char *private_do[4]; /* malloced. */ + char cafpr1len; /* Length of the CA-fingerprint or 0 if invalid. */ + char cafpr2len; + char cafpr3len; + char cafpr1[20]; + char cafpr2[20]; + char cafpr3[20]; + key_info_t kinfo; /* Linked list with all keypair related data. */ + unsigned long sig_counter; + int chv1_cached; /* For openpgp this is true if a PIN is not + required for each signing. Note that the + gpg-agent might cache it anyway. */ + int is_v2; /* True if this is a v2 openpgp card. */ + int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ + int chvinfo[3]; /* Allowed retries for the CHV; 0 = blocked. */ + unsigned char chvusage[2]; /* Data object 5F2F */ + struct key_attr key_attr[3]; /* OpenPGP card key attributes. */ + struct { + unsigned int ki:1; /* Key import available. */ + unsigned int aac:1; /* Algorithm attributes are changeable. */ + unsigned int kdf:1; /* KDF object to support PIN hashing available. */ + unsigned int bt:1; /* Button for confirmation available. */ + } extcap; + unsigned int status_indicator; + int kdf_do_enabled; /* True if card has a KDF object. */ + int uif[3]; /* True if User Interaction Flag is on. */ +}; +typedef struct card_info_s *card_info_t; + + +/*-- card-keys.c --*/ +void release_keyblock (keyblock_t keyblock); +void flush_keyblock_cache (void); +gpg_error_t get_matching_keys (const unsigned char *keygrip, int protocol, + keyblock_t *r_keyblock); +gpg_error_t test_get_matching_keys (const char *hexgrip); + + +/*-- card-misc.c --*/ +key_info_t find_kinfo (card_info_t info, const char *keyref); +void *hex_to_buffer (const char *string, size_t *r_length); +gpg_error_t send_apdu (const char *hexapdu, const char *desc, + unsigned int ignore, + unsigned char **r_data, size_t *r_datalen); + +/*-- card-call-scd.c --*/ +void release_card_info (card_info_t info); +const char *app_type_string (app_type_t app_type); + +gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw, + unsigned char **r_data, size_t *r_datalen); +gpg_error_t scd_learn (card_info_t info); +gpg_error_t scd_getattr (const char *name, struct card_info_s *info); +gpg_error_t scd_setattr (const char *name, + const unsigned char *value, size_t valuelen); +gpg_error_t scd_writecert (const char *certidstr, + const unsigned char *certdata, size_t certdatalen); +gpg_error_t scd_writekey (const char *keyref, int force, const char *keygrip); +gpg_error_t scd_genkey (const char *keyref, int force, const char *algo, + u32 *createtime); +gpg_error_t scd_serialno (char **r_serialno, const char *demand); +gpg_error_t scd_readcert (const char *certidstr, + void **r_buf, size_t *r_buflen); +gpg_error_t scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result); +gpg_error_t scd_cardlist (strlist_t *result); +gpg_error_t scd_change_pin (const char *pinref, int reset_mode); +gpg_error_t scd_checkpin (const char *serialno); + +unsigned long agent_get_s2k_count (void); + +/*-- card-yubikey.c --*/ +gpg_error_t yubikey_commands (estream_t fp, int argc, char *argv[]); + + +#endif /*GNUPG_GPG_CARD_H*/ diff --git a/tools/gpg-check-pattern.c b/tools/gpg-check-pattern.c index 4db8f3706..dee5d5d47 100644 --- a/tools/gpg-check-pattern.c +++ b/tools/gpg-check-pattern.c @@ -91,7 +91,7 @@ static struct enum { PAT_NULL, /* Indicates end of the array. */ PAT_STRING, /* The pattern is a simple string. */ - PAT_REGEX /* The pattern is an extended regualr expression. */ + PAT_REGEX /* The pattern is an extended regular expression. */ }; diff --git a/tools/gpg-connect-agent-w32info.rc b/tools/gpg-connect-agent-w32info.rc index 4e7b19d35..8c6735918 100644 --- a/tools/gpg-connect-agent-w32info.rc +++ b/tools/gpg-connect-agent-w32info.rc @@ -1,4 +1,4 @@ -/* scdaemon-w32info.rc -*- c -*- +/* gpg-connect-agent-w32info.rc -*- c -*- * Copyright (C) 2013 g10 Code GmbH * * This file is free software; as a special exception the author gives diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c index 00482a32e..7eb7ffa3a 100644 --- a/tools/gpg-connect-agent.c +++ b/tools/gpg-connect-agent.c @@ -983,7 +983,7 @@ do_open (char *line) name, mode, strerror (errno)); return; } - fd = fileno (fp); + fd = dup (fileno (fp)); if (fd >= 0 && fd < DIM (open_fd_table)) { open_fd_table[fd].inuse = 1; @@ -1030,8 +1030,10 @@ do_open (char *line) else { log_error ("can't put fd %d into table\n", fd); - close (fd); + if (fd != -1) + close (fd); /* Table was full. */ } + fclose (fp); } diff --git a/tools/gpg-pair-tool.c b/tools/gpg-pair-tool.c new file mode 100644 index 000000000..347b29d24 --- /dev/null +++ b/tools/gpg-pair-tool.c @@ -0,0 +1,2020 @@ +/* gpg-pair-tool.c - The tool to run the pairing protocol. + * Copyright (C) 2018 g10 Code GmbH + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +/* Protocol: + * + * Initiator Responder + * | | + * | COMMIT | + * |-------------------->| + * | | + * | DHPART1 | + * |<--------------------| + * | | + * | DHPART2 | + * |-------------------->| + * | | + * | CONFIRM | + * |<--------------------| + * | | + * + * The initiator creates a keypar (PKi,SKi) and sends this COMMIT + * message to the responder: + * + * 7 byte Magic, value: "GPG-pa1" + * 1 byte MessageType, value 1 (COMMIT) + * 8 byte SessionId, value: 8 random bytes + * 1 byte Realm, value 1 + * 2 byte reserved, value 0 + * 5 byte ExpireTime, value: seconds since Epoch as an unsigned int. + * 32 byte Hash(PKi) + * + * The initiator also needs to locally store the sessionid, the realm, + * the expiration time, the keypair and a hash of the entire message + * sent. + * + * The responder checks that the received message has not expired and + * stores sessionid, realm, expiretime and the Hash(PKi). The + * Responder then creates and locally stores its own keypair (PKr,SKr) + * and sends the DHPART1 message back: + * + * 7 byte Magic, value: "GPG-pa1" + * 1 byte MessageType, value 2 (DHPART1) + * 8 byte SessionId from COMMIT message + * 32 byte PKr + * 32 byte Hash(Hash(COMMIT) || DHPART1[0..47]) + * + * Note that Hash(COMMIT) is the hash over the entire received COMMIT + * message. DHPART1[0..47] are the first 48 bytes of the created + * DHPART1 message. + * + * The Initiator receives the DHPART1 message and checks that the hash + * matches. Although this hash is easily malleable it is later in the + * protocol used to assert the integrity of all messages. The + * Initiator then computes the shared master secret from its SKi and + * the received PKr. Using this master secret several keys are + * derived: + * + * - HMACi-key using the label "GPG-pa1-HMACi-key". + * - SYMx-key using the label "GPG-pa1-SYMx-key" + * + * For details on the KDF see the implementation of the function kdf. + * The master secret is stored securily in the local state. The + * DHPART2 message is then created and send to the Responder: + * + * 7 byte Magic, value: "GPG-pa1" + * 1 byte MessageType, value 3 (DHPART2) + * 8 byte SessionId from COMMIT message + * 32 byte PKi + * 32 byte MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key) + * + * The Responder receives the DHPART2 message and checks that the hash + * of the received PKi matches the Hash(PKi) value as received earlier + * with the COMMIT message. The Responder now also computes the + * shared master secret from its SKr and the recived PKi and derives + * the keys: + * + * - HMACi-key using the label "GPG-pa1-HMACi-key". + * - HMACr-key using the label "GPG-pa1-HMACr-key". + * - SYMx-key using the label "GPG-pa1-SYMx-key" + * - SAS using the label "GPG-pa1-SAS" + * + * With these keys the MAC from the received DHPART2 message is + * checked. On success a SAS is displayed to the user and a CONFIRM + * message send back: + * + * 7 byte Magic, value: "GPG-pa1" + * 1 byte MessageType, value 4 (CONFIRM) + * 8 byte SessionId from COMMIT message + * 32 byte MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key) + * + * The Initiator receives this CONFIRM message, gets the master shared + * secrey from its local state and derives the keys. It checks the + * the MAC in the received CONFIRM message and ask the user to enter + * the SAS as displayed by the responder. Iff the SAS matches the + * master key is flagged as confirmed and the Initiator may now use a + * derived key to send encrypted data to the Responder. + * + * In case the Responder also needs to send encrypted data we need to + * introduce another final message to tell the responder that the + * Initiator validated the SAS. + * + * TODO: Encrypt the state files using a key stored in gpg-agent's cache. + * + */ + + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> +#include <stdarg.h> + +#include "../common/util.h" +#include "../common/status.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/init.h" +#include "../common/name-value.h" + + +/* Constants to identify the commands and options. */ +enum cmd_and_opt_values + { + aNull = 0, + + oQuiet = 'q', + oVerbose = 'v', + oOutput = 'o', + oArmor = 'a', + + aInitiate = 400, + aRespond = 401, + aGet = 402, + aCleanup = 403, + + oDebug = 500, + oStatusFD, + oHomedir, + oSAS, + + oDummy + }; + + +/* The list of commands and options. */ +static gpgrt_opt_t opts[] = { + ARGPARSE_group (300, ("@Commands:\n ")), + + ARGPARSE_c (aInitiate, "initiate", N_("initiate a pairing request")), + ARGPARSE_c (aRespond, "respond", N_("respond to a pairing request")), + ARGPARSE_c (aGet, "get", N_("return the keys")), + ARGPARSE_c (aCleanup, "cleanup", N_("remove expired states etc.")), + + ARGPARSE_group (301, ("@\nOptions:\n ")), + + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), + ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")), + ARGPARSE_s_s (oSAS, "sas", N_("|SAS|the SAS as shown by the peer")), + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_s (oOutput, "output", N_("|FILE|write the request to FILE")), + ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + + ARGPARSE_s_s (oHomedir, "homedir", "@"), + + ARGPARSE_end () +}; + + +/* We keep all global options in the structure OPT. */ +static struct +{ + int verbose; + unsigned int debug; + int quiet; + int armor; + const char *output; + estream_t statusfp; + unsigned int ttl; + const char *sas; +} opt; + + +/* Debug values and macros. */ +#define DBG_MESSAGE_VALUE 2 /* Debug the messages. */ +#define DBG_CRYPTO_VALUE 4 /* Debug low level crypto. */ +#define DBG_MEMORY_VALUE 32 /* Debug memory allocation stuff. */ + +#define DBG_MESSAGE (opt.debug & DBG_MESSAGE_VALUE) +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_MESSAGE_VALUE, "message" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_MEMORY_VALUE , "memory" }, + { 0, NULL } + }; + + +/* The directory name below the cache dir to store paring states. */ +#define PAIRING_STATE_DIR "state" + +/* Message types. */ +#define MSG_TYPE_COMMIT 1 +#define MSG_TYPE_DHPART1 2 +#define MSG_TYPE_DHPART2 3 +#define MSG_TYPE_CONFIRM 4 + + +/* Realm values. */ +#define REALM_STANDARD 1 + + + + +/* Local prototypes. */ +static void wrong_args (const char *text) GPGRT_ATTR_NORETURN; +static void xnvc_set_printf (nvc_t nvc, const char *name, const char *format, + ...) GPGRT_ATTR_PRINTF(3,4); +static void *hash_data (void *result, size_t resultsize, + ...) GPGRT_ATTR_SENTINEL(0); +static void *hmac_data (void *result, size_t resultsize, + const unsigned char *key, size_t keylen, + ...) GPGRT_ATTR_SENTINEL(0); + + +static gpg_error_t command_initiate (void); +static gpg_error_t command_respond (void); +static gpg_error_t command_cleanup (void); +static gpg_error_t command_get (const char *sessionidstr); + + + + +/* Print usage information and provide strings for help. */ +static const char * +my_strusage( int level ) +{ + const char *p; + + switch (level) + { + case 9: p = "LGPL-2.1-or-later"; break; + case 11: p = "gpg-pair-tool"; break; + case 12: p = "@GNUPG@"; break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: + p = ("Usage: gpg-pair-tool [command] [options] [args] (-h for help)"); + break; + case 41: + p = ("Syntax: gpg-pair-tool [command] [options] [args]\n" + "Client to run the pairing protocol\n"); + break; + + default: p = NULL; break; + } + return p; +} + + +static void +wrong_args (const char *text) +{ + es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text); + exit (2); +} + + +/* Set the status FD. */ +static void +set_status_fd (int fd) +{ + static int last_fd = -1; + + if (fd != -1 && last_fd == fd) + return; + + if (opt.statusfp && opt.statusfp != es_stdout && opt.statusfp != es_stderr) + es_fclose (opt.statusfp); + opt.statusfp = NULL; + if (fd == -1) + return; + + if (fd == 1) + opt.statusfp = es_stdout; + else if (fd == 2) + opt.statusfp = es_stderr; + else + opt.statusfp = es_fdopen (fd, "w"); + if (!opt.statusfp) + { + log_fatal ("can't open fd %d for status output: %s\n", + fd, gpg_strerror (gpg_error_from_syserror ())); + } + last_fd = fd; +} + + +/* Write a status line with code NO followed by the outout of the + * printf style FORMAT. The caller needs to make sure that LFs and + * CRs are not printed. */ +static void +write_status (int no, const char *format, ...) +{ + va_list arg_ptr; + + if (!opt.statusfp) + return; /* Not enabled. */ + + es_fputs ("[GNUPG:] ", opt.statusfp); + es_fputs (get_status_string (no), opt.statusfp); + if (format) + { + es_putc (' ', opt.statusfp); + va_start (arg_ptr, format); + es_vfprintf (opt.statusfp, format, arg_ptr); + va_end (arg_ptr); + } + es_putc ('\n', opt.statusfp); +} + + + +/* gpg-pair-tool main. */ +int +main (int argc, char **argv) +{ + gpg_error_t err; + gpgrt_argparse_t pargs = { &argc, &argv }; + enum cmd_and_opt_values cmd = 0; + + opt.ttl = 8*3600; /* Default to 8 hours. */ + + gnupg_reopen_std ("gpg-pair-tool"); + gpgrt_set_strusage (my_strusage); + log_set_prefix ("gpg-pair-tool", GPGRT_LOG_WITH_PREFIX); + + /* Make sure that our subsystems are ready. */ + i18n_init(); + init_common_subsystems (&argc, &argv); + + /* Parse the command line. */ + while (gpgrt_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oArmor: opt.armor = 1; break; + + case oDebug: + if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) + { + pargs.r_opt = ARGPARSE_INVALID_ARG; + pargs.err = ARGPARSE_PRINT_ERROR; + } + break; + + case oOutput: + opt.output = pargs.r.ret_str; + break; + + case oStatusFD: + set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); + break; + + case oHomedir: + gnupg_set_homedir (pargs.r.ret_str); + break; + + case oSAS: + opt.sas = pargs.r.ret_str; + break; + + case aInitiate: + case aRespond: + case aGet: + case aCleanup: + if (cmd && cmd != pargs.r_opt) + log_error (_("conflicting commands\n")); + else + cmd = pargs.r_opt; + break; + + default: pargs.err = ARGPARSE_PRINT_WARNING; break; + } + } + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (("NOTE: '%s' is not considered an option\n"), argv[i]); + } + gpgrt_argparse (NULL, &pargs, NULL); /* Free internal memory. */ + + if (opt.sas) + { + if (strlen (opt.sas) != 11 + || !digitp (opt.sas+0) || !digitp (opt.sas+1) || !digitp (opt.sas+2) + || opt.sas[3] != '-' + || !digitp (opt.sas+4) || !digitp (opt.sas+5) || !digitp (opt.sas+6) + || opt.sas[7] != '-' + || !digitp (opt.sas+8) || !digitp (opt.sas+9) || !digitp (opt.sas+10)) + log_error ("invalid formatted SAS\n"); + } + + /* Stop if any error, inclduing ARGPARSE_PRINT_WARNING, occurred. */ + if (log_get_errorcount (0)) + exit (2); + + if (DBG_CRYPTO) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1|2); + + + /* Now run the requested command. */ + switch (cmd) + { + case aInitiate: + if (argc) + wrong_args ("--initiate"); + err = command_initiate (); + break; + + case aRespond: + if (argc) + wrong_args ("--respond"); + err = command_respond (); + break; + + case aGet: + if (argc > 1) + wrong_args ("--respond [sessionid]"); + err = command_get (argc? *argv:NULL); + break; + + case aCleanup: + if (argc) + wrong_args ("--cleanup"); + err = command_cleanup (); + break; + + default: + gpgrt_usage (1); + err = 0; + break; + } + + if (err) + write_status (STATUS_FAILURE, "- %u", err); + else if (log_get_errorcount (0)) + write_status (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); + else + write_status (STATUS_SUCCESS, NULL); + return log_get_errorcount (0)? 1:0; +} + + + +/* Wrapper around nvc_new which terminates in the error case. */ +static nvc_t +xnvc_new (void) +{ + nvc_t c = nvc_new (); + if (!c) + log_fatal ("error creating NVC object: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + return c; +} + +/* Wrapper around nvc_set which terminates in the error case. */ +static void +xnvc_set (nvc_t nvc, const char *name, const char *value) +{ + gpg_error_t err = nvc_set (nvc, name, value); + if (err) + log_fatal ("error updating NVC object: %s\n", gpg_strerror (err)); +} + +/* Call vnc_set with (BUFFER, BUFLEN) converted to a hex string as + * value. Terminates in the error case. */ +static void +xnvc_set_hex (nvc_t nvc, const char *name, const void *buffer, size_t buflen) +{ + char *hex; + + hex = bin2hex (buffer, buflen, NULL); + if (!hex) + xoutofcore (); + strlwr (hex); + xnvc_set (nvc, name, hex); + xfree (hex); +} + +/* Call nvc_set with a value created from the string generated using + * the printf style FORMAT. Terminates in the error case. */ +static void +xnvc_set_printf (nvc_t nvc, const char *name, const char *format, ...) +{ + va_list arg_ptr; + char *buffer; + + va_start (arg_ptr, format); + if (gpgrt_vasprintf (&buffer, format, arg_ptr) < 0) + log_fatal ("estream_asprintf failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + va_end (arg_ptr); + xnvc_set (nvc, name, buffer); + xfree (buffer); +} + + +/* Return the string for the first entry in NVC with NAME. If NAME is + * missing, an empty string is returned. The returned string is a + * pointer into NVC. */ +static const char * +xnvc_get_string (nvc_t nvc, const char *name) +{ + nve_t item; + + if (!nvc) + return ""; + item = nvc_lookup (nvc, name); + if (!item) + return ""; + return nve_value (item); +} + + + +/* Return a string for MSGTYPE. */ +const char * +msgtypestr (int msgtype) +{ + switch (msgtype) + { + case MSG_TYPE_COMMIT: return "Commit"; + case MSG_TYPE_DHPART1: return "DHPart1"; + case MSG_TYPE_DHPART2: return "DHPart2"; + case MSG_TYPE_CONFIRM: return "Confirm"; + } + return "?"; +} + + +/* Private to {get,set}_session_id(). */ +static struct { + int initialized; + unsigned char sessid[8]; +} session_id; + + +/* Return the 8 octet session. */ +static unsigned char * +get_session_id (void) +{ + if (!session_id.initialized) + { + session_id.initialized = 1; + gcry_create_nonce (session_id.sessid, sizeof session_id.sessid); + } + + return session_id.sessid; +} + +static void +set_session_id (const void *sessid, size_t len) +{ + log_assert (!session_id.initialized); + if (len > sizeof session_id.sessid) + len = sizeof session_id.sessid; + memcpy (session_id.sessid, sessid, len); + if (len < sizeof session_id.sessid) + memset (session_id.sessid+len, 0, sizeof session_id.sessid - len); + session_id.initialized = 1; +} + +/* Return a string with the hexified session id. */ +static const char * +get_session_id_hex (void) +{ + static char hexstr[16+1]; + + bin2hex (get_session_id (), 8, hexstr); + strlwr (hexstr); + return hexstr; +} + + +/* Return a fixed string with the directory used to store the state of + * pairings. On error a diagnostic is printed but the file name is + * returned anyway. It is expected that the expected failure of the + * following open is responsible for error handling. */ +static const char * +get_pairing_statedir (void) +{ + static char *fname; + gpg_error_t err = 0; + char *tmpstr; + struct stat statbuf; + + if (fname) + return fname; + + fname = make_filename (gnupg_homedir (), GNUPG_CACHE_DIR, NULL); + if (stat (fname, &statbuf) && errno == ENOENT) + { + if (gnupg_mkdir (fname, "-rwx")) + { + err = gpg_error_from_syserror (); + log_error (_("can't create directory '%s': %s\n"), + fname, gpg_strerror (err) ); + } + else if (!opt.quiet) + log_info (_("directory '%s' created\n"), fname); + } + + tmpstr = make_filename (fname, PAIRING_STATE_DIR, NULL); + xfree (fname); + fname = tmpstr; + if (stat (fname, &statbuf) && errno == ENOENT) + { + if (gnupg_mkdir (fname, "-rwx")) + { + if (!err) + { + err = gpg_error_from_syserror (); + log_error (_("can't create directory '%s': %s\n"), + fname, gpg_strerror (err) ); + } + } + else if (!opt.quiet) + log_info (_("directory '%s' created\n"), fname); + } + + return fname; +} + + +/* Open the pairing state file. SESSIONID is a 8 byte buffer with the + * session-id. If CREATE_FLAG is set the file is created and will + * always return a valid stream. If CREATE_FLAG is not set the file + * is opened for reading and writing. If the file does not exist NULL + * is return; in all other error cases the process is terminated. If + * R_FNAME is not NULL the name of the file is stored there and the + * caller needs to free it. */ +static estream_t +open_pairing_state (const unsigned char *sessionid, int create_flag, + char **r_fname) +{ + gpg_error_t err; + char *fname, *tmpstr; + estream_t fp; + + /* The filename is the session id with a "pa1" suffix. Note that + * the state dir may eventually be used for other purposes as well + * and thus the suffix identifies that the file belongs to this + * tool. We use lowercase file names for no real reason. */ + tmpstr = bin2hex (sessionid, 8, NULL); + if (!tmpstr) + xoutofcore (); + strlwr (tmpstr); + fname = xstrconcat (tmpstr, ".pa1", NULL); + xfree (tmpstr); + tmpstr = make_filename (get_pairing_statedir (), fname, NULL); + xfree (fname); + fname = tmpstr; + + fp = es_fopen (fname, create_flag? "wbx,mode=-rw": "rb+,mode=-rw"); + if (!fp) + { + err = gpg_error_from_syserror (); + if (create_flag) + { + /* We should always be able to create a file. Also we use a + * 64 bit session id, it is theoretically possible that such + * a session already exists. However, that is rare enough + * and thus the fatal error message should still be okay. */ + log_fatal ("can't create '%s': %s\n", fname, gpg_strerror (err)); + } + else if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + /* That is an expected error; return NULL. */ + } + else + { + log_fatal ("can't open '%s': %s\n", fname, gpg_strerror (err)); + } + } + + if (r_fname) + *r_fname = fname; + else + xfree (fname); + + return fp; +} + + +/* Write the state to a possible new state file. */ +static void +write_state (nvc_t state, int create_flag) +{ + gpg_error_t err; + char *fname = NULL; + estream_t fp; + + fp = open_pairing_state (get_session_id (), create_flag, &fname); + log_assert (fp); + + err = nvc_write (state, fp); + if (err) + { + es_fclose (fp); + gnupg_remove (fname); + log_fatal ("error writing '%s': %s\n", fname, gpg_strerror (err)); + } + + /* If we did not create the file, we need to truncate the file. */ + if (!create_flag && ftruncate (es_fileno (fp), es_ftello (fp))) + { + err = gpg_error_from_syserror (); + log_fatal ("error truncating '%s': %s\n", fname, gpg_strerror (err)); + } + if (es_ferror (fp) || es_fclose (fp)) + { + err = gpg_error_from_syserror (); + es_fclose (fp); + gnupg_remove (fname); + log_fatal ("error writing '%s': %s\n", fname, gpg_strerror (err)); + } +} + + +/* Read the state into a newly allocated state object and store that + * at R_STATE. If no state is available GPG_ERR_NOT_FOUND is returned + * and as with all errors NULL is tored at R_STATE. SESSIONID is an + * input with the 8 session id. */ +static gpg_error_t +read_state (nvc_t *r_state) +{ + gpg_error_t err; + char *fname = NULL; + estream_t fp; + nvc_t state = NULL; + nve_t item; + const char *value; + unsigned long expire; + + *r_state = NULL; + + fp = open_pairing_state (get_session_id (), 0, &fname); + if (!fp) + return gpg_error (GPG_ERR_NOT_FOUND); + + err = nvc_parse (&state, NULL, fp); + if (err) + { + log_info ("failed to parse state file '%s': %s\n", + fname, gpg_strerror (err)); + goto leave; + } + + /* Check whether the state already expired. */ + item = nvc_lookup (state, "Expires:"); + if (!item) + { + log_info ("invalid state file '%s': %s\n", + fname, "field 'expire' not found"); + goto leave; + } + value = nve_value (item); + if (!value || !(expire = strtoul (value, NULL, 10))) + { + log_info ("invalid state file '%s': %s\n", + fname, "field 'expire' has an invalid value"); + goto leave; + } + if (expire <= gnupg_get_time ()) + { + es_fclose (fp); + fp = NULL; + if (gnupg_remove (fname)) + { + err = gpg_error_from_syserror (); + log_info ("failed to delete state file '%s': %s\n", + fname, gpg_strerror (err)); + } + else if (opt.verbose) + log_info ("state file '%s' deleted\n", fname); + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + + *r_state = state; + state = NULL; + + leave: + nvc_release (state); + es_fclose (fp); + return err; +} + + +/* Send (MSG,MSGLEN) to the output device. */ +static void +send_message (const unsigned char *msg, size_t msglen) +{ + gpg_error_t err; + + if (opt.verbose) + log_info ("session %s: sending %s message\n", + get_session_id_hex (), msgtypestr (msg[7])); + + if (DBG_MESSAGE) + log_printhex (msg, msglen, "send msg(%s):", msgtypestr (msg[7])); + + /* FIXME: For now only stdout. */ + if (opt.armor) + { + gpgrt_b64state_t state; + + state = gpgrt_b64enc_start (es_stdout, ""); + if (!state) + log_fatal ("error setting up base64 encoder: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + err = gpgrt_b64enc_write (state, msg, msglen); + if (!err) + err = gpgrt_b64enc_finish (state); + if (err) + log_fatal ("error writing base64 to stdout: %s\n", gpg_strerror (err)); + } + else + { + if (es_fwrite (msg, msglen, 1, es_stdout) != 1) + log_fatal ("error writing to stdout: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + } + es_fputc ('\n', es_stdout); +} + + +/* Read a message from stdin and store it at the address (R_MSG, + * R_MSGLEN). This function detects armoring and removes it. On + * error NULL is stored at R_MSG, a diagnostic printed and an error + * code returned. The returned message has a proper message type and + * an appropriate length. The message type is stored at R_MSGTYPE and + * if a state is availabale it is stored at R_STATE. */ +static gpg_error_t +read_message (unsigned char **r_msg, size_t *r_msglen, int *r_msgtype, + nvc_t *r_state) +{ + gpg_error_t err; + unsigned char msg[128]; /* max msg size is 80 but 107 with base64. */ + size_t msglen; + size_t reqlen; + + *r_msg = NULL; + *r_state = NULL; + + es_setvbuf (es_stdin, NULL, _IONBF, 0); + es_set_binary (es_stdin); + + if (es_read (es_stdin, msg, sizeof msg, &msglen)) + { + err = gpg_error_from_syserror (); + log_error ("error reading from message: %s\n", gpg_strerror (err)); + return err; + } + + if (msglen > 4 && !memcmp (msg, "R1BH", 4)) + { + /* This is base64 of the first 3 bytes. */ + gpgrt_b64state_t state = gpgrt_b64dec_start (NULL); + if (!state) + log_fatal ("error setting up base64 decoder: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + err = gpgrt_b64dec_proc (state, msg, msglen, &msglen); + gpgrt_b64dec_finish (state); + if (err) + { + log_error ("error decoding message: %s\n", gpg_strerror (err)); + return err; + } + } + + if (msglen < 16 || memcmp (msg, "GPG-pa1", 7)) + { + log_error ("error parsing message: %s\n", + msglen? "invalid header":"empty message"); + return gpg_error (GPG_ERR_INV_RESPONSE); + } + switch (msg[7]) + { + case MSG_TYPE_COMMIT: reqlen = 56; break; + case MSG_TYPE_DHPART1: reqlen = 80; break; + case MSG_TYPE_DHPART2: reqlen = 80; break; + case MSG_TYPE_CONFIRM: reqlen = 48; break; + + default: + log_error ("error parsing message: %s\n", "invalid message type"); + return gpg_error (GPG_ERR_INV_RESPONSE); + } + if (msglen < reqlen) + { + log_error ("error parsing message: %s\n", "message too short"); + return gpg_error (GPG_ERR_INV_RESPONSE); + } + + if (DBG_MESSAGE) + log_printhex (msg, msglen, "recv msg(%s):", msgtypestr (msg[7])); + + /* Note that we ignore any garbage at the end of a message. */ + msglen = reqlen; + + set_session_id (msg+8, 8); + + if (opt.verbose) + log_info ("session %s: received %s message\n", + get_session_id_hex (), msgtypestr (msg[7])); + + /* Read the state. */ + err = read_state (r_state); + if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) + return err; + + *r_msg = xmalloc (msglen); + memcpy (*r_msg, msg, msglen); + *r_msglen = msglen; + *r_msgtype = msg[7]; + return err; +} + + +/* Display the Short Authentication String (SAS). If WAIT is true the + * function waits until the user has entered the SAS as seen at the + * peer. + * + * To construct the SAS we take the 4 most significant octets of HASH, + * interpret them as a 32 bit big endian unsigned integer, divide that + * integer by 10^9 and take the remainder. The remainder is displayed + * as 3 groups of 3 decimal digits delimited by a hyphens. This gives + * a search space of close to 2^30 and is still easy to compare. + */ +static gpg_error_t +display_sas (const unsigned char *hash, size_t hashlen, int wait) +{ + gpg_error_t err; + unsigned long sas = 0; + char sasbuf[12]; + + log_assert (hashlen >= 4); + + sas |= (unsigned long)hash[20] << 24; + sas |= (unsigned long)hash[21] << 16; + sas |= (unsigned long)hash[22] << 8; + sas |= (unsigned long)hash[23]; + sas %= 1000000000ul; + snprintf (sasbuf, sizeof sasbuf, "%09lu", sas); + memmove (sasbuf+8, sasbuf+6, 3); + memmove (sasbuf+4, sasbuf+3, 3); + sasbuf[3] = sasbuf[7] = '-'; + sasbuf[11] = 0; + + if (wait) + log_info ("Please check the SAS:\n"); + else + log_info ("Please note the SAS:\n"); + log_info ("\n"); + log_info (" %s\n", sasbuf); + log_info ("\n"); + + if (wait) + { + if (!opt.sas || strcmp (sasbuf, opt.sas)) + err = gpg_error (GPG_ERR_NOT_CONFIRMED); + else + log_info ("SAS confirmed\n"); + } + + if (err) + log_info ("checking SAS failed: %s\n", gpg_strerror (err)); + return err; +} + + + +static gpg_error_t +create_dh_keypair (unsigned char *dh_secret, size_t dh_secret_len, + unsigned char *dh_public, size_t dh_public_len) +{ + gpg_error_t err; + gcry_sexp_t sexp; + gcry_sexp_t s_keypair; + gcry_buffer_t secret; + gcry_buffer_t public; + unsigned char publicbuf[33]; + + /* We need a temporary buffer for the public key. Check the length + * for the later memcpy. */ + if (dh_public_len < 32) + return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); + + secret.size = dh_secret_len; + secret.data = dh_secret; + secret.off = 0; + public.size = sizeof publicbuf; + public.data = publicbuf; + public.off = 0; + + err = gcry_sexp_build (&sexp, NULL, + "(genkey(ecc(curve Curve25519)(flags djb-tweak)))"); + if (err) + return err; + err = gcry_pk_genkey (&s_keypair, sexp); + gcry_sexp_release (sexp); + if (err) + return err; + err = gcry_sexp_extract_param (s_keypair, "key-data!private-key", + "&dq", &secret, &public, NULL); + gcry_sexp_release (s_keypair); + if (err) + return err; + + /* Gcrypt prepends a 0x40 indicator - remove that. */ + if (public.len == 33) + { + public.len = 32; + memmove (public.data, publicbuf+1, 32); + } + memcpy (dh_public, public.data, public.len); + + if (DBG_CRYPTO) + { + log_printhex (secret.data, secret.len, "DH secret:"); + log_printhex (public.data, public.len, "DH public:"); + } + + return 0; +} + + +/* SHA256 the data given as varargs tuples of (const void*, size_t) + * and store the result in RESULT. The end of the list is indicated + * by a NULL element in a tuple. RESULTLEN gives the length of the + * RESULT buffer which must be at least 32. Note that the second item + * of the tuple is the length and it is a size_t. */ +static void * +hash_data (void *result, size_t resultsize, ...) +{ + va_list arg_ptr; + gpg_error_t err; + gcry_md_hd_t hd; + const void *data; + size_t datalen; + + log_assert (resultsize >= 32); + + err = gcry_md_open (&hd, GCRY_MD_SHA256, 0); + if (err) + log_fatal ("error creating a Hash handle: %s\n", gpg_strerror (err)); + /* log_printhex ("", 0, "Hash-256:"); */ + + va_start (arg_ptr, resultsize); + while ((data = va_arg (arg_ptr, const void *))) + { + datalen = va_arg (arg_ptr, size_t); + /* log_printhex (data, datalen, " data:"); */ + gcry_md_write (hd, data, datalen); + } + va_end (arg_ptr); + + memcpy (result, gcry_md_read (hd, 0), 32); + /* log_printhex (result, 32, " result:"); */ + + gcry_md_close (hd); + + return result; +} + + +/* HMAC-SHA256 the data given as varargs tuples of (const void*, + * size_t) using (KEYLEN,KEY) and store the result in RESULT. The end + * of the list is indicated by a NULL element in a tuple. RESULTLEN + * gives the length of the RESULT buffer which must be at least 32. + * Note that the second item of the tuple is the length and it is a + * size_t. */ +static void * +hmac_data (void *result, size_t resultsize, + const unsigned char *key, size_t keylen, ...) +{ + va_list arg_ptr; + gpg_error_t err; + gcry_mac_hd_t hd; + const void *data; + size_t datalen; + + log_assert (resultsize >= 32); + + err = gcry_mac_open (&hd, GCRY_MAC_HMAC_SHA256, 0, NULL); + if (err) + log_fatal ("error creating a MAC handle: %s\n", gpg_strerror (err)); + err = gcry_mac_setkey (hd, key, keylen); + if (err) + log_fatal ("error setting the MAC key: %s\n", gpg_strerror (err)); + /* log_printhex (key, keylen, "HMAC-key:"); */ + + va_start (arg_ptr, keylen); + while ((data = va_arg (arg_ptr, const void *))) + { + datalen = va_arg (arg_ptr, size_t); + /* log_printhex (data, datalen, " data:"); */ + err = gcry_mac_write (hd, data, datalen); + if (err) + log_fatal ("error writing to the MAC handle: %s\n", gpg_strerror (err)); + } + va_end (arg_ptr); + + err = gcry_mac_read (hd, result, &resultsize); + if (err || resultsize != 32) + log_fatal ("error reading MAC value: %s\n", gpg_strerror (err)); + /* log_printhex (result, resultsize, " result:"); */ + + gcry_mac_close (hd); + + return result; +} + + +/* Key derivation function: + * + * FIXME(doc) + */ +static void +kdf (unsigned char *result, size_t resultlen, + const unsigned char *master, size_t masterlen, + const unsigned char *sessionid, size_t sessionidlen, + const unsigned char *expire, size_t expirelen, + const char *label) +{ + log_assert (masterlen == 32 && sessionidlen == 8 && expirelen == 5); + log_assert (*label); + log_assert (resultlen == 32); + + hmac_data (result, resultlen, master, masterlen, + "\x00\x00\x00\x01", (size_t)4, /* Counter=1*/ + label, strlen (label) + 1, /* Label, 0x00 */ + sessionid, sessionidlen, /* Context */ + expire, expirelen, /* Context */ + "\x00\x00\x01\x00", (size_t)4, /* L=256 */ + NULL); +} + + +static gpg_error_t +compute_master_secret (unsigned char *master, size_t masterlen, + const unsigned char *sk_a, size_t sk_a_len, + const unsigned char *pk_b, size_t pk_b_len) +{ + gpg_error_t err; + gcry_sexp_t s_sk_a = NULL; + gcry_sexp_t s_pk_b = NULL; + gcry_sexp_t s_shared = NULL; + gcry_sexp_t s_tmp; + const char *s; + size_t n; + + log_assert (masterlen == 32); + + err = gcry_sexp_build (&s_sk_a, NULL, "%b", (int)sk_a_len, sk_a); + if (!err) + err = gcry_sexp_build (&s_pk_b, NULL, + "(public-key(ecdh(curve Curve25519)" + " (flags djb-tweak)(q%b)))", + (int)pk_b_len, pk_b); + if (err) + { + log_error ("error building S-expression: %s\n", gpg_strerror (err)); + goto leave; + } + + err = gcry_pk_encrypt (&s_shared, s_sk_a, s_pk_b); + if (err) + { + log_error ("error computing DH: %s\n", gpg_strerror (err)); + goto leave; + } + /* gcry_log_debugsxp ("sk_a", s_sk_a); */ + /* gcry_log_debugsxp ("pk_b", s_pk_b); */ + /* gcry_log_debugsxp ("shared", s_shared); */ + + s_tmp = gcry_sexp_find_token (s_shared, "s", 0); + if (!s_tmp || !(s = gcry_sexp_nth_data (s_tmp, 1, &n)) + || n != 33 || s[0] != 0x40) + { + err = gpg_error (GPG_ERR_INTERNAL); + log_error ("error computing DH: %s\n", gpg_strerror (err)); + goto leave; + } + memcpy (master, s+1, 32); + + + leave: + gcry_sexp_release (s_sk_a); + gcry_sexp_release (s_pk_b); + gcry_sexp_release (s_shared); + return err; +} + + +/* We are the Initiator: Create the commit message. This function + * sends the COMMIT message and writes STATE. */ +static gpg_error_t +make_msg_commit (nvc_t state) +{ + gpg_error_t err; + uint64_t now, expire; + unsigned char secret[32]; + unsigned char public[32]; + unsigned char *newmsg; + size_t newmsglen; + unsigned char tmphash[32]; + + err = create_dh_keypair (secret, sizeof secret, public, sizeof public ); + if (err) + log_error ("creating DH keypair failed: %s\n", gpg_strerror (err)); + + now = gnupg_get_time (); + expire = now + opt.ttl; + + newmsglen = 7+1+8+1+2+5+32; + newmsg = xmalloc (newmsglen); + memcpy (newmsg+0, "GPG-pa1", 7); + newmsg[7] = MSG_TYPE_COMMIT; + memcpy (newmsg+8, get_session_id (), 8); + newmsg[16] = REALM_STANDARD; + newmsg[17] = 0; + newmsg[18] = 0; + newmsg[19] = expire >> 32; + newmsg[20] = expire >> 24; + newmsg[21] = expire >> 16; + newmsg[22] = expire >> 8; + newmsg[23] = expire; + gcry_md_hash_buffer (GCRY_MD_SHA256, newmsg+24, public, 32); + + /* Create the state file. */ + xnvc_set (state, "State:", "Commit-sent"); + xnvc_set_printf (state, "Created:", "%llu", (unsigned long long)now); + xnvc_set_printf (state, "Expires:", "%llu", (unsigned long long)expire); + xnvc_set_hex (state, "DH-PKi:", public, 32); + xnvc_set_hex (state, "DH-SKi:", secret, 32); + gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen); + xnvc_set_hex (state, "Hash-Commit:", tmphash, 32); + + /* Write the state. Note that we need to create it. The state + * updating should in theory be done atomically with send_message. + * However, we can't assure that the message will actually be + * delivered and thus it doesn't matter whether we have an already + * update state when we later fail in send_message. */ + write_state (state, 1); + + /* Write the message. */ + send_message (newmsg, newmsglen); + + xfree (newmsg); + return err; +} + + +/* We are the Responder: Process a commit message in (MSG,MSGLEN) + * which has already been validated to have a correct header and + * message type. Sends the DHPart1 message and writes STATE. */ +static gpg_error_t +proc_msg_commit (nvc_t state, const unsigned char *msg, size_t msglen) +{ + gpg_error_t err; + uint64_t now, expire; + unsigned char tmphash[32]; + unsigned char secret[32]; + unsigned char public[32]; + unsigned char *newmsg = NULL; + size_t newmsglen; + + log_assert (msglen >= 56); + now = gnupg_get_time (); + + /* Check that the message has not expired. */ + expire = (uint64_t)msg[19] << 32; + expire |= (uint64_t)msg[20] << 24; + expire |= (uint64_t)msg[21] << 16; + expire |= (uint64_t)msg[22] << 8; + expire |= (uint64_t)msg[23]; + if (expire < now) + { + log_error ("received %s message is too old\n", + msgtypestr (MSG_TYPE_COMMIT)); + err = gpg_error (GPG_ERR_TOO_OLD); + goto leave; + } + + /* Create the response. */ + err = create_dh_keypair (secret, sizeof secret, public, sizeof public ); + if (err) + { + log_error ("creating DH keypair failed: %s\n", gpg_strerror (err)); + goto leave; + } + + newmsglen = 7+1+8+32+32; + newmsg = xmalloc (newmsglen); + memcpy (newmsg+0, "GPG-pa1", 7); + newmsg[7] = MSG_TYPE_DHPART1; + memcpy (newmsg+8, msg + 8, 8); /* SessionID. */ + memcpy (newmsg+16, public, 32); /* PKr */ + /* Hash(Hash(Commit) || DHPart1[0..47]) */ + gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen); + hash_data (newmsg+48, 32, + tmphash, sizeof tmphash, + newmsg, (size_t)48, + NULL); + + /* Update the state. */ + xnvc_set (state, "State:", "DHPart1-sent"); + xnvc_set_printf (state, "Created:", "%llu", (unsigned long long)now); + xnvc_set_printf (state, "Expires:", "%llu", (unsigned long long)expire); + xnvc_set_hex (state, "Hash-PKi:", msg+24, 32); + xnvc_set_hex (state, "DH-PKr:", public, 32); + xnvc_set_hex (state, "DH-SKr:", secret, 32); + gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen); + xnvc_set_hex (state, "Hash-DHPart1:", tmphash, 32); + + /* Write the state. Note that we need to create it. */ + write_state (state, 1); + + /* Write the message. */ + send_message (newmsg, newmsglen); + + leave: + xfree (newmsg); + return err; +} + + +/* We are the Initiator: Process a DHPART1 message in (MSG,MSGLEN) + * which has already been validated to have a correct header and + * message type. Sends the DHPart2 message and writes STATE. */ +static gpg_error_t +proc_msg_dhpart1 (nvc_t state, const unsigned char *msg, size_t msglen) +{ + gpg_error_t err; + unsigned char hash[32]; + unsigned char tmphash[32]; + unsigned char pki[32]; + unsigned char pkr[32]; + unsigned char ski[32]; + unsigned char master[32]; + uint64_t expire; + unsigned char expirebuf[5]; + unsigned char hmacikey[32]; + unsigned char symxkey[32]; + unsigned char *newmsg = NULL; + size_t newmsglen; + + log_assert (msglen >= 80); + + /* Check that the message includes the Hash(Commit). */ + if (hex2bin (xnvc_get_string (state, "Hash-Commit:"), hash, sizeof hash) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'Hash-Commit' in our state file\n"); + goto leave; + } + hash_data (tmphash, 32, + hash, sizeof hash, + msg, (size_t)48, + NULL); + if (memcmp (msg+48, tmphash, 32)) + { + err = gpg_error (GPG_ERR_BAD_DATA); + log_error ("manipulation of received %s message detected: %s\n", + msgtypestr (MSG_TYPE_DHPART1), "Bad Hash"); + goto leave; + } + /* Check that the received PKr is different from our PKi and copy + * PKr into PKR. */ + if (hex2bin (xnvc_get_string (state, "DH-PKi:"), pki, sizeof pki) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'DH-PKi' in our state file\n"); + goto leave; + } + if (!memcmp (msg+16, pki, 32)) + { + /* This can only happen if the state file leaked to the + * responder. */ + err = gpg_error (GPG_ERR_BAD_DATA); + log_error ("received our own public key PKi instead of PKr\n"); + goto leave; + } + memcpy (pkr, msg+16, 32); + + /* Put the expire value into a buffer. */ + expire = string_to_u64 (xnvc_get_string (state, "Expires:")); + if (!expire) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no 'Expire' in our state file\n"); + goto leave; + } + expirebuf[0] = expire >> 32; + expirebuf[1] = expire >> 24; + expirebuf[2] = expire >> 16; + expirebuf[3] = expire >> 8; + expirebuf[4] = expire; + + /* Get our secret from the state. */ + if (hex2bin (xnvc_get_string (state, "DH-SKi:"), ski, sizeof ski) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'DH-SKi' in our state file\n"); + goto leave; + } + + /* Compute the shared secrets. */ + err = compute_master_secret (master, sizeof master, + ski, sizeof ski, pkr, sizeof pkr); + if (err) + { + log_error ("creating DH keypair failed: %s\n", gpg_strerror (err)); + goto leave; + } + + kdf (hmacikey, sizeof hmacikey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-HMACi-key"); + kdf (symxkey, sizeof symxkey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-SYMx-key"); + + + /* Create the response. */ + newmsglen = 7+1+8+32+32; + newmsg = xmalloc (newmsglen); + memcpy (newmsg+0, "GPG-pa1", 7); + newmsg[7] = MSG_TYPE_DHPART2; + memcpy (newmsg+8, msg + 8, 8); /* SessionID. */ + memcpy (newmsg+16, pki, 32); /* PKi */ + /* MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key) */ + gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen); + hmac_data (newmsg+48, 32, hmacikey, sizeof hmacikey, + tmphash, sizeof tmphash, + newmsg, (size_t)48, + symxkey, sizeof symxkey, + NULL); + + /* Update the state. */ + xnvc_set (state, "State:", "DHPart2-sent"); + xnvc_set_hex (state, "DH-Master:", master, sizeof master); + gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen); + xnvc_set_hex (state, "Hash-DHPart2:", tmphash, 32); + + /* Write the state. */ + write_state (state, 0); + + /* Write the message. */ + send_message (newmsg, newmsglen); + + leave: + xfree (newmsg); + return err; +} + + +/* We are the Responder: Process a DHPART2 message in (MSG,MSGLEN) + * which has already been validated to have a correct header and + * message type. Sends the CONFIRM message and writes STATE. */ +static gpg_error_t +proc_msg_dhpart2 (nvc_t state, const unsigned char *msg, size_t msglen) +{ + gpg_error_t err; + unsigned char hash[32]; + unsigned char tmphash[32]; + uint64_t expire; + unsigned char expirebuf[5]; + unsigned char pki[32]; + unsigned char pkr[32]; + unsigned char skr[32]; + unsigned char master[32]; + unsigned char hmacikey[32]; + unsigned char hmacrkey[32]; + unsigned char symxkey[32]; + unsigned char sas[32]; + unsigned char *newmsg = NULL; + size_t newmsglen; + + log_assert (msglen >= 80); + + /* Check that the PKi in the message matches the Hash(Pki) received + * with the Commit message. */ + memcpy (pki, msg + 16, 32); + gcry_md_hash_buffer (GCRY_MD_SHA256, hash, pki, 32); + if (hex2bin (xnvc_get_string (state, "Hash-PKi:"), + tmphash, sizeof tmphash) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'Hash-PKi' in our state file\n"); + goto leave; + } + if (memcmp (hash, tmphash, 32)) + { + err = gpg_error (GPG_ERR_BAD_DATA); + log_error ("Initiator sent a different key in %s than announced in %s\n", + msgtypestr (MSG_TYPE_DHPART2), + msgtypestr (MSG_TYPE_COMMIT)); + goto leave; + } + /* Check that the received PKi is different from our PKr. */ + if (hex2bin (xnvc_get_string (state, "DH-PKr:"), pkr, sizeof pkr) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'DH-PKr' in our state file\n"); + goto leave; + } + if (!memcmp (pkr, pki, 32)) + { + err = gpg_error (GPG_ERR_BAD_DATA); + log_error ("Initiator sent our own PKr back\n"); + goto leave; + } + + /* Put the expire value into a buffer. */ + expire = string_to_u64 (xnvc_get_string (state, "Expires:")); + if (!expire) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no 'Expire' in our state file\n"); + goto leave; + } + expirebuf[0] = expire >> 32; + expirebuf[1] = expire >> 24; + expirebuf[2] = expire >> 16; + expirebuf[3] = expire >> 8; + expirebuf[4] = expire; + + /* Get our secret from the state. */ + if (hex2bin (xnvc_get_string (state, "DH-SKr:"), skr, sizeof skr) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'DH-SKr' in our state file\n"); + goto leave; + } + + /* Compute the shared secrets. */ + err = compute_master_secret (master, sizeof master, + skr, sizeof skr, pki, sizeof pki); + if (err) + { + log_error ("creating DH keypair failed: %s\n", gpg_strerror (err)); + goto leave; + } + + kdf (hmacikey, sizeof hmacikey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-HMACi-key"); + kdf (hmacrkey, sizeof hmacrkey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-HMACr-key"); + kdf (symxkey, sizeof symxkey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-SYMx-key"); + kdf (sas, sizeof sas, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-SAS"); + + /* Check the MAC from the message which is + * MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key). + * For that we need to fetch the stored hash from the state. */ + if (hex2bin (xnvc_get_string (state, "Hash-DHPart1:"), + tmphash, sizeof tmphash) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'Hash-DHPart1' in our state file\n"); + goto leave; + } + hmac_data (hash, 32, hmacikey, sizeof hmacikey, + tmphash, sizeof tmphash, + msg, 48, + symxkey, sizeof symxkey, + NULL); + if (memcmp (msg+48, hash, 32)) + { + err = gpg_error (GPG_ERR_BAD_DATA); + log_error ("manipulation of received %s message detected: %s\n", + msgtypestr (MSG_TYPE_DHPART2), "Bad MAC"); + goto leave; + } + + /* Create the response. */ + newmsglen = 7+1+8+32; + newmsg = xmalloc (newmsglen); + memcpy (newmsg+0, "GPG-pa1", 7); + newmsg[7] = MSG_TYPE_CONFIRM; + memcpy (newmsg+8, msg + 8, 8); /* SessionID. */ + /* MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key) */ + gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen); + hmac_data (newmsg+16, 32, hmacrkey, sizeof hmacrkey, + tmphash, sizeof tmphash, + newmsg, (size_t)16, + symxkey, sizeof symxkey, + NULL); + + /* Update the state. */ + xnvc_set (state, "State:", "Confirm-sent"); + xnvc_set_hex (state, "DH-Master:", master, sizeof master); + + /* Write the state. */ + write_state (state, 0); + + /* Write the message. */ + send_message (newmsg, newmsglen); + + display_sas (sas, sizeof sas, 0); + + + leave: + xfree (newmsg); + return err; +} + + +/* We are the Initiator: Process a CONFIRM message in (MSG,MSGLEN) + * which has already been validated to have a correct header and + * message type. Does not send anything back. */ +static gpg_error_t +proc_msg_confirm (nvc_t state, const unsigned char *msg, size_t msglen) +{ + gpg_error_t err; + unsigned char hash[32]; + unsigned char tmphash[32]; + unsigned char master[32]; + uint64_t expire; + unsigned char expirebuf[5]; + unsigned char hmacrkey[32]; + unsigned char symxkey[32]; + unsigned char sas[32]; + + log_assert (msglen >= 48); + + /* Put the expire value into a buffer. */ + expire = string_to_u64 (xnvc_get_string (state, "Expires:")); + if (!expire) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no 'Expire' in our state file\n"); + goto leave; + } + expirebuf[0] = expire >> 32; + expirebuf[1] = expire >> 24; + expirebuf[2] = expire >> 16; + expirebuf[3] = expire >> 8; + expirebuf[4] = expire; + + /* Get the master secret. */ + if (hex2bin (xnvc_get_string (state, "DH-Master:"),master,sizeof master) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'DH-Master' in our state file\n"); + goto leave; + } + + kdf (hmacrkey, sizeof hmacrkey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-HMACr-key"); + kdf (symxkey, sizeof symxkey, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-SYMx-key"); + kdf (sas, sizeof sas, + master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, + "GPG-pa1-SAS"); + + /* Check the MAC from the message which is */ + /* MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key). */ + if (hex2bin (xnvc_get_string (state, "Hash-DHPart2:"), + tmphash, sizeof tmphash) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("no or garbled 'Hash-DHPart2' in our state file\n"); + goto leave; + } + hmac_data (hash, 32, hmacrkey, sizeof hmacrkey, + tmphash, sizeof tmphash, + msg, (size_t)16, + symxkey, sizeof symxkey, + NULL); + if (!memcmp (msg+48, hash, 32)) + { + err = gpg_error (GPG_ERR_BAD_DATA); + log_error ("manipulation of received %s message detected: %s\n", + msgtypestr (MSG_TYPE_CONFIRM), "Bad MAC"); + goto leave; + } + + + err = display_sas (sas, sizeof sas, 1); + if (err) + goto leave; + + /* Update the state. */ + xnvc_set (state, "State:", "Confirmed"); + + /* Write the state. */ + write_state (state, 0); + + leave: + return err; +} + + + +/* Expire old state files. This loops over all state files and remove + * those which are expired. */ +static void +expire_old_states (void) +{ + gpg_error_t err = 0; + const char *dirname; + DIR *dir = NULL; + struct dirent *dir_entry; + char *fname = NULL; + estream_t fp = NULL; + nvc_t nvc = NULL; + nve_t item; + const char *value; + unsigned long expire; + unsigned long now = gnupg_get_time (); + + dirname = get_pairing_statedir (); + dir = opendir (dirname); + if (!dir) + { + err = gpg_error_from_syserror (); + goto leave; + } + + while ((dir_entry = readdir (dir))) + { + if (strlen (dir_entry->d_name) != 16+4 + || strcmp (dir_entry->d_name + 16, ".pa1")) + continue; + + xfree (fname); + fname = make_filename (dirname, dir_entry->d_name, NULL); + es_fclose (fp); + fp = es_fopen (fname, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) != GPG_ERR_ENOENT) + log_info ("failed to open state file '%s': %s\n", + fname, gpg_strerror (err)); + continue; + } + nvc_release (nvc); + + /* NB.: The following is similar to code in read_state. */ + err = nvc_parse (&nvc, NULL, fp); + if (err) + { + log_info ("failed to parse state file '%s': %s\n", + fname, gpg_strerror (err)); + continue; /* Skip */ + } + item = nvc_lookup (nvc, "Expires:"); + if (!item) + { + log_info ("invalid state file '%s': %s\n", + fname, "field 'expire' not found"); + continue; /* Skip */ + } + value = nve_value (item); + if (!value || !(expire = strtoul (value, NULL, 10))) + { + log_info ("invalid state file '%s': %s\n", + fname, "field 'expire' has an invalid value"); + continue; /* Skip */ + } + + if (expire <= now) + { + es_fclose (fp); + fp = NULL; + if (gnupg_remove (fname)) + { + err = gpg_error_from_syserror (); + log_info ("failed to delete state file '%s': %s\n", + fname, gpg_strerror (err)); + } + else if (opt.verbose) + log_info ("state file '%s' deleted\n", fname); + } + } + + leave: + if (err) + log_error ("expiring old states in '%s' failed: %s\n", + dirname, gpg_strerror (err)); + if (dir) + closedir (dir); + es_fclose (fp); + xfree (fname); +} + + + +/* Initiate a pairing. The output needs to be conveyed to the + * peer */ +static gpg_error_t +command_initiate (void) +{ + gpg_error_t err; + nvc_t state; + + state = xnvc_new (); + xnvc_set (state, "Version:", "GPG-pa1"); + xnvc_set_hex (state, "Session:", get_session_id (), 8); + xnvc_set (state, "Role:", "Initiator"); + + err = make_msg_commit (state); + + nvc_release (state); + return err; +} + + + +/* Helper for command_respond(). */ +static gpg_error_t +expect_state (int msgtype, const char *statestr, const char *expected) +{ + if (strcmp (statestr, expected)) + { + log_error ("received %s message in %s state (should be %s)\n", + msgtypestr (msgtype), statestr, expected); + return gpg_error (GPG_ERR_INV_RESPONSE); + } + return 0; +} + +/* Respond to a pairing intiation. This is used by the peer and later + * by the original responder. Depending on the state the output needs + * to be conveyed to the peer. */ +static gpg_error_t +command_respond (void) +{ + gpg_error_t err; + unsigned char *msg; + size_t msglen = 0; /* In case that read_message returns an error. */ + int msgtype = 0; /* ditto. */ + nvc_t state; + const char *rolestr; + const char *statestr; + + err = read_message (&msg, &msglen, &msgtype, &state); + if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) + goto leave; + rolestr = xnvc_get_string (state, "Role:"); + statestr = xnvc_get_string (state, "State:"); + if (DBG_MESSAGE) + { + if (!state) + log_debug ("no state available\n"); + else + log_debug ("we are %s, our current state is %s\n", rolestr, statestr); + log_debug ("got message of type %s (%d)\n", + msgtypestr (msgtype), msgtype); + } + + if (!state) + { + if (msgtype == MSG_TYPE_COMMIT) + { + state = xnvc_new (); + xnvc_set (state, "Version:", "GPG-pa1"); + xnvc_set_hex (state, "Session:", get_session_id (), 8); + xnvc_set (state, "Role:", "Responder"); + err = proc_msg_commit (state, msg, msglen); + } + else + { + log_error ("%s message expected but got %s\n", + msgtypestr (MSG_TYPE_COMMIT), msgtypestr (msgtype)); + if (msgtype == MSG_TYPE_DHPART1) + log_info ("the pairing probably took too long and timed out\n"); + err = gpg_error (GPG_ERR_INV_RESPONSE); + goto leave; + } + } + else if (!strcmp (rolestr, "Initiator")) + { + if (msgtype == MSG_TYPE_DHPART1) + { + if (!(err = expect_state (msgtype, statestr, "Commit-sent"))) + err = proc_msg_dhpart1 (state, msg, msglen); + } + else if (msgtype == MSG_TYPE_CONFIRM) + { + if (!(err = expect_state (msgtype, statestr, "DHPart2-sent"))) + err = proc_msg_confirm (state, msg, msglen); + } + else + { + log_error ("%s message not expected by Initiator\n", + msgtypestr (msgtype)); + err = gpg_error (GPG_ERR_INV_RESPONSE); + goto leave; + } + } + else if (!strcmp (rolestr, "Responder")) + { + if (msgtype == MSG_TYPE_DHPART2) + { + if (!(err = expect_state (msgtype, statestr, "DHPart1-sent"))) + err = proc_msg_dhpart2 (state, msg, msglen); + } + else + { + log_error ("%s message not expected by Responder\n", + msgtypestr (msgtype)); + err = gpg_error (GPG_ERR_INV_RESPONSE); + goto leave; + } + } + else + log_fatal ("invalid role '%s' in state file\n", rolestr); + + + leave: + xfree (msg); + nvc_release (state); + return err; +} + + + +/* Return the keys for SESSIONIDSTR or the last one if it is NULL. + * Two keys are returned: The first is the one for sending encrypted + * data and the second one for decrypting received data. The keys are + * always returned hex encoded and both are terminated by a LF. */ +static gpg_error_t +command_get (const char *sessionidstr) +{ + gpg_error_t err; + unsigned char sessid[8]; + nvc_t state; + + if (!sessionidstr) + { + log_error ("calling without session-id is not yet implemented\n"); + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + goto leave; + } + if (hex2bin (sessionidstr, sessid, sizeof sessid) < 0) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("invalid session id given\n"); + goto leave; + } + set_session_id (sessid, sizeof sessid); + err = read_state (&state); + if (err) + { + log_error ("reading state of session %s failed: %s\n", + sessionidstr, gpg_strerror (err)); + goto leave; + } + + leave: + return err; +} + + + +/* Cleanup command. */ +static gpg_error_t +command_cleanup (void) +{ + expire_old_states (); + return 0; +} diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c index 73945ff30..0f08737c4 100644 --- a/tools/gpg-wks-client.c +++ b/tools/gpg-wks-client.c @@ -22,6 +22,8 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/types.h> +#include <sys/stat.h> #include "../common/util.h" #include "../common/status.h" @@ -48,6 +50,7 @@ enum cmd_and_opt_values oQuiet = 'q', oVerbose = 'v', oOutput = 'o', + oDirectory = 'C', oDebug = 500, @@ -56,11 +59,14 @@ enum cmd_and_opt_values aCreate, aReceive, aRead, + aInstallKey, + aRemoveKey, oGpgProgram, oSend, oFakeSubmissionAddr, oStatusFD, + oWithColons, oDummy }; @@ -80,6 +86,10 @@ static ARGPARSE_OPTS opts[] = { ("receive a MIME confirmation request")), ARGPARSE_c (aRead, "read", ("receive a plain text confirmation request")), + ARGPARSE_c (aInstallKey, "install-key", + "install a key into a directory"), + ARGPARSE_c (aRemoveKey, "remove-key", + "remove a key from a directory"), ARGPARSE_group (301, ("@\nOptions:\n ")), @@ -90,6 +100,8 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"), ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + ARGPARSE_s_n (oWithColons, "with-colons", "@"), + ARGPARSE_s_s (oDirectory, "directory", "@"), ARGPARSE_s_s (oFakeSubmissionAddr, "fake-submission-addr", "@"), @@ -192,6 +204,9 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; + case oDirectory: + opt.directory = pargs->r.ret_str; + break; case oSend: opt.use_sendmail = 1; break; @@ -204,12 +219,17 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) case oStatusFD: wks_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); break; + case oWithColons: + opt.with_colons = 1; + break; case aSupported: case aCreate: case aReceive: case aRead: case aCheck: + case aInstallKey: + case aRemoveKey: cmd = pargs->r_opt; break; @@ -264,18 +284,52 @@ main (int argc, char **argv) if (!opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); + if (!opt.directory) + opt.directory = "openpgpkey"; + /* Tell call-dirmngr what options we want. */ set_dirmngr_options (opt.verbose, (opt.debug & DBG_IPC_VALUE), 1); + + /* Check that the top directory exists. */ + if (cmd == aInstallKey || cmd == aRemoveKey) + { + struct stat sb; + + if (stat (opt.directory, &sb)) + { + err = gpg_error_from_syserror (); + log_error ("error accessing directory '%s': %s\n", + opt.directory, gpg_strerror (err)); + goto leave; + } + if (!S_ISDIR(sb.st_mode)) + { + log_error ("error accessing directory '%s': %s\n", + opt.directory, "not a directory"); + err = gpg_error (GPG_ERR_ENOENT); + goto leave; + } + } + /* Run the selected command. */ switch (cmd) { case aSupported: - if (argc != 1) - wrong_args ("--supported USER-ID"); - err = command_supported (argv[0]); - if (err && gpg_err_code (err) != GPG_ERR_FALSE) - log_error ("checking support failed: %s\n", gpg_strerror (err)); + if (opt.with_colons) + { + for (; argc; argc--, argv++) + command_supported (*argv); + err = 0; + } + else + { + if (argc != 1) + wrong_args ("--supported DOMAIN"); + err = command_supported (argv[0]); + if (err && gpg_err_code (err) != GPG_ERR_FALSE) + log_error ("checking support failed: %s\n", gpg_strerror (err)); + } break; case aCreate: @@ -308,12 +362,28 @@ main (int argc, char **argv) err = command_check (argv[0]); break; + case aInstallKey: + if (!argc) + err = wks_cmd_install_key (NULL, NULL); + else if (argc == 2) + err = wks_cmd_install_key (*argv, argv[1]); + else + wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]"); + break; + + case aRemoveKey: + if (argc != 1) + wrong_args ("--remove-key USER-ID"); + err = wks_cmd_remove_key (*argv); + break; + default: usage (1); err = 0; break; } + leave: if (err) wks_write_status (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) @@ -471,6 +541,134 @@ decrypt_stream (estream_t *r_output, struct decrypt_stream_parm_s *decinfo, } +/* Return the submission address for the address or just the domain in + * ADDRSPEC. The submission address is stored as a malloced string at + * R_SUBMISSION_ADDRESS. At R_POLICY the policy flags of the domain + * are stored. The caller needs to free them with wks_free_policy. + * The function returns an error code on failure to find a submission + * address or policy file. Note: The function may store NULL at + * R_SUBMISSION_ADDRESS but return success to indicate that the web + * key directory is supported but not the web key service. As per WKD + * specs a policy file is always required and will thus be return on + * success. */ +static gpg_error_t +get_policy_and_sa (const char *addrspec, int silent, + policy_flags_t *r_policy, char **r_submission_address) +{ + gpg_error_t err; + estream_t mbuf = NULL; + const char *domain; + const char *s; + policy_flags_t policy = NULL; + char *submission_to = NULL; + + *r_submission_address = NULL; + *r_policy = NULL; + + domain = strchr (addrspec, '@'); + if (domain) + domain++; + + if (opt.with_colons) + { + s = domain? domain : addrspec; + es_write_sanitized (es_stdout, s, strlen (s), ":", NULL); + es_putc (':', es_stdout); + } + + /* We first try to get the submission address from the policy file + * (this is the new method). If both are available we check that + * they match and print a warning if not. In the latter case we + * keep on using the one from the submission-address file. */ + err = wkd_get_policy_flags (addrspec, &mbuf); + if (err && gpg_err_code (err) != GPG_ERR_NO_DATA + && gpg_err_code (err) != GPG_ERR_NO_NAME) + { + if (!opt.with_colons) + log_error ("error reading policy flags for '%s': %s\n", + domain, gpg_strerror (err)); + goto leave; + } + if (!mbuf) + { + if (!opt.with_colons) + log_error ("provider for '%s' does NOT support the Web Key Directory\n", + addrspec); + err = gpg_error (GPG_ERR_FALSE); + goto leave; + } + + policy = xtrycalloc (1, sizeof *policy); + if (!policy) + err = gpg_error_from_syserror (); + else + err = wks_parse_policy (policy, mbuf, 1); + es_fclose (mbuf); + mbuf = NULL; + if (err) + goto leave; + + err = wkd_get_submission_address (addrspec, &submission_to); + if (err && !policy->submission_address) + { + if (!silent && !opt.with_colons) + log_error (_("error looking up submission address for domain '%s'" + ": %s\n"), domain, gpg_strerror (err)); + if (!silent && gpg_err_code (err) == GPG_ERR_NO_DATA && !opt.with_colons) + log_error (_("this domain probably doesn't support WKS.\n")); + goto leave; + } + + if (submission_to && policy->submission_address + && ascii_strcasecmp (submission_to, policy->submission_address)) + log_info ("Warning: different submission addresses (sa=%s, po=%s)\n", + submission_to, policy->submission_address); + + if (!submission_to && policy->submission_address) + { + submission_to = xtrystrdup (policy->submission_address); + if (!submission_to) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + leave: + *r_submission_address = submission_to; + submission_to = NULL; + *r_policy = policy; + policy = NULL; + + if (opt.with_colons) + { + if (*r_policy && !*r_submission_address) + es_fprintf (es_stdout, "1:0::"); + else if (*r_policy && *r_submission_address) + es_fprintf (es_stdout, "1:1::"); + else if (err && !(gpg_err_code (err) == GPG_ERR_FALSE + || gpg_err_code (err) == GPG_ERR_NO_DATA + || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST)) + es_fprintf (es_stdout, "0:0:%d:", err); + else + es_fprintf (es_stdout, "0:0::"); + if (*r_policy) + { + es_fprintf (es_stdout, "%u:%u:%u:", + (*r_policy)->protocol_version, + (*r_policy)->auth_submit, + (*r_policy)->mailbox_only); + } + es_putc ('\n', es_stdout); + } + + xfree (submission_to); + wks_free_policy (policy); + xfree (policy); + es_fclose (mbuf); + return err; +} + /* Check whether the provider supports the WKS protocol. */ @@ -480,15 +678,16 @@ command_supported (char *userid) gpg_error_t err; char *addrspec = NULL; char *submission_to = NULL; + policy_flags_t policy = NULL; if (!strchr (userid, '@')) { char *tmp = xstrconcat ("foo@", userid, NULL); - addrspec = mailbox_from_userid (tmp); + addrspec = mailbox_from_userid (tmp, 0); xfree (tmp); } else - addrspec = mailbox_from_userid (userid); + addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); @@ -497,24 +696,41 @@ command_supported (char *userid) } /* Get the submission address. */ - err = wkd_get_submission_address (addrspec, &submission_to); - if (err) + err = get_policy_and_sa (addrspec, 1, &policy, &submission_to); + if (err || !submission_to) { - if (gpg_err_code (err) == GPG_ERR_NO_DATA - || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST) + if (!submission_to + || gpg_err_code (err) == GPG_ERR_FALSE + || gpg_err_code (err) == GPG_ERR_NO_DATA + || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST + ) { - if (opt.verbose) - log_info ("provider for '%s' does NOT support WKS (%s)\n", - addrspec, gpg_strerror (err)); + /* FALSE is returned if we already figured out that even the + * Web Key Directory is not supported and thus printed an + * error message. */ + if (opt.verbose && gpg_err_code (err) != GPG_ERR_FALSE + && !opt.with_colons) + { + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + log_info ("provider for '%s' does NOT support WKS\n", + addrspec); + else + log_info ("provider for '%s' does NOT support WKS (%s)\n", + addrspec, gpg_strerror (err)); + } err = gpg_error (GPG_ERR_FALSE); - log_inc_errorcount (); + if (!opt.with_colons) + log_inc_errorcount (); } goto leave; } - if (opt.verbose) + + if (opt.verbose && !opt.with_colons) log_info ("provider for '%s' supports WKS\n", addrspec); leave: + wks_free_policy (policy); + xfree (policy); xfree (submission_to); xfree (addrspec); return err; @@ -534,7 +750,7 @@ command_check (char *userid) uidinfo_list_t sl; int found = 0; - addrspec = mailbox_from_userid (userid); + addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); @@ -628,7 +844,7 @@ command_send (const char *fingerprint, const char *userid) estream_t keyenc = NULL; char *submission_to = NULL; mime_maker_t mime = NULL; - struct policy_flags_s policy; + policy_flags_t policy = NULL; int no_encrypt = 0; int posteo_hack = 0; const char *domain; @@ -636,18 +852,15 @@ command_send (const char *fingerprint, const char *userid) uidinfo_list_t uid, thisuid; time_t thistime; - memset (&policy, 0, sizeof policy); - if (classify_user_id (fingerprint, &desc, 1) - || !(desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR20)) + || desc.mode != KEYDB_SEARCH_MODE_FPR) { log_error (_("\"%s\" is not a fingerprint\n"), fingerprint); err = gpg_error (GPG_ERR_INV_NAME); goto leave; } - addrspec = mailbox_from_userid (userid); + addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); @@ -665,62 +878,26 @@ command_send (const char *fingerprint, const char *userid) /* Get the submission address. */ if (fake_submission_addr) { + policy = xcalloc (1, sizeof *policy); submission_to = xstrdup (fake_submission_addr); err = 0; } else { - /* We first try to get the submission address from the policy - * file (this is the new method). If both are available we - * check that they match and print a warning if not. In the - * latter case we keep on using the one from the - * submission-address file. */ - estream_t mbuf; - - err = wkd_get_policy_flags (addrspec, &mbuf); - if (err && gpg_err_code (err) != GPG_ERR_NO_DATA) - { - log_error ("error reading policy flags for '%s': %s\n", - domain, gpg_strerror (err)); - goto leave; - } - if (mbuf) - { - err = wks_parse_policy (&policy, mbuf, 1); - es_fclose (mbuf); - if (err) - goto leave; - } - - err = wkd_get_submission_address (addrspec, &submission_to); - if (err && !policy.submission_address) - { - log_error (_("error looking up submission address for domain '%s'" - ": %s\n"), domain, gpg_strerror (err)); - if (gpg_err_code (err) == GPG_ERR_NO_DATA) - log_error (_("this domain probably doesn't support WKS.\n")); - goto leave; - } - - if (submission_to && policy.submission_address - && ascii_strcasecmp (submission_to, policy.submission_address)) - log_info ("Warning: different submission addresses (sa=%s, po=%s)\n", - submission_to, policy.submission_address); - + err = get_policy_and_sa (addrspec, 0, &policy, &submission_to); + if (err) + goto leave; if (!submission_to) { - submission_to = xtrystrdup (policy.submission_address); - if (!submission_to) - { - err = gpg_error_from_syserror (); - goto leave; - } + log_error (_("this domain probably doesn't support WKS.\n")); + err = gpg_error (GPG_ERR_NO_DATA); + goto leave; } } log_info ("submitting request to '%s'\n", submission_to); - if (policy.auth_submit) + if (policy->auth_submit) log_info ("no confirmation required for '%s'\n", addrspec); /* In case the key has several uids with the same addr-spec we will @@ -738,8 +915,7 @@ command_send (const char *fingerprint, const char *userid) { if (!uid->mbox) continue; /* Should not happen anyway. */ - if (policy.mailbox_only - && ascii_strcasecmp (uid->uid, uid->mbox)) + if (policy->mailbox_only && ascii_strcasecmp (uid->uid, uid->mbox)) continue; /* UID has more than just the mailbox. */ if (uid->created > thistime) { @@ -770,7 +946,7 @@ command_send (const char *fingerprint, const char *userid) key = newkey; } - if (policy.mailbox_only + if (policy->mailbox_only && (!thisuid->mbox || ascii_strcasecmp (thisuid->uid, thisuid->mbox))) { log_info ("Warning: policy requires 'mailbox-only'" @@ -791,7 +967,7 @@ command_send (const char *fingerprint, const char *userid) /* Hack to support posteo but let them disable this by setting the * new policy-version flag. */ - if (policy.protocol_version < 3 + if (policy->protocol_version < 3 && !ascii_strcasecmp (domain, "posteo.de")) { log_info ("Warning: Using draft-1 method for domain '%s'\n", domain); @@ -908,7 +1084,8 @@ command_send (const char *fingerprint, const char *userid) free_uidinfo_list (uidlist); es_fclose (keyenc); es_fclose (key); - wks_free_policy (&policy); + wks_free_policy (policy); + xfree (policy); xfree (addrspec); return err; } @@ -973,6 +1150,7 @@ encrypt_response (estream_t *r_output, estream_t input, const char *addrspec, ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--armor"); + ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */ if (fake_submission_addr) ccparray_put (&ccp, "--auto-key-locate=clear,local"); else diff --git a/tools/gpg-wks-server.c b/tools/gpg-wks-server.c index a5881557f..f83ef6528 100644 --- a/tools/gpg-wks-server.c +++ b/tools/gpg-wks-server.c @@ -58,6 +58,7 @@ enum cmd_and_opt_values oQuiet = 'q', oVerbose = 'v', oOutput = 'o', + oDirectory = 'C', oDebug = 500, @@ -108,6 +109,7 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"), ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"), + ARGPARSE_s_s (oDirectory, "directory", "|DIR|use DIR as top directory"), ARGPARSE_s_s (oFrom, "from", "|ADDR|use ADDR as the default sender"), ARGPARSE_s_s (oHeader, "header" , "|NAME=VALUE|add \"NAME: VALUE\" as header to all mails"), @@ -155,8 +157,6 @@ static gpg_error_t command_receive_cb (void *opaque, const char *mediatype, estream_t fp, unsigned int flags); static gpg_error_t command_list_domains (void); -static gpg_error_t command_install_key (const char *fname, const char *userid); -static gpg_error_t command_remove_key (const char *mailaddr); static gpg_error_t command_revoke_key (const char *mailaddr); static gpg_error_t command_check_key (const char *mailaddr); static gpg_error_t command_cron (void); @@ -225,6 +225,9 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; + case oDirectory: + opt.directory = pargs->r.ret_str; + break; case oFrom: opt.default_from = pargs->r.ret_str; break; @@ -350,6 +353,7 @@ main (int argc, char **argv) { log_error ("directory '%s' has too relaxed permissions\n", opt.directory); + log_info ("Fix by running: chmod o-rw '%s'\n", opt.directory); exit (2); } } @@ -377,15 +381,18 @@ main (int argc, char **argv) break; case aInstallKey: - if (argc != 2) - wrong_args ("--install-key FILE USER-ID"); - err = command_install_key (*argv, argv[1]); + if (!argc) + err = wks_cmd_install_key (NULL, NULL); + else if (argc == 2) + err = wks_cmd_install_key (*argv, argv[1]); + else + wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]"); break; case aRemoveKey: if (argc != 1) wrong_args ("--remove-key USER-ID"); - err = command_remove_key (*argv); + err = wks_cmd_remove_key (*argv); break; case aRevokeKey: @@ -579,6 +586,7 @@ encrypt_stream (estream_t *r_output, estream_t input, const char *keyfile) ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--no-keyring"); ccparray_put (&ccp, "--armor"); + ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */ ccparray_put (&ccp, "--recipient-file"); ccparray_put (&ccp, keyfile); ccparray_put (&ccp, "--encrypt"); @@ -1340,81 +1348,6 @@ send_congratulation_message (const char *mbox, const char *keyfile) } -/* Write the content of SRC to the new file FNAME. */ -static gpg_error_t -write_to_file (estream_t src, const char *fname) -{ - gpg_error_t err; - estream_t dst; - char buffer[4096]; - size_t nread, written; - - dst = es_fopen (fname, "wb"); - if (!dst) - return gpg_error_from_syserror (); - - do - { - nread = es_fread (buffer, 1, sizeof buffer, src); - if (!nread) - break; - written = es_fwrite (buffer, 1, nread, dst); - if (written != nread) - break; - } - while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst)); - if (!es_feof (src) || es_ferror (src) || es_ferror (dst)) - { - err = gpg_error_from_syserror (); - es_fclose (dst); - gnupg_remove (fname); - return err; - } - - if (es_fclose (dst)) - { - err = gpg_error_from_syserror (); - log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); - return err; - } - - return 0; -} - - -/* Compute the the full file name for the key with ADDRSPEC and return - * it at R_FNAME. */ -static gpg_error_t -compute_hu_fname (char **r_fname, const char *addrspec) -{ - gpg_error_t err; - char *hash; - const char *domain; - char sha1buf[20]; - - *r_fname = NULL; - - domain = strchr (addrspec, '@'); - if (!domain || !domain[1] || domain == addrspec) - return gpg_error (GPG_ERR_INV_ARG); - domain++; - - gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1); - hash = zb32_encode (sha1buf, 8*20); - if (!hash) - return gpg_error_from_syserror (); - - *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); - if (!*r_fname) - err = gpg_error_from_syserror (); - else - err = 0; - - xfree (hash); - return err; -} - - /* Check that we have send a request with NONCE and publish the key. */ static gpg_error_t check_and_publish (server_ctx_t ctx, const char *address, const char *nonce) @@ -1489,7 +1422,7 @@ check_and_publish (server_ctx_t ctx, const char *address, const char *nonce) } /* Hash user ID and create filename. */ - err = compute_hu_fname (&fnewname, address); + err = wks_compute_hu_fname (&fnewname, address); if (err) goto leave; @@ -1667,7 +1600,7 @@ command_receive_cb (void *opaque, const char *mediatype, -/* Return a list of all configured domains. ECh list element is the +/* Return a list of all configured domains. Each list element is the * top directory for the domain. To figure out the actual domain * name strrchr(name, '/') can be used. */ static gpg_error_t @@ -1946,7 +1879,17 @@ command_list_domains (void) if (!fp) { err = gpg_error_from_syserror (); - if (gpg_err_code (err) != GPG_ERR_ENOENT) + if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + fp = es_fopen (fname, "w"); + if (!fp) + log_error ("domain %s: can't create policy file: %s\n", + domain, gpg_strerror (err)); + else + es_fclose (fp); + fp = NULL; + } + else log_error ("domain %s: error in policy file: %s\n", domain, gpg_strerror (err)); } @@ -1955,17 +1898,8 @@ command_list_domains (void) struct policy_flags_s policy; err = wks_parse_policy (&policy, fp, 0); es_fclose (fp); - if (!err) - { - struct policy_flags_s empty_policy; - memset (&empty_policy, 0, sizeof empty_policy); - if (!memcmp (&empty_policy, &policy, sizeof policy)) - log_error ("domain %s: empty policy file\n", domain); - } wks_free_policy (&policy); } - - } err = 0; @@ -1997,195 +1931,6 @@ command_cron (void) } -/* Install a single key into the WKD by reading FNAME and extracting - * USERID. */ -static gpg_error_t -command_install_key (const char *fname, const char *userid) -{ - gpg_error_t err; - KEYDB_SEARCH_DESC desc; - estream_t fp = NULL; - char *addrspec = NULL; - char *fpr = NULL; - uidinfo_list_t uidlist = NULL; - uidinfo_list_t uid, thisuid; - time_t thistime; - char *huname = NULL; - int any; - - addrspec = mailbox_from_userid (userid); - if (!addrspec) - { - log_error ("\"%s\" is not a proper mail address\n", userid); - err = gpg_error (GPG_ERR_INV_USER_ID); - goto leave; - } - - if (!classify_user_id (fname, &desc, 1) - && (desc.mode == KEYDB_SEARCH_MODE_FPR - || desc.mode == KEYDB_SEARCH_MODE_FPR20)) - { - /* FNAME looks like a fingerprint. Get the key from the - * standard keyring. */ - err = wks_get_key (&fp, fname, addrspec, 0); - if (err) - { - log_error ("error getting key '%s' (uid='%s'): %s\n", - fname, addrspec, gpg_strerror (err)); - goto leave; - } - } - else /* Take it from the file */ - { - fp = es_fopen (fname, "rb"); - if (!fp) - { - err = gpg_error_from_syserror (); - log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); - goto leave; - } - } - - /* List the key so that we can figure out the newest UID with the - * requested addrspec. */ - err = wks_list_key (fp, &fpr, &uidlist); - if (err) - { - log_error ("error parsing key: %s\n", gpg_strerror (err)); - err = gpg_error (GPG_ERR_NO_PUBKEY); - goto leave; - } - thistime = 0; - thisuid = NULL; - any = 0; - for (uid = uidlist; uid; uid = uid->next) - { - if (!uid->mbox) - continue; /* Should not happen anyway. */ - if (ascii_strcasecmp (uid->mbox, addrspec)) - continue; /* Not the requested addrspec. */ - any = 1; - if (uid->created > thistime) - { - thistime = uid->created; - thisuid = uid; - } - } - if (!thisuid) - thisuid = uidlist; /* This is the case for a missing timestamp. */ - if (!any) - { - log_error ("public key in '%s' has no mail address '%s'\n", - fname, addrspec); - err = gpg_error (GPG_ERR_INV_USER_ID); - goto leave; - } - - if (opt.verbose) - log_info ("using key with user id '%s'\n", thisuid->uid); - - { - estream_t fp2; - - es_rewind (fp); - err = wks_filter_uid (&fp2, fp, thisuid->uid, 1); - if (err) - { - log_error ("error filtering key: %s\n", gpg_strerror (err)); - err = gpg_error (GPG_ERR_NO_PUBKEY); - goto leave; - } - es_fclose (fp); - fp = fp2; - } - - /* Hash user ID and create filename. */ - err = compute_hu_fname (&huname, addrspec); - if (err) - goto leave; - - /* Publish. */ - err = write_to_file (fp, huname); - if (err) - { - log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err)); - goto leave; - } - - /* Make sure it is world readable. */ - if (gnupg_chmod (huname, "-rwxr--r--")) - log_error ("can't set permissions of '%s': %s\n", - huname, gpg_strerror (gpg_err_code_from_syserror())); - - if (!opt.quiet) - log_info ("key %s published for '%s'\n", fpr, addrspec); - - leave: - xfree (huname); - free_uidinfo_list (uidlist); - xfree (fpr); - xfree (addrspec); - es_fclose (fp); - return err; -} - - -/* Return the filename and optionally the addrspec for USERID at - * R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. */ -static gpg_error_t -fname_from_userid (const char *userid, char **r_fname, char **r_addrspec) -{ - gpg_error_t err; - char *addrspec = NULL; - const char *domain; - char *hash = NULL; - const char *s; - char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */ - - *r_fname = NULL; - if (r_addrspec) - *r_addrspec = NULL; - - addrspec = mailbox_from_userid (userid); - if (!addrspec) - { - if (opt.verbose) - log_info ("\"%s\" is not a proper mail address\n", userid); - err = gpg_error (GPG_ERR_INV_USER_ID); - goto leave; - } - - domain = strchr (addrspec, '@'); - log_assert (domain); - domain++; - - /* Hash user ID and create filename. */ - s = strchr (addrspec, '@'); - log_assert (s); - gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, addrspec, s - addrspec); - hash = zb32_encode (shaxbuf, 8*20); - if (!hash) - { - err = gpg_error_from_syserror (); - goto leave; - } - - *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); - if (!*r_fname) - err = gpg_error_from_syserror (); - else - err = 0; - - leave: - if (r_addrspec && addrspec) - *r_addrspec = addrspec; - else - xfree (addrspec); - xfree (hash); - return err; -} - - /* Check whether the key with USER_ID is installed. */ static gpg_error_t command_check_key (const char *userid) @@ -2194,7 +1939,7 @@ command_check_key (const char *userid) char *addrspec = NULL; char *fname = NULL; - err = fname_from_userid (userid, &fname, &addrspec); + err = wks_fname_from_userid (userid, &fname, &addrspec); if (err) goto leave; @@ -2229,49 +1974,11 @@ command_check_key (const char *userid) } -/* Remove the key with mail address in USERID. */ -static gpg_error_t -command_remove_key (const char *userid) -{ - gpg_error_t err; - char *addrspec = NULL; - char *fname = NULL; - - err = fname_from_userid (userid, &fname, &addrspec); - if (err) - goto leave; - - if (gnupg_remove (fname)) - { - err = gpg_error_from_syserror (); - if (gpg_err_code (err) == GPG_ERR_ENOENT) - { - if (!opt.quiet) - log_info ("key for '%s' is not installed\n", addrspec); - log_inc_errorcount (); - err = 0; - } - else - log_error ("error removing '%s': %s\n", fname, gpg_strerror (err)); - goto leave; - } - - if (opt.verbose) - log_info ("key for '%s' removed\n", addrspec); - err = 0; - - leave: - xfree (fname); - xfree (addrspec); - return err; -} - - /* Revoke the key with mail address MAILADDR. */ static gpg_error_t command_revoke_key (const char *mailaddr) { /* Remove should be different from removing but we have not yet * defined a suitable way to do this. */ - return command_remove_key (mailaddr); + return wks_cmd_remove_key (mailaddr); } diff --git a/tools/gpg-wks.h b/tools/gpg-wks.h index 1b91b6504..e36943090 100644 --- a/tools/gpg-wks.h +++ b/tools/gpg-wks.h @@ -36,6 +36,7 @@ struct unsigned int debug; int quiet; int use_sendmail; + int with_colons; const char *output; const char *gpg_program; const char *directory; @@ -97,6 +98,13 @@ gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown); void wks_free_policy (policy_flags_t policy); +gpg_error_t wks_fname_from_userid (const char *userid, + char **r_fname, char **r_addrspec); +gpg_error_t wks_compute_hu_fname (char **r_fname, const char *addrspec); +gpg_error_t wks_cmd_install_key (const char *fname, const char *userid); +gpg_error_t wks_cmd_remove_key (const char *userid); + + /*-- wks-receive.c --*/ /* Flag values for the receive callback. */ diff --git a/tools/gpg-zip.in b/tools/gpg-zip.in deleted file mode 100644 index 48c4766b1..000000000 --- a/tools/gpg-zip.in +++ /dev/null @@ -1,148 +0,0 @@ -#!/bin/sh - -# gpg-archive - gpg-ized tar using the same format as PGP's PGP Zip. -# Copyright (C) 2005 Free Software Foundation, Inc. -# -# This file is part of GnuPG. -# -# GnuPG is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# GnuPG is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, see <http://www.gnu.org/licenses/>. -# Despite the name, PGP Zip format is actually an OpenPGP-wrapped tar -# file. To be compatible with PGP itself, this must be a USTAR format -# tar file. Unclear on whether there is a distinction here between -# the GNU or POSIX variant of USTAR. - -VERSION=@VERSION@ -TAR=@TAR@ -GPG=gpg - -usage="\ -Usage: gpg-zip [--help] [--version] [--encrypt] [--decrypt] [--symmetric] - [--list-archive] [--output FILE] [--gpg GPG] [--gpg-args ARGS] - [--tar TAR] [--tar-args ARGS] filename1 [filename2, ...] - directory1 [directory2, ...] - -Encrypt or sign files into an archive." - -tar_verbose_opt="v" - -while test $# -gt 0 ; do - case $1 in - -h | --help | --h*) - echo "$usage" - exit 0 - ;; - --list-archive) - list=yes - create=no - unpack=no - shift - ;; - --encrypt | -e) - gpg_args="$gpg_args --encrypt" - list=no - create=yes - unpack=no - shift - ;; - --decrypt | -d) - gpg_args="$gpg_args --decrypt" - list=no - create=no - unpack=yes - shift - ;; - --symmetric | -c) - gpg_args="$gpg_args --symmetric" - list=no - create=yes - unpack=no - shift - ;; - --sign | -s) - gpg_args="$gpg_args --sign" - list=no - create=yes - unpack=no - shift - ;; - --recipient | -r) - gpg_args="$gpg_args --recipient $2" - shift - shift - ;; - --local-user | -u) - gpg_args="$gpg_args --local-user $2" - shift - shift - ;; - --output | -o) - gpg_args="$gpg_args --output $2" - shift - shift - ;; - --version) - echo "gpg-zip (GnuPG) $VERSION" - exit 0 - ;; - --gpg) - GPG=$2 - shift - shift - ;; - --gpg-args) - gpg_args="$gpg_args $2" - shift - shift - ;; - --tar) - TAR=$2 - shift - shift - ;; - --tar-args) - tar_args="$tar_args $2" - shift - shift - ;; - --quiet) - tar_verbose_opt="" - shift - ;; - --) - shift - break - ;; - -*) - echo "$usage" 1>&2 - exit 1 - ;; - *) - break - ;; - esac -done - -if test x$create = xyes ; then -# echo "$TAR $tar_args -cf - "$@" | $GPG --set-filename x.tar $gpg_args" 1>&2 - $TAR $tar_args -cf - "$@" | $GPG --set-filename x.tar $gpg_args -elif test x$list = xyes ; then -# echo "cat \"$1\" | $GPG $gpg_args | $TAR $tar_args -tf -" 1>&2 - cat "$1" | $GPG $gpg_args | $TAR $tar_args -tf - -elif test x$unpack = xyes ; then -# echo "cat \"$1\" | $GPG $gpg_args | $TAR $tar_args -xvf -" 1>&2 - cat "$1" | $GPG $gpg_args | $TAR $tar_args -x${tar_verbose_opt}f - -else - echo "$usage" 1>&2 - exit 1 -fi diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 924f90785..2ae79d91d 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -1066,34 +1066,6 @@ static gc_option_t gc_options_pinentry[] = -/* Component system. Each component is a set of options that can be - configured at the same time. If you change this, don't forget to - update GC_COMPONENT below. */ -typedef enum - { - /* The classic GPG for OpenPGP. */ - GC_COMPONENT_GPG, - - /* The GPG Agent. */ - GC_COMPONENT_GPG_AGENT, - - /* The Smardcard Daemon. */ - GC_COMPONENT_SCDAEMON, - - /* GPG for S/MIME. */ - GC_COMPONENT_GPGSM, - - /* The LDAP Directory Manager for CRLs. */ - GC_COMPONENT_DIRMNGR, - - /* The external Pinentry. */ - GC_COMPONENT_PINENTRY, - - /* The number of components. */ - GC_COMPONENT_NR - } gc_component_t; - - /* The information associated with each component. */ static const struct { diff --git a/tools/gpgconf.c b/tools/gpgconf.c index 59085d8b5..b67125b89 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -47,6 +47,7 @@ enum cmd_and_opt_values oHomedir, oBuilddir, oStatusFD, + oShowSocket, aListComponents, aCheckPrograms, @@ -108,6 +109,7 @@ static ARGPARSE_OPTS opts[] = { oBuilddir, "build-prefix", 2, "@" }, { oNull, "null", 0, "@" }, { oNoVerbose, "no-verbose", 0, "@"}, + ARGPARSE_s_n (oShowSocket, "show-socket", "@"), ARGPARSE_end(), }; @@ -525,6 +527,7 @@ main (int argc, char **argv) int no_more_options = 0; enum cmd_and_opt_values cmd = 0; estream_t outfp = NULL; + int show_socket = 0; early_system_init (); gnupg_reopen_std (GPGCONF_NAME); @@ -558,6 +561,7 @@ main (int argc, char **argv) case oStatusFD: set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); break; + case oShowSocket: show_socket = 1; break; case aListDirs: case aListComponents: @@ -682,7 +686,22 @@ main (int argc, char **argv) } else if (cmd == aLaunch) { - if (gc_component_launch (idx)) + err = gc_component_launch (idx); + if (show_socket) + { + char *names[2]; + + if (idx == GC_COMPONENT_GPG_AGENT) + names[0] = "agent-socket"; + else if (idx == GC_COMPONENT_DIRMNGR) + names[0] = "dirmngr-socket"; + else + names[0] = NULL; + names[1] = NULL; + get_outfp (&outfp); + list_dirs (outfp, names); + } + if (err) gpgconf_failure (0); } else @@ -823,7 +842,7 @@ main (int argc, char **argv) ; else if (rmdir (socketdir)) { - /* If the director is not empty we first try to delet + /* If the director is not empty we first try to delete * socket files. */ err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOTEMPTY diff --git a/tools/gpgconf.h b/tools/gpgconf.h index 8a061ef68..192259789 100644 --- a/tools/gpgconf.h +++ b/tools/gpgconf.h @@ -43,6 +43,34 @@ void gpgconf_failure (gpg_error_t err) GPGRT_ATTR_NORETURN; /*-- gpgconf-comp.c --*/ +/* Component system. Each component is a set of options that can be + * configured at the same time. If you change this, don't forget to + * update GC_COMPONENT in gpgconf-comp.c. */ +typedef enum + { + /* The classic GPG for OpenPGP. */ + GC_COMPONENT_GPG, + + /* The GPG Agent. */ + GC_COMPONENT_GPG_AGENT, + + /* The Smardcard Daemon. */ + GC_COMPONENT_SCDAEMON, + + /* GPG for S/MIME. */ + GC_COMPONENT_GPGSM, + + /* The LDAP Directory Manager for CRLs. */ + GC_COMPONENT_DIRMNGR, + + /* The external Pinentry. */ + GC_COMPONENT_PINENTRY, + + /* The number of components. */ + GC_COMPONENT_NR + } gc_component_t; + + /* Initialize the components. */ void gc_components_init (void); diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c index c622a6672..a08601634 100644 --- a/tools/gpgtar-create.c +++ b/tools/gpgtar-create.c @@ -762,6 +762,14 @@ gpgtar_create (char **inpattern, int encrypt, int sign) memset (scanctrl, 0, sizeof *scanctrl); scanctrl->flist_tail = &scanctrl->flist; + if (opt.directory && gnupg_chdir (opt.directory)) + { + err = gpg_error_from_syserror (); + log_error ("chdir to '%s' failed: %s\n", + opt.directory, gpg_strerror (err)); + return err; + } + while (!eof_seen) { char *pat, *p; diff --git a/tools/gpgtar-extract.c b/tools/gpgtar-extract.c index 8613d193f..3da100c07 100644 --- a/tools/gpgtar-extract.c +++ b/tools/gpgtar-extract.c @@ -36,7 +36,7 @@ static gpg_error_t extract_regular (estream_t stream, const char *dirname, - tar_header_t hdr) + tarinfo_t info, tar_header_t hdr) { gpg_error_t err; char record[RECORDSIZE]; @@ -70,6 +70,7 @@ extract_regular (estream_t stream, const char *dirname, err = read_record (stream, record); if (err) goto leave; + info->nblocks++; n++; if (n < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE))) nbytes = RECORDSIZE; @@ -163,7 +164,8 @@ extract_directory (const char *dirname, tar_header_t hdr) static gpg_error_t -extract (estream_t stream, const char *dirname, tar_header_t hdr) +extract (estream_t stream, const char *dirname, tarinfo_t info, + tar_header_t hdr) { gpg_error_t err; size_t n; @@ -190,7 +192,7 @@ extract (estream_t stream, const char *dirname, tar_header_t hdr) } if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN) - err = extract_regular (stream, dirname, hdr); + err = extract_regular (stream, dirname, info, hdr); else if (hdr->typeflag == TF_DIRECTORY) err = extract_directory (dirname, hdr); else @@ -200,7 +202,11 @@ extract (estream_t stream, const char *dirname, tar_header_t hdr) log_info ("unsupported file type %d for '%s' - skipped\n", (int)hdr->typeflag, hdr->name); for (err = 0, n=0; !err && n < hdr->nrecords; n++) - err = read_record (stream, record); + { + err = read_record (stream, record); + if (!err) + info->nblocks++; + } } return err; } @@ -282,6 +288,10 @@ gpgtar_extract (const char *filename, int decrypt) tar_header_t header = NULL; const char *dirprefix = NULL; char *dirname = NULL; + struct tarinfo_s tarinfo_buffer; + tarinfo_t tarinfo = &tarinfo_buffer; + + memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer); if (filename) { @@ -378,11 +388,11 @@ gpgtar_extract (const char *filename, int decrypt) for (;;) { - err = gpgtar_read_header (stream, &header); + err = gpgtar_read_header (stream, tarinfo, &header); if (err || header == NULL) goto leave; - err = extract (stream, dirname, header); + err = extract (stream, dirname, tarinfo, header); if (err) goto leave; xfree (header); diff --git a/tools/gpgtar-list.c b/tools/gpgtar-list.c index 0e10be8a0..396e837f4 100644 --- a/tools/gpgtar-list.c +++ b/tools/gpgtar-list.c @@ -77,12 +77,15 @@ parse_xoctal (const void *data, size_t length, const char *filename) static tar_header_t -parse_header (const void *record, const char *filename) +parse_header (const void *record, const char *filename, tarinfo_t info) { const struct ustar_raw_header *raw = record; size_t n, namelen, prefixlen; tar_header_t header; int use_prefix; + int anyerror = 0; + + info->headerblock = info->nblocks - 1; use_prefix = (!memcmp (raw->magic, "ustar", 5) && (raw->magic[5] == ' ' || !raw->magic[5])); @@ -91,27 +94,31 @@ parse_header (const void *record, const char *filename) for (namelen=0; namelen < sizeof raw->name && raw->name[namelen]; namelen++) ; if (namelen == sizeof raw->name) - log_info ("%s: warning: name not terminated by a nul byte\n", filename); + { + log_info ("%s: warning: name not terminated by a nul\n", filename); + anyerror = 1; + } for (n=namelen+1; n < sizeof raw->name; n++) if (raw->name[n]) { log_info ("%s: warning: garbage after name\n", filename); + anyerror = 1; break; } - if (use_prefix && raw->prefix[0]) { for (prefixlen=0; (prefixlen < sizeof raw->prefix && raw->prefix[prefixlen]); prefixlen++) ; if (prefixlen == sizeof raw->prefix) - log_info ("%s: warning: prefix not terminated by a nul byte\n", - filename); + log_info ("%s: warning: prefix not terminated by a nul (block %llu)\n", + filename, info->headerblock); for (n=prefixlen+1; n < sizeof raw->prefix; n++) if (raw->prefix[n]) { log_info ("%s: warning: garbage after prefix\n", filename); + anyerror = 1; break; } } @@ -156,25 +163,32 @@ parse_header (const void *record, const char *filename) default: header->typeflag = TF_UNKNOWN; break; } - /* Compute the number of data records following this header. */ if (header->typeflag == TF_REGULAR || header->typeflag == TF_UNKNOWN) header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE; else header->nrecords = 0; + if (anyerror) + { + log_info ("%s: header block %llu is corrupt" + " (size=%llu type=%d nrec=%llu)\n", + filename, info->headerblock, + header->size, header->typeflag, header->nrecords); + /* log_printhex (record, RECORDSIZE, " "); */ + } return header; } -/* Read the next block, assming it is a tar header. Returns a header +/* Read the next block, assuming it is a tar header. Returns a header object on success in R_HEADER, or an error. If the stream is consumed, R_HEADER is set to NULL. In case of an error an error message has been printed. */ static gpg_error_t -read_header (estream_t stream, tar_header_t *r_header) +read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header) { gpg_error_t err; char record[RECORDSIZE]; @@ -183,6 +197,7 @@ read_header (estream_t stream, tar_header_t *r_header) err = read_record (stream, record); if (err) return err; + info->nblocks++; for (i=0; i < RECORDSIZE && !record[i]; i++) ; @@ -193,6 +208,7 @@ read_header (estream_t stream, tar_header_t *r_header) err = read_record (stream, record); if (err) return err; + info->nblocks++; for (i=0; i < RECORDSIZE && !record[i]; i++) ; @@ -207,7 +223,7 @@ read_header (estream_t stream, tar_header_t *r_header) } } - *r_header = parse_header (record, es_fname_get (stream)); + *r_header = parse_header (record, es_fname_get (stream), info); return *r_header ? 0 : gpg_error_from_syserror (); } @@ -215,7 +231,7 @@ read_header (estream_t stream, tar_header_t *r_header) /* Skip the data records according to HEADER. Prints an error message on error and return -1. */ static int -skip_data (estream_t stream, tar_header_t header) +skip_data (estream_t stream, tarinfo_t info, tar_header_t header) { char record[RECORDSIZE]; unsigned long long n; @@ -224,6 +240,7 @@ skip_data (estream_t stream, tar_header_t header) { if (read_record (stream, record)) return -1; + info->nblocks++; } return 0; @@ -278,6 +295,10 @@ gpgtar_list (const char *filename, int decrypt) estream_t stream; estream_t cipher_stream = NULL; tar_header_t header = NULL; + struct tarinfo_s tarinfo_buffer; + tarinfo_t tarinfo = &tarinfo_buffer; + + memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer); if (filename) { @@ -339,13 +360,13 @@ gpgtar_list (const char *filename, int decrypt) for (;;) { - err = read_header (stream, &header); + err = read_header (stream, tarinfo, &header); if (err || header == NULL) goto leave; print_header (header, es_stdout); - if (skip_data (stream, header)) + if (skip_data (stream, tarinfo, header)) goto leave; xfree (header); header = NULL; @@ -362,9 +383,9 @@ gpgtar_list (const char *filename, int decrypt) } gpg_error_t -gpgtar_read_header (estream_t stream, tar_header_t *r_header) +gpgtar_read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header) { - return read_header (stream, r_header); + return read_header (stream, info, r_header); } void diff --git a/tools/gpgtar.c b/tools/gpgtar.c index 2757ab011..b33aa6d0f 100644 --- a/tools/gpgtar.c +++ b/tools/gpgtar.c @@ -112,7 +112,7 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_group (302, N_("@\nTar options:\n ")), ARGPARSE_s_s (oDirectory, "directory", - N_("|DIRECTORY|extract files into DIRECTORY")), + N_("|DIRECTORY|change to DIRECTORY first")), ARGPARSE_s_s (oFilesFrom, "files-from", N_("|FILE|get names to create from FILE")), ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")), @@ -136,6 +136,14 @@ static ARGPARSE_OPTS tar_opts[] = { }; +/* Global flags. */ +enum cmd_and_opt_values cmd = 0; +int skip_crypto = 0; +const char *files_from = NULL; +int null_names = 0; + + + /* Print usage information and provide strings for help. */ static const char * @@ -169,23 +177,25 @@ my_strusage( int level ) static void set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd) { - enum cmd_and_opt_values cmd = *ret_cmd; - - if (!cmd || cmd == new_cmd) - cmd = new_cmd; - else if (cmd == aSign && new_cmd == aEncrypt) - cmd = aSignEncrypt; - else if (cmd == aEncrypt && new_cmd == aSign) - cmd = aSignEncrypt; + enum cmd_and_opt_values c = *ret_cmd; + + if (!c || c == new_cmd) + c = new_cmd; + else if (c == aSign && new_cmd == aEncrypt) + c = aSignEncrypt; + else if (c == aEncrypt && new_cmd == aSign) + c = aSignEncrypt; else { log_error (_("conflicting commands\n")); exit (2); } - *ret_cmd = cmd; + *ret_cmd = c; } + + /* Shell-like argument splitting. For compatibility with gpg-zip we accept arguments for GnuPG and @@ -287,14 +297,9 @@ shell_parse_argv (const char *s, int *r_argc, char ***r_argv) gpgrt_annotate_leaked_object (*r_argv); return 0; } - -/* Global flags. */ -enum cmd_and_opt_values cmd = 0; -int skip_crypto = 0; -const char *files_from = NULL; -int null_names = 0; + /* Command line parsing. */ static void parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) diff --git a/tools/gpgtar.h b/tools/gpgtar.h index 8cbe80bbb..1a1b913d7 100644 --- a/tools/gpgtar.h +++ b/tools/gpgtar.h @@ -41,12 +41,21 @@ struct } opt; +/* An info structure to avoid global variables. */ +struct tarinfo_s +{ + unsigned long long nblocks; /* Count of processed blocks. */ + unsigned long long headerblock; /* Number of current header block. */ +}; +typedef struct tarinfo_s *tarinfo_t; + + /* The size of a tar record. All IO is done in chunks of this size. Note that we don't care about blocking because this version of tar is not expected to be used directly on a tape drive in fact it is used in a pipeline with GPG and thus any blocking would be useless. */ -#define RECORDSIZE 512 +#define RECORDSIZE 512 /* Description of the USTAR header format. */ @@ -64,16 +73,16 @@ struct ustar_raw_header char magic[6]; char version[2]; char uname[32]; - char gname[32]; - char devmajor[8]; + char gname[32]; + char devmajor[8]; char devminor[8]; - char prefix[155]; + char prefix[155]; char pad[12]; }; /* Filetypes as defined by USTAR. */ -typedef enum +typedef enum { TF_REGULAR, TF_HARDLINK, @@ -88,12 +97,12 @@ typedef enum } typeflag_t; -/* The internal represenation of a TAR header. */ +/* The internal representation of a TAR header. */ struct tar_header_s; typedef struct tar_header_s *tar_header_t; struct tar_header_s { - tar_header_t next; /* Used to build a linked list iof entries. */ + tar_header_t next; /* Used to build a linked list of entries. */ unsigned long mode; /* The file mode. */ unsigned long nlink; /* Number of hard links. */ @@ -106,7 +115,7 @@ struct tar_header_s that 32 bit and thus allows tracking times beyond 2106. */ typeflag_t typeflag; /* The type of the file. */ - + unsigned long long nrecords; /* Number of data records. */ @@ -126,7 +135,8 @@ gpg_error_t gpgtar_extract (const char *filename, int decrypt); /*-- gpgtar-list.c --*/ gpg_error_t gpgtar_list (const char *filename, int decrypt); -gpg_error_t gpgtar_read_header (estream_t stream, tar_header_t *r_header); +gpg_error_t gpgtar_read_header (estream_t stream, tarinfo_t info, + tar_header_t *r_header); void gpgtar_print_header (tar_header_t header, estream_t out); diff --git a/tools/mime-maker.c b/tools/mime-maker.c index 0edc14d78..91eab8258 100644 --- a/tools/mime-maker.c +++ b/tools/mime-maker.c @@ -25,14 +25,10 @@ #include "../common/util.h" #include "../common/zb32.h" +#include "rfc822parse.h" #include "mime-maker.h" -/* All valid characters in a header name. */ -#define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \ - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ - "-01234567890") - /* An object to store an header. Also used for a list of headers. */ struct header_s { @@ -269,38 +265,6 @@ ensure_part (mime_maker_t ctx, part_t *r_parent) } -/* Transform a header name into a standard capitalized format. - * "Content-Type". Conversion stops at the colon. */ -static void -capitalize_header_name (char *name) -{ - unsigned char *p = name; - int first = 1; - - /* Special cases first. */ - if (!ascii_strcasecmp (name, "MIME-Version")) - { - strcpy (name, "MIME-Version"); - return; - } - - /* Regular cases. */ - for (; *p && *p != ':'; p++) - { - if (*p == '-') - first = 1; - else if (first) - { - if (*p >= 'a' && *p <= 'z') - *p = *p - 'a' + 'A'; - first = 0; - } - else if (*p >= 'A' && *p <= 'Z') - *p = *p - 'A' + 'a'; - } -} - - /* Check whether a header with NAME has already been set into PART. * NAME must be in canonical capitalized format. Return true or * false. */ @@ -344,17 +308,14 @@ add_header (part_t part, const char *name, const char *value) memcpy (hdr->name, name, namelen); hdr->name[namelen] = 0; - /* Check that the header name is valid. We allow all lower and - * uppercase letters and, except for the first character, digits and - * the dash. */ - if (strspn (hdr->name, HEADER_NAME_CHARS) != namelen - || strchr ("-0123456789", *hdr->name)) + /* Check that the header name is valid. */ + if (!rfc822_valid_header_name_p (hdr->name)) { xfree (hdr); return gpg_error (GPG_ERR_INV_NAME); } - capitalize_header_name (hdr->name); + rfc822_capitalize_header_name (hdr->name); hdr->value = xtrystrdup (value); if (!hdr->value) { diff --git a/tools/mime-parser.c b/tools/mime-parser.c index a151dc65e..98f27105c 100644 --- a/tools/mime-parser.c +++ b/tools/mime-parser.c @@ -50,7 +50,7 @@ struct mime_parser_context_s { void *cookie; /* Cookie passed to all callbacks. */ - /* The callback to announce the transation from header to body. */ + /* The callback to announce the transition from header to body. */ gpg_error_t (*t2body) (void *cookie, int level); /* The callback to announce a new part. */ diff --git a/tools/no-libgcrypt.c b/tools/no-libgcrypt.c index 873996889..3b577567a 100644 --- a/tools/no-libgcrypt.c +++ b/tools/no-libgcrypt.c @@ -114,7 +114,7 @@ gcry_free (void *a) /* We need this dummy because exechelp.c uses gcry_control to - terminate the secure memeory. */ + terminate the secure memory. */ gcry_error_t gcry_control (enum gcry_ctl_cmds cmd, ...) { diff --git a/tools/rfc822parse.c b/tools/rfc822parse.c index e8cdb0215..f1e95bd34 100644 --- a/tools/rfc822parse.c +++ b/tools/rfc822parse.c @@ -41,6 +41,12 @@ #include "rfc822parse.h" +/* All valid characters in a header name. */ +#define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "-01234567890") + + enum token_type { tSPACE, @@ -90,7 +96,7 @@ struct rfc822parse_context void *callback_value; int callback_error; int in_body; - int in_preamble; /* Wether we are before the first boundary. */ + int in_preamble; /* Whether we are before the first boundary. */ part_t parts; /* The tree of parts. */ part_t current_part; /* Whom we are processing (points into parts). */ const char *boundary; /* Current boundary. */ @@ -131,28 +137,31 @@ lowercase_string (unsigned char *string) *string = *string - 'A' + 'a'; } -/* Transform a header name into a standard capitalized format; i.e - "Content-Type". Conversion stops at the colon. As usual we don't - use the localized versions of ctype.h. - */ -static void -capitalize_header_name (unsigned char *name) + +static int +my_toupper (int c) { - int first = 1; + if (c >= 'a' && c <= 'z') + c &= ~0x20; + return c; +} + +/* This is the same as ascii_strcasecmp. */ +static int +my_strcasecmp (const char *a, const char *b) +{ + if (a == b) + return 0; - for (; *name && *name != ':'; name++) - if (*name == '-') - first = 1; - else if (first) - { - if (*name >= 'a' && *name <= 'z') - *name = *name - 'a' + 'A'; - first = 0; - } - else if (*name >= 'A' && *name <= 'Z') - *name = *name - 'A' + 'a'; + for (; *a && *b; a++, b++) + { + if (*a != *b && my_toupper(*a) != my_toupper(*b)) + break; + } + return *a == *b? 0 : (my_toupper (*a) - my_toupper (*b)); } + #ifndef HAVE_STPCPY static char * my_stpcpy (char *a,const char *b) @@ -167,7 +176,7 @@ my_stpcpy (char *a,const char *b) #endif -/* If a callback has been registerd, call it for the event of type +/* If a callback has been registered, call it for the event of type EVENT. */ static int do_callback (rfc822parse_t msg, rfc822parse_event_t event) @@ -228,6 +237,62 @@ release_handle_data (rfc822parse_t msg) } +/* Check that the header name is valid. We allow all lower and + * uppercase letters and, except for the first character, digits and + * the dash. The check stops at the first colon or at string end. + * Returns true if the name is valid. */ +int +rfc822_valid_header_name_p (const char *name) +{ + const char *s; + size_t namelen; + + if ((s=strchr (name, ':'))) + namelen = s - name; + else + namelen = strlen (name); + + if (!namelen + || strspn (name, HEADER_NAME_CHARS) != namelen + || strchr ("-0123456789", *name)) + return 0; + return 1; +} + + +/* Transform a header NAME into a standard capitalized format. + * Conversion stops at the colon. */ +void +rfc822_capitalize_header_name (char *name) +{ + unsigned char *p = name; + int first = 1; + + /* Special cases first. */ + if (!my_strcasecmp (name, "MIME-Version")) + { + strcpy (name, "MIME-Version"); + return; + } + + /* Regular cases. */ + for (; *p && *p != ':'; p++) + { + if (*p == '-') + first = 1; + else if (first) + { + if (*p >= 'a' && *p <= 'z') + *p = *p - 'a' + 'A'; + first = 0; + } + else if (*p >= 'A' && *p <= 'Z') + *p = *p - 'A' + 'a'; + } +} + + + /* Create a new parsing context for an entire rfc822 message and return it. CB and CB_VALUE may be given to callback for certain events. NULL is returned on error with errno set appropriately. */ @@ -432,7 +497,7 @@ insert_header (rfc822parse_t msg, const unsigned char *line, size_t length) /* Transform a field name into canonical format. */ if (!hdr->cont && strchr (line, ':')) - capitalize_header_name (hdr->line); + rfc822_capitalize_header_name (hdr->line); *msg->current_part->hdr_lines_tail = hdr; msg->current_part->hdr_lines_tail = &hdr->next; @@ -578,7 +643,7 @@ rfc822parse_get_field (rfc822parse_t msg, const char *name, int which, /**************** * Enumerate all header. Caller has to provide the address of a pointer - * which has to be initialzed to NULL, the caller should then never change this + * which has to be initialized to NULL, the caller should then never change this * pointer until he has closed the enumeration by passing again the address * of the pointer but with msg set to NULL. * The function returns pointers to all the header lines or NULL when @@ -616,7 +681,7 @@ rfc822parse_enum_header_lines (rfc822parse_t msg, void **context) * >0 : Retrieve the n-th field * RPREV may be used to return the predecessor of the returned field; - * which may be NULL for the very first one. It has to be initialzed + * which may be NULL for the very first one. It has to be initialized * to either NULL in which case the search start at the first header line, * or it may point to a headerline, where the search should start */ @@ -1013,7 +1078,7 @@ is_parameter (TOKEN t) parse context is valid; NULL is returned in case that attr is not defined in the header, a missing value is reppresented by an empty string. - With LOWER_VALUE set to true, a matching field valuebe be + With LOWER_VALUE set to true, a matching field value will be lowercased. Note, that ATTR should be lowercase. diff --git a/tools/rfc822parse.h b/tools/rfc822parse.h index 177d8271a..e2f2bedac 100644 --- a/tools/rfc822parse.h +++ b/tools/rfc822parse.h @@ -48,6 +48,8 @@ typedef int (*rfc822parse_cb_t) (void *opaque, rfc822parse_event_t event, rfc822parse_t msg); +int rfc822_valid_header_name_p (const char *name); +void rfc822_capitalize_header_name (char *name); rfc822parse_t rfc822parse_open (rfc822parse_cb_t cb, void *opaque_value); diff --git a/tools/wks-util.c b/tools/wks-util.c index 3fd824c1a..1459045ef 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -19,12 +19,17 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/types.h> +#include <sys/stat.h> #include "../common/util.h" #include "../common/status.h" #include "../common/ccparray.h" #include "../common/exectool.h" +#include "../common/zb32.h" +#include "../common/userids.h" #include "../common/mbox-util.h" +#include "../common/sysutils.h" #include "mime-maker.h" #include "send-mail.h" #include "gpg-wks.h" @@ -65,7 +70,7 @@ wks_set_status_fd (int fd) } -/* Write a status line with code NO followed by the outout of the +/* Write a status line with code NO followed by the output of the * printf style FORMAT. The caller needs to make sure that LFs and * CRs are not printed. */ void @@ -104,7 +109,7 @@ append_to_uidinfo_list (uidinfo_list_t *list, const char *uid, time_t created) strcpy (sl->uid, uid); sl->created = created; - sl->mbox = mailbox_from_userid (uid); + sl->mbox = mailbox_from_userid (uid, 0); sl->next = NULL; if (!*list) *list = sl; @@ -187,7 +192,7 @@ wks_get_key (estream_t *r_key, const char *fingerprint, const char *addrspec, es_fputs ("Content-Type: application/pgp-keys\n" "\n", key); - filterexp = es_bsprintf ("keep-uid=%s=%s", exact? "uid":"mbox", addrspec); + filterexp = es_bsprintf ("keep-uid=%s= %s", exact? "uid":"mbox", addrspec); if (!filterexp) { err = gpg_error_from_syserror (); @@ -461,7 +466,7 @@ wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid, es_fputs ("Content-Type: application/pgp-keys\n" "\n", newkey); - filterexp = es_bsprintf ("keep-uid=uid=%s", uid); + filterexp = es_bsprintf ("keep-uid=uid= %s", uid); if (!filterexp) { err = gpg_error_from_syserror (); @@ -556,7 +561,7 @@ wks_send_mime (mime_maker_t mime) /* Parse the policy flags by reading them from STREAM and storing them - * into FLAGS. If IGNORE_UNKNOWN is iset unknown keywords are + * into FLAGS. If IGNORE_UNKNOWN is set unknown keywords are * ignored. */ gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown) @@ -699,3 +704,388 @@ wks_free_policy (policy_flags_t policy) memset (policy, 0, sizeof *policy); } } + + +/* Write the content of SRC to the new file FNAME. */ +static gpg_error_t +write_to_file (estream_t src, const char *fname) +{ + gpg_error_t err; + estream_t dst; + char buffer[4096]; + size_t nread, written; + + dst = es_fopen (fname, "wb"); + if (!dst) + return gpg_error_from_syserror (); + + do + { + nread = es_fread (buffer, 1, sizeof buffer, src); + if (!nread) + break; + written = es_fwrite (buffer, 1, nread, dst); + if (written != nread) + break; + } + while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst)); + if (!es_feof (src) || es_ferror (src) || es_ferror (dst)) + { + err = gpg_error_from_syserror (); + es_fclose (dst); + gnupg_remove (fname); + return err; + } + + if (es_fclose (dst)) + { + err = gpg_error_from_syserror (); + log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); + return err; + } + + return 0; +} + + +/* Return the filename and optionally the addrspec for USERID at + * R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. */ +gpg_error_t +wks_fname_from_userid (const char *userid, char **r_fname, char **r_addrspec) +{ + gpg_error_t err; + char *addrspec = NULL; + const char *domain; + char *hash = NULL; + const char *s; + char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */ + + *r_fname = NULL; + if (r_addrspec) + *r_addrspec = NULL; + + addrspec = mailbox_from_userid (userid, 0); + if (!addrspec) + { + if (opt.verbose) + log_info ("\"%s\" is not a proper mail address\n", userid); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + + domain = strchr (addrspec, '@'); + log_assert (domain); + domain++; + + /* Hash user ID and create filename. */ + s = strchr (addrspec, '@'); + log_assert (s); + gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, addrspec, s - addrspec); + hash = zb32_encode (shaxbuf, 8*20); + if (!hash) + { + err = gpg_error_from_syserror (); + goto leave; + } + + *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); + if (!*r_fname) + err = gpg_error_from_syserror (); + else + err = 0; + + leave: + if (r_addrspec && addrspec) + *r_addrspec = addrspec; + else + xfree (addrspec); + xfree (hash); + return err; +} + + +/* Compute the the full file name for the key with ADDRSPEC and return + * it at R_FNAME. */ +gpg_error_t +wks_compute_hu_fname (char **r_fname, const char *addrspec) +{ + gpg_error_t err; + char *hash; + const char *domain; + char sha1buf[20]; + char *fname; + struct stat sb; + + *r_fname = NULL; + + domain = strchr (addrspec, '@'); + if (!domain || !domain[1] || domain == addrspec) + return gpg_error (GPG_ERR_INV_ARG); + domain++; + + gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1); + hash = zb32_encode (sha1buf, 8*20); + if (!hash) + return gpg_error_from_syserror (); + + /* Try to create missing directories below opt.directory. */ + fname = make_filename_try (opt.directory, domain, NULL); + if (fname && stat (fname, &sb) + && gpg_err_code_from_syserror () == GPG_ERR_ENOENT) + if (!gnupg_mkdir (fname, "-rwxr--r--") && opt.verbose) + log_info ("directory '%s' created\n", fname); + xfree (fname); + fname = make_filename_try (opt.directory, domain, "hu", NULL); + if (fname && stat (fname, &sb) + && gpg_err_code_from_syserror () == GPG_ERR_ENOENT) + if (!gnupg_mkdir (fname, "-rwxr--r--") && opt.verbose) + log_info ("directory '%s' created\n", fname); + xfree (fname); + + /* Create the filename. */ + fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); + err = fname? 0 : gpg_error_from_syserror (); + + if (err) + xfree (fname); + else + *r_fname = fname; /* Okay. */ + xfree (hash); + return err; +} + + + +/* Helper form wks_cmd_install_key. */ +static gpg_error_t +install_key_from_spec_file (const char *fname) +{ + gpg_error_t err; + estream_t fp; + char *line = NULL; + size_t linelen = 0; + size_t maxlen = 2048; + char *fields[2]; + unsigned int lnr = 0; + + if (!fname || !strcmp (fname, "")) + fp = es_stdin; + else + fp = es_fopen (fname, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + while (es_read_line (fp, &line, &linelen, &maxlen) > 0) + { + if (!maxlen) + { + err = gpg_error (GPG_ERR_LINE_TOO_LONG); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + lnr++; + trim_spaces (line); + if (!*line || *line == '#') + continue; + if (split_fields (line, fields, DIM(fields)) < 2) + { + log_error ("error reading '%s': syntax error at line %u\n", + fname, lnr); + continue; + } + err = wks_cmd_install_key (fields[0], fields[1]); + if (err) + goto leave; + } + if (es_ferror (fp)) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + leave: + if (fp != es_stdin) + es_fclose (fp); + es_free (line); + return err; +} + + +/* Install a single key into the WKD by reading FNAME and extracting + * USERID. If USERID is NULL FNAME is expected to be a list of fpr + * mbox lines and for each line the respective key will be + * installed. */ +gpg_error_t +wks_cmd_install_key (const char *fname, const char *userid) +{ + gpg_error_t err; + KEYDB_SEARCH_DESC desc; + estream_t fp = NULL; + char *addrspec = NULL; + char *fpr = NULL; + uidinfo_list_t uidlist = NULL; + uidinfo_list_t uid, thisuid; + time_t thistime; + char *huname = NULL; + int any; + + if (!userid) + return install_key_from_spec_file (fname); + + addrspec = mailbox_from_userid (userid, 0); + if (!addrspec) + { + log_error ("\"%s\" is not a proper mail address\n", userid); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + + if (!classify_user_id (fname, &desc, 1) + && desc.mode == KEYDB_SEARCH_MODE_FPR) + { + /* FNAME looks like a fingerprint. Get the key from the + * standard keyring. */ + err = wks_get_key (&fp, fname, addrspec, 0); + if (err) + { + log_error ("error getting key '%s' (uid='%s'): %s\n", + fname, addrspec, gpg_strerror (err)); + goto leave; + } + } + else /* Take it from the file */ + { + fp = es_fopen (fname, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + } + + /* List the key so that we can figure out the newest UID with the + * requested addrspec. */ + err = wks_list_key (fp, &fpr, &uidlist); + if (err) + { + log_error ("error parsing key: %s\n", gpg_strerror (err)); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + thistime = 0; + thisuid = NULL; + any = 0; + for (uid = uidlist; uid; uid = uid->next) + { + if (!uid->mbox) + continue; /* Should not happen anyway. */ + if (ascii_strcasecmp (uid->mbox, addrspec)) + continue; /* Not the requested addrspec. */ + any = 1; + if (uid->created > thistime) + { + thistime = uid->created; + thisuid = uid; + } + } + if (!thisuid) + thisuid = uidlist; /* This is the case for a missing timestamp. */ + if (!any) + { + log_error ("public key in '%s' has no mail address '%s'\n", + fname, addrspec); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + + if (opt.verbose) + log_info ("using key with user id '%s'\n", thisuid->uid); + + { + estream_t fp2; + + es_rewind (fp); + err = wks_filter_uid (&fp2, fp, thisuid->uid, 1); + if (err) + { + log_error ("error filtering key: %s\n", gpg_strerror (err)); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + es_fclose (fp); + fp = fp2; + } + + /* Hash user ID and create filename. */ + err = wks_compute_hu_fname (&huname, addrspec); + if (err) + goto leave; + + /* Publish. */ + err = write_to_file (fp, huname); + if (err) + { + log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err)); + goto leave; + } + + /* Make sure it is world readable. */ + if (gnupg_chmod (huname, "-rwxr--r--")) + log_error ("can't set permissions of '%s': %s\n", + huname, gpg_strerror (gpg_err_code_from_syserror())); + + if (!opt.quiet) + log_info ("key %s published for '%s'\n", fpr, addrspec); + + leave: + xfree (huname); + free_uidinfo_list (uidlist); + xfree (fpr); + xfree (addrspec); + es_fclose (fp); + return err; +} + + +/* Remove the key with mail address in USERID. */ +gpg_error_t +wks_cmd_remove_key (const char *userid) +{ + gpg_error_t err; + char *addrspec = NULL; + char *fname = NULL; + + err = wks_fname_from_userid (userid, &fname, &addrspec); + if (err) + goto leave; + + if (gnupg_remove (fname)) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + if (!opt.quiet) + log_info ("key for '%s' is not installed\n", addrspec); + log_inc_errorcount (); + err = 0; + } + else + log_error ("error removing '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + if (opt.verbose) + log_info ("key for '%s' removed\n", addrspec); + err = 0; + + leave: + xfree (fname); + xfree (addrspec); + return err; +} |