From 9ae3cfcabec9252c22d67b7a15c36f0a8cf22f0f Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 5 Jul 2023 09:29:54 +0900 Subject: [PATCH 01/55] dirmngr: Enable the call of ks_ldap_help_variables when USE_LDAP. * dirmngr/server.c [USE_LDAP] (cmd_ad_query): Conditionalize. -- Cherry-pick master commit of: dc13361524c1477b2106c7385f2059f9ea111b84 Signed-off-by: NIIBE Yutaka --- dirmngr/server.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dirmngr/server.c b/dirmngr/server.c index 51a149cb2..ee61f63d6 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -2776,7 +2776,9 @@ cmd_ad_query (assuan_context_t ctx, char *line) if (opt_help) { +#if USE_LDAP ks_ldap_help_variables (ctrl); +#endif err = 0; goto leave; } From a3be97df4ddfce008dcc6e877e9fb98c71656ec6 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Mon, 10 Jul 2023 11:18:08 +0900 Subject: [PATCH 02/55] common:w32: Fix gnupg_w32_set_errno. * common/sysutils.c (gnupg_w32_set_errno): Return EC. -- Cherry-pick master commit of: 4c6b759368bcf19a13df07c5c6080765ecac28ca Signed-off-by: NIIBE Yutaka --- common/sysutils.c | 1 + 1 file changed, 1 insertion(+) diff --git a/common/sysutils.c b/common/sysutils.c index f8e6d86fc..c88c02d36 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -337,6 +337,7 @@ gnupg_w32_set_errno (int ec) if (ec == -1) ec = GetLastError (); _set_errno (map_w32_to_errno (ec)); + return ec; } #endif /*HAVE_W32_SYSTEM*/ From 083a16ae08eb0226f55783d6f7b65a35e7724067 Mon Sep 17 00:00:00 2001 From: Andre Heinecke Date: Wed, 19 Jul 2023 11:27:08 +0200 Subject: [PATCH 03/55] dirmngr: Add doc for faked-system-time * dirmngr/dirmngr.c (gpgrt_opt_t): Use string for oFakedSystemTime. (oFakedSystemTime): Use similar conversion as gpgsm has. * dirmngr/dirmngr.texi (faked-system-time): Document it. -- For testing X509 certificates this is usually required and then confusing that the example from the gpgsm man page does not work for dirmngr. --- dirmngr/dirmngr.c | 9 +++++++-- doc/dirmngr.texi | 7 +++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index b460ed3b3..97c2dc490 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -221,7 +221,7 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), ARGPARSE_s_i (oMaxReplies, "max-replies", N_("|N|do not return more than N items in one query")), - ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/ + ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), ARGPARSE_s_s (oIgnoreCert,"ignore-cert", "@"), ARGPARSE_s_s (oIgnoreCertExtension,"ignore-cert-extension", "@"), @@ -1176,7 +1176,12 @@ main (int argc, char **argv) case oLDAPAddServers: opt.add_new_ldapservers = 1; break; case oFakedSystemTime: - gnupg_set_time ((time_t)pargs.r.ret_ulong, 0); + { + time_t faked_time = isotime2epoch (pargs.r.ret_str); + if (faked_time == (time_t)(-1)) + faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10); + gnupg_set_time (faked_time, 0); + } break; case oForce: opt.force = 1; break; diff --git a/doc/dirmngr.texi b/doc/dirmngr.texi index 0bf35b72f..fb448752a 100644 --- a/doc/dirmngr.texi +++ b/doc/dirmngr.texi @@ -175,6 +175,13 @@ names and are OR-ed together. The special flag "none" clears the list and allows to start over with an empty list. To get a list of available flags the sole word "help" can be used. +@item --faked-system-time @var{epoch} +@opindex faked-system-time +This option is only useful for testing; it sets the system time back or +forth to @var{epoch} which is the number of seconds elapsed since the year +1970. Alternatively @var{epoch} may be given as a full ISO time string +(e.g. "20070924T154812"). + @item --debug-level @var{level} @opindex debug-level Select the debug level for investigating problems. @var{level} may be a From c68b70ce9d63221abfcaa9bb299c9f4556077006 Mon Sep 17 00:00:00 2001 From: Andre Heinecke Date: Fri, 21 Jul 2023 10:29:22 +0200 Subject: [PATCH 04/55] w32: Add keyboxd.exe to signed files * build-aux/speedo.mk (AUTHENTICODE_FILES): Add keyboxd.exe -- This should prevent that keyboxd.exe is blocked on systems that only allow signed executables. --- build-aux/speedo.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/build-aux/speedo.mk b/build-aux/speedo.mk index 170c20b79..7777411e2 100644 --- a/build-aux/speedo.mk +++ b/build-aux/speedo.mk @@ -282,6 +282,7 @@ AUTHENTICODE_FILES= \ gpgtar.exe \ gpgv.exe \ gpg-card.exe \ + keyboxd.exe \ libassuan-0.dll \ libgcrypt-20.dll \ libgpg-error-0.dll \ From 2258bcded654fc970a747627c4f560a8b03cc5e8 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 12 Jul 2023 13:34:19 +0900 Subject: [PATCH 05/55] gpg: Fix expiration time when Creation-Date is specified. * g10/keygen.c (parse_expire_string_with_ct): New function, optionally supply the creation time. (parse_expire_string): Use parse_expire_string_with_ct with no creation time. (proc_parameter_file): Use parse_expire_string_with_ct possibly with the creation time. -- Cherry-pick from master commit of: b07b5144ff6a9208ea27fe1e1518270bd22b382c GnuPG-bug-id: 5252 Signed-off-by: NIIBE Yutaka --- g10/keygen.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/g10/keygen.c b/g10/keygen.c index d5099dbb9..608867cfa 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -2740,14 +2740,19 @@ ask_curve (int *algo, int *subkey_algo, const char *current) * just cope for the next few years until we get a 64-bit time_t or * similar. */ -u32 -parse_expire_string( const char *string ) +static u32 +parse_expire_string_with_ct (const char *string, u32 creation_time) { int mult; u32 seconds; u32 abs_date = 0; - u32 curtime = make_timestamp (); time_t tt; + u32 curtime; + + if (creation_time == (u32)-1) + curtime = make_timestamp (); + else + curtime = creation_time; if (!string || !*string || !strcmp (string, "none") || !strcmp (string, "never") || !strcmp (string, "-")) @@ -2767,6 +2772,13 @@ parse_expire_string( const char *string ) return seconds; } +u32 +parse_expire_string ( const char *string ) +{ + return parse_expire_string_with_ct (string, (u32)-1); +} + + /* Parse a Creation-Date string which is either "1986-04-26" or "19860426T042640". Returns 0 on error. */ static u32 @@ -4130,6 +4142,7 @@ proc_parameter_file (ctrl_t ctrl, struct para_data_s *para, const char *fname, int is_default = 0; int have_user_id = 0; int err, algo; + u32 creation_time = (u32)-1; /* Check that we have all required parameters. */ r = get_parameter( para, pKEYTYPE ); @@ -4295,15 +4308,13 @@ proc_parameter_file (ctrl_t ctrl, struct para_data_s *para, const char *fname, if (r && *r->u.value && !(get_parameter_bool (para, pCARDKEY) && get_parameter_u32 (para, pKEYCREATIONDATE))) { - u32 seconds; - - seconds = parse_creation_string (r->u.value); - if (!seconds) + creation_time = parse_creation_string (r->u.value); + if (!creation_time) { log_error ("%s:%d: invalid creation date\n", fname, r->lnr ); return -1; } - r->u.creation = seconds; + r->u.creation = creation_time; r->key = pKEYCREATIONDATE; /* Change that entry. */ } @@ -4313,7 +4324,7 @@ proc_parameter_file (ctrl_t ctrl, struct para_data_s *para, const char *fname, { u32 seconds; - seconds = parse_expire_string( r->u.value ); + seconds = parse_expire_string_with_ct (r->u.value, creation_time); if( seconds == (u32)-1 ) { log_error("%s:%d: invalid expire date\n", fname, r->lnr ); From 96b69c1866dd960942c0c845ea3630f8884a8849 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 12 Jul 2023 14:04:28 +0900 Subject: [PATCH 06/55] gpg: Add support for Subkey-Expire-Date. * g10/keygen.c (enum para_name): Add pSUBKEYEXPIREDATE. (proc_parameter_file): Add support for pSUBKEYEXPIREDATE. (read_parameter_file): Add "Subkey-Expire-Date". -- Cherry-pick from master commit of: 23bcb78d279ebc81ec9340356401d19cf89985f1 Signed-off-by: NIIBE Yutaka --- g10/keygen.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/g10/keygen.c b/g10/keygen.c index 608867cfa..c252b0de4 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -87,6 +87,7 @@ enum para_name { pEXPIREDATE, pKEYEXPIRE, /* in n seconds */ pSUBKEYCREATIONDATE, + pSUBKEYEXPIREDATE, pSUBKEYEXPIRE, /* in n seconds */ pAUTHKEYCREATIONDATE, /* Not yet used. */ pPASSPHRASE, @@ -4331,12 +4332,29 @@ proc_parameter_file (ctrl_t ctrl, struct para_data_s *para, const char *fname, return -1; } r->u.expire = seconds; - r->key = pKEYEXPIRE; /* change hat entry */ - /* also set it for the subkey */ - r = xmalloc_clear( sizeof *r + 20 ); - r->key = pSUBKEYEXPIRE; - r->u.expire = seconds; - append_to_parameter (para, r); + r->key = pKEYEXPIRE; /* change that entry */ + + /* Make SUBKEYEXPIRE from Subkey-Expire-Date, if any. */ + r = get_parameter( para, pSUBKEYEXPIREDATE ); + if( r && *r->u.value ) + { + seconds = parse_expire_string_with_ct (r->u.value, creation_time); + if( seconds == (u32)-1 ) + { + log_error("%s:%d: invalid subkey expire date\n", fname, r->lnr ); + return -1; + } + r->key = pSUBKEYEXPIRE; /* change that entry */ + r->u.expire = seconds; + } + else + { + /* Or else, set Expire-Date for the subkey */ + r = xmalloc_clear( sizeof *r + 20 ); + r->key = pSUBKEYEXPIRE; + r->u.expire = seconds; + append_to_parameter (para, r); + } } do_generate_keypair (ctrl, para, outctrl, card ); @@ -4367,6 +4385,7 @@ read_parameter_file (ctrl_t ctrl, const char *fname ) { "Name-Email", pNAMEEMAIL }, { "Name-Comment", pNAMECOMMENT }, { "Expire-Date", pEXPIREDATE }, + { "Subkey-Expire-Date", pSUBKEYEXPIREDATE }, { "Creation-Date", pCREATIONDATE }, { "Passphrase", pPASSPHRASE }, { "Preferences", pPREFERENCES }, From fa29c86582487880364b710fd9679c8e77c8dce6 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 2 Aug 2023 11:39:40 +0900 Subject: [PATCH 07/55] build: Update libassuan.m4 to allow build with libassuan 3. * m4/libassuan.m4: Update from libassuan master. -- Signed-off-by: NIIBE Yutaka --- m4/libassuan.m4 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/m4/libassuan.m4 b/m4/libassuan.m4 index 79391bb4d..a2eb5d973 100644 --- a/m4/libassuan.m4 +++ b/m4/libassuan.m4 @@ -9,7 +9,7 @@ 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 -# Last-changed: 2022-11-01 +# Last-changed: 2023-07-26 dnl dnl Common code used for libassuan detection [internal] @@ -89,6 +89,7 @@ AC_DEFUN([_AM_PATH_LIBASSUAN_COMMON], if test $ok = yes; then AC_MSG_RESULT([yes ($libassuan_config_version)]) + AC_DEFINE(LIBASSUAN_API_REQUESTED, $req_libassuan_api, Requested API version for libassuan) else AC_MSG_RESULT(no) fi @@ -104,6 +105,8 @@ AC_DEFUN([_AM_PATH_LIBASSUAN_COMMON], AC_MSG_CHECKING([LIBASSUAN API version]) if test "$req_libassuan_api" -eq "$tmp" ; then AC_MSG_RESULT(okay) + elif test "$req_libassuan_api" -eq 2 -a "$tmp" -eq 3; then + AC_MSG_RESULT(okay) else ok=no AC_MSG_RESULT([does not match. want=$req_libassuan_api got=$tmp.]) From 32c55603dfeb14c7e3a2fd44cdcb301280dc7f6d Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 24 May 2023 16:02:39 +0200 Subject: [PATCH 08/55] dirmngr: Fix LDAP time parser. * dirmngr/ldap-misc.c (rfc4517toisotime): Correct index. -- Obviously the parser assumes the standard ISO format with the 'T' before the hour. That is not correct here. We need this parser for the modifyTimestamp thingy. --- dirmngr/ldap-misc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dirmngr/ldap-misc.c b/dirmngr/ldap-misc.c index 6b0939a3b..c3a659d5c 100644 --- a/dirmngr/ldap-misc.c +++ b/dirmngr/ldap-misc.c @@ -380,13 +380,14 @@ rfc4517toisotime (gnupg_isotime_t timebuf, const char *string) int year, month, day, hour, minu, sec; const char *s; + /* Sample value: "20230823141623Z"; */ for (i=0, s=string; i < 10; i++, s++) /* Need yyyymmddhh */ if (!digitp (s)) return gpg_error (GPG_ERR_INV_TIME); year = atoi_4 (string); month = atoi_2 (string + 4); day = atoi_2 (string + 6); - hour = atoi_2 (string + 9); + hour = atoi_2 (string + 8); minu = 0; sec = 0; if (digitp (s) && digitp (s+1)) From ee27ac18eaf27802be9258ac384e8844911a5443 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 24 Aug 2023 11:28:12 +0200 Subject: [PATCH 09/55] doc: Add some hints for AD queries. -- This is repo only. --- doc/ad-query-hints.org | 65 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 doc/ad-query-hints.org diff --git a/doc/ad-query-hints.org b/doc/ad-query-hints.org new file mode 100644 index 000000000..fd32a5831 --- /dev/null +++ b/doc/ad-query-hints.org @@ -0,0 +1,65 @@ + + +* Examples + +** List the DNs of all users in our QAUsers group + +: ad_query --subst --attr=dn +: ^OU=QAUsers,$domain&sub&(&(objectcategory=person)(objectclass=user)) + +** List the DN using the user's mail address + +: ad_query --subst --attr=dn,userAccountControl +: (&(objectcategory=person)(objectclass=user) +: (|(userPrincipalName=dd9jn@w32demo.g10code.de) +: (mail=dd9jn@w32demo.g10code.de))) + +After that the userControlFlags should be checked - see below for +the bit flags. For a non-disabled user use: + +: if ((userControlFlags & 0x0212) == 0x200)) +: use_this_user() + + +* Useful attributes + +** userAccountControl + +These are bit flags. For details see +https://learn.microsoft.com/en-us/windows/win32/api/iads/ne-iads-ads_user_flag_enum + +- 0x00000002 :: ADS_UF_ACCOUNTDISABLE, the account is disabled. +- 0x00000010 :: ADS_UF_LOCKOUT, the account is temporarily locked out. +- 0x00000100 :: ADS_UF_TEMP_DUPLICATE_ACCOUNT, this is an account for + a user whose primary account is in another domain. +- 0x00000200 :: ADS_UF_NORMAL_ACCOUNT, the default account type that + represents a typical user. +- 0x00000800 :: ADS_UF_INTERDOMAIN_TRUST_ACCOUNT, the account for a + domain-to-domain trust. +- 0x00001000 :: ADS_UF_WORKSTATION_ACCOUNT, the computer account for a + computer that is a member of this domain. +- 0x00002000 :: ADS_UF_SERVER_TRUST_ACCOUNT, the computer account for + a DC. +- 0x00010000 :: ADS_UF_DONT_EXPIRE_PASSWD, the password will not expire. +- 0x04000000 :: ADS_UF_PARTIAL_SECRETS_ACCOUNT, the computer account + for an RODC. + +For example to select only user accounts which are not disabled or +are locked out could naivly be used: + +: (userAccountControl:1.2.840.113556.1.4.803:=512) + +1.2.840.113556.1.4.803 is bit wise AND, 1.2.840.113556.1.4.804 is bit +wise OR. However, because a mask can't be specified, this is not really +useful. Thus the above needs to be replaced by explicit checks; i.e. + +: (&(userAccountControl:1.2.840.113556.1.4.804:=512) +: (!(userAccountControl:1.2.840.113556.1.4.804:=2)) +: (!(userAccountControl:1.2.840.113556.1.4.804:=16))) + +I'd suggest to also add explict checks on the returned data. + + +* Resources + +- https://qa.social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx From 776876ce1c4c5da3a0fe1dc538fc7a67cf18c054 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 31 Aug 2023 11:13:38 +0200 Subject: [PATCH 10/55] gpgsm: Add --always-trust feature. * sm/gpgsm.h (opt): Re-purpose unused flag always_trust. (struct server_control_s): Add "always_trust". (VALIDATE_FLAG_BYPASS): New. * sm/gpgsm.c (oAlwaysTrust): New. (opts): Add "--always-trust" (main): Set option. * sm/server.c (option_handler): Add option "always-trust". (reset_notify): Clear that option. (cmd_encrypt): Ditto. (cmd_getinfo): Add sub-command always-trust. * sm/certchain.c (gpgsm_validate_chain): Handle VALIDATE_FLAG_BYPASS. * sm/certlist.c (gpgsm_add_to_certlist): Set that flag for recipients in always-trust mode. -- GnuPG-bug-id: 6559 --- doc/gpgsm.texi | 20 ++++++++++++++++++++ sm/certchain.c | 14 +++++++++++--- sm/certlist.c | 8 +++++++- sm/gpgsm.c | 13 +++++++++++++ sm/gpgsm.h | 10 ++++++++-- sm/server.c | 21 ++++++++++++++++++++- 6 files changed, 79 insertions(+), 7 deletions(-) diff --git a/doc/gpgsm.texi b/doc/gpgsm.texi index e976767f6..497b33203 100644 --- a/doc/gpgsm.texi +++ b/doc/gpgsm.texi @@ -732,6 +732,13 @@ instead to make sure that the gpgsm process exits with a failure if the compliance rules are not fulfilled. Note that this option has currently an effect only in "de-vs" mode. +@item --always-trust +@opindex always-trust +Force encryption to the specified certificates without any validation +of the certificate chain. The only requirement is that the +certificate is capable of encryption. Note that this option is +ineffective if @option{--require-compliance} is used. + @item --ignore-cert-with-oid @var{oid} @opindex ignore-cert-with-oid Add @var{oid} to the list of OIDs to be checked while reading @@ -1622,6 +1629,10 @@ The leading two dashes usually used with @var{opt} shall not be given. Return OK if the connection is in offline mode. This may be either due to a @code{OPTION offline=1} or due to @command{gpgsm} being started with option @option{--disable-dirmngr}. +@item always-trust +Returns OK of the connection is in always-trust mode. That is either +@option{--always-trust} or @option{GPGSM OPTION always-trust} are +active. @end table @node GPGSM OPTION @@ -1728,6 +1739,15 @@ If @var{value} is true or @var{value} is not given all network access is disabled for this session. This is the same as the command line option @option{--disable-dirmngr}. +@item always-trust +If @var{value} is true or @var{value} is not given encryption to the +specified certificates is forced without any validation of the +certificate chain. The only requirement is that the certificates are +capable of encryption. If set to false the standard behaviour is +re-established. This option is cleared by a RESET and after each +encrypt operation. Note that this option is ignored if +@option{--always-trust} or @option{--require-compliance} are used. + @item input-size-hint This is the same as the @option{--input-size-hint} command line option. diff --git a/sm/certchain.c b/sm/certchain.c index 84dbed696..9d0fe684b 100644 --- a/sm/certchain.c +++ b/sm/certchain.c @@ -2199,9 +2199,15 @@ gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime, memset (&rootca_flags, 0, sizeof rootca_flags); - rc = do_validate_chain (ctrl, cert, checktime, - r_exptime, listmode, listfp, flags, - &rootca_flags); + if ((flags & VALIDATE_FLAG_BYPASS)) + { + *retflags |= VALIDATE_FLAG_BYPASS; + rc = 0; + } + else + rc = do_validate_chain (ctrl, cert, checktime, + r_exptime, listmode, listfp, flags, + &rootca_flags); if (!rc && (flags & VALIDATE_FLAG_STEED)) { *retflags |= VALIDATE_FLAG_STEED; @@ -2223,6 +2229,8 @@ gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime, if (opt.verbose) do_list (0, listmode, listfp, _("validation model used: %s"), + (*retflags & VALIDATE_FLAG_BYPASS)? + "bypass" : (*retflags & VALIDATE_FLAG_STEED)? "steed" : (*retflags & VALIDATE_FLAG_CHAIN_MODEL)? diff --git a/sm/certlist.c b/sm/certlist.c index fdf31a198..53d90ac30 100644 --- a/sm/certlist.c +++ b/sm/certlist.c @@ -448,6 +448,11 @@ gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret, if (!rc && !is_cert_in_certlist (cert, *listaddr)) { + unsigned int valflags = 0; + + if (!secret && (opt.always_trust || ctrl->always_trust)) + valflags |= VALIDATE_FLAG_BYPASS; + if (!rc && secret) { char *p; @@ -461,9 +466,10 @@ gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret, xfree (p); } } + if (!rc) rc = gpgsm_validate_chain (ctrl, cert, GNUPG_ISOTIME_NONE, NULL, - 0, NULL, 0, NULL); + 0, NULL, valflags, NULL); if (!rc) { certlist_t cl = xtrycalloc (1, sizeof *cl); diff --git a/sm/gpgsm.c b/sm/gpgsm.c index ce977413d..b3d48abce 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -215,6 +215,7 @@ enum cmd_and_opt_values { oRequireCompliance, oCompatibilityFlags, oKbxBufferSize, + oAlwaysTrust, oNoAutostart }; @@ -417,6 +418,7 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"), ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"), ARGPARSE_s_n (oRequireCompliance, "require-compliance", "@"), + ARGPARSE_s_n (oAlwaysTrust, "always-trust", "@"), ARGPARSE_header (NULL, N_("Options for unattended use")), @@ -1499,6 +1501,7 @@ main ( int argc, char **argv) case oMinRSALength: opt.min_rsa_length = pargs.r.ret_ulong; break; case oRequireCompliance: opt.require_compliance = 1; break; + case oAlwaysTrust: opt.always_trust = 1; break; case oKbxBufferSize: keybox_set_buffersize (pargs.r.ret_ulong, 0); @@ -1588,10 +1591,20 @@ main ( int argc, char **argv) if (may_coredump && !opt.quiet) log_info (_("WARNING: program may create a core file!\n")); + if (opt.require_compliance && opt.always_trust) + { + opt.always_trust = 0; + if (opt.quiet) + log_info (_("WARNING: %s overrides %s\n"), + "--require-compliance","--always-trust"); + } + + npth_init (); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); + /* if (opt.qualsig_approval && !opt.quiet) */ /* log_info (_("This software has officially been approved to " */ /* "create and verify\n" */ diff --git a/sm/gpgsm.h b/sm/gpgsm.h index e1aca8bb7..a22327edc 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -116,8 +116,6 @@ struct int extra_digest_algo; /* A digest algorithm also used for verification of signatures. */ - int always_trust; /* Trust the given keys even if there is no - valid certification chain */ int skip_verify; /* do not check signatures on data */ int lock_once; /* Keep lock once they are set */ @@ -164,6 +162,10 @@ struct * mode. */ int require_compliance; + /* Enable always-trust mode - note that there is also server option + * for this. */ + int always_trust; + /* Enable creation of authenticode signatures. */ int authenticode; @@ -269,6 +271,9 @@ struct server_control_s 2 := STEED model. */ int offline; /* If true gpgsm won't do any network access. */ + int always_trust; /* True in always-trust mode; see also + * opt.always-trust. */ + /* The current time. Used as a helper in certchain.c. */ ksba_isotime_t current_time; @@ -388,6 +393,7 @@ int gpgsm_create_cms_signature (ctrl_t ctrl, #define VALIDATE_FLAG_NO_DIRMNGR 1 #define VALIDATE_FLAG_CHAIN_MODEL 2 #define VALIDATE_FLAG_STEED 4 +#define VALIDATE_FLAG_BYPASS 8 /* No actual validation. */ gpg_error_t gpgsm_walk_cert_chain (ctrl_t ctrl, ksba_cert_t start, ksba_cert_t *r_next); diff --git a/sm/server.c b/sm/server.c index b545c1bfb..184ec9379 100644 --- a/sm/server.c +++ b/sm/server.c @@ -287,6 +287,17 @@ option_handler (assuan_context_t ctx, const char *key, const char *value) ctrl->offline = i; } } + else if (!strcmp (key, "always-trust")) + { + /* We ignore this option if gpgsm has been started with + --always-trust (which also sets offline) and if + --require-compliance is active */ + if (!opt.always_trust && !opt.require_compliance) + { + int i = *value? !!atoi (value) : 1; + ctrl->always_trust = i; + } + } else if (!strcmp (key, "request-origin")) { if (!opt.request_origin) @@ -320,6 +331,7 @@ reset_notify (assuan_context_t ctx, char *line) gpgsm_release_certlist (ctrl->server_local->signerlist); ctrl->server_local->recplist = NULL; ctrl->server_local->signerlist = NULL; + ctrl->always_trust = 0; close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); @@ -488,6 +500,7 @@ cmd_encrypt (assuan_context_t ctx, char *line) gpgsm_release_certlist (ctrl->server_local->recplist); ctrl->server_local->recplist = NULL; + ctrl->always_trust = 0; /* Close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); @@ -1189,7 +1202,8 @@ static const char hlp_getinfo[] = " agent-check - Return success if the agent is running.\n" " cmd_has_option CMD OPT\n" " - Returns OK if the command CMD implements the option OPT.\n" - " offline - Returns OK if the connection is in offline mode."; + " offline - Returns OK if the connection is in offline mode." + " always-trust- Returns OK if the connection is in always-trust mode."; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { @@ -1248,6 +1262,11 @@ cmd_getinfo (assuan_context_t ctx, char *line) { rc = ctrl->offline? 0 : gpg_error (GPG_ERR_FALSE); } + else if (!strcmp (line, "always-trust")) + { + rc = (ctrl->always_trust || opt.always_trust)? 0 + /**/ : gpg_error (GPG_ERR_FALSE); + } else rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); From 1f76cbca35133969ccccfa324d633556e19a386c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 4 Sep 2023 16:34:55 +0200 Subject: [PATCH 11/55] gpg: Add option --with-v5-fingerprint * g10/gpg.c (oWithV5Fingerprint): New. (opts): Add new option. (main): Set option. * g10/options.h (opt): Add with_v5_fingerprint. * g10/keyid.c (hash_public_key): Factor out to ... (do_hash_public_key): this. Add new arg to foce v5 style hashing. (v5_fingerprint_from_pk): New. (v5hexfingerprint): New. * g10/keylist.c (print_fingerprint): Print v5 fingerprint for v4 keys if the option is set. -- GnuPG-bug-id: 6705 --- doc/gpg.texi | 5 +++ g10/gpg.c | 13 ++++++++ g10/keydb.h | 2 ++ g10/keyid.c | 84 +++++++++++++++++++++++++++++++++++++++++++++------ g10/keylist.c | 6 ++++ g10/options.h | 1 + 6 files changed, 102 insertions(+), 9 deletions(-) diff --git a/doc/gpg.texi b/doc/gpg.texi index 15b3243d0..ce72afbf5 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -2849,6 +2849,11 @@ achieved by using the @option{--with-fingerprint} twice but by using this option along with keyid-format "none" a compact fingerprint is printed. +@item --with-v5-fingerprint +@opindex with-v5-fingerprint +In a colon mode listing emit "fp2" lines for version 4 OpenPGP keys +having a v5 style fingerprint of the key. + @item --with-icao-spelling @opindex with-icao-spelling Print the ICAO spelling of the fingerprint in addition to the hex digits. diff --git a/g10/gpg.c b/g10/gpg.c index 2ae3750a9..cb6e42e3c 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -203,6 +203,7 @@ enum cmd_and_opt_values oAskCertLevel, oNoAskCertLevel, oFingerprint, + oWithV5Fingerprint, oWithFingerprint, oWithSubkeyFingerprint, oWithICAOSpelling, @@ -816,6 +817,7 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"), ARGPARSE_s_n (oWithSigList,"with-sig-list", "@"), ARGPARSE_s_n (oWithSigCheck,"with-sig-check", "@"), + ARGPARSE_s_n (oWithV5Fingerprint, "with-v5-fingerprint", "@"), ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"), ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprint", "@"), ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprints", "@"), @@ -2890,6 +2892,9 @@ main (int argc, char **argv) opt_log_time = 1; break; + case oWithV5Fingerprint: + opt.with_v5_fingerprint = 1; + break; case oWithFingerprint: opt.with_fingerprint = 1; opt.fingerprint++; @@ -3794,6 +3799,14 @@ main (int argc, char **argv) g10_exit(2); } + /* Set depended fingerprint options. */ + if (opt.with_v5_fingerprint && !opt.with_fingerprint) + { + opt.with_fingerprint = 1; + if (!opt.fingerprint) + opt.fingerprint = 1; + } + /* Process common component options. */ if (parse_comopt (GNUPG_MODULE_NAME_GPG, debug_argparser)) { diff --git a/g10/keydb.h b/g10/keydb.h index 1a66d664e..b18f6e93a 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -570,8 +570,10 @@ const char *colon_datestr_from_pk (PKT_public_key *pk); const char *colon_datestr_from_sig (PKT_signature *sig); const char *colon_expirestr_from_sig (PKT_signature *sig); byte *fingerprint_from_pk( PKT_public_key *pk, byte *buf, size_t *ret_len ); +byte *v5_fingerprint_from_pk (PKT_public_key *pk, byte *array, size_t *ret_len); void fpr20_from_pk (PKT_public_key *pk, byte array[20]); char *hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen); +char *v5hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen); char *format_hexfingerprint (const char *fingerprint, char *buffer, size_t buflen); gpg_error_t keygrip_from_pk (PKT_public_key *pk, unsigned char *array); diff --git a/g10/keyid.c b/g10/keyid.c index 9191fec92..b80ee320e 100644 --- a/g10/keyid.c +++ b/g10/keyid.c @@ -2,7 +2,7 @@ * Copyright (C) 1998, 1999, 2000, 2001, 2003, * 2004, 2006, 2010 Free Software Foundation, Inc. * Copyright (C) 2014 Werner Koch - * Copyright (C) 2016 g10 Code GmbH + * Copyright (C) 2016, 2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -18,6 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later */ #include @@ -139,10 +140,11 @@ pubkey_string (PKT_public_key *pk, char *buffer, size_t bufsize) } -/* 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) +/* Hash a public key and allow to specify the to be used format. + * Note that if the v5 format is requested for a v4 key, a 0x04 as + * version is hashed instead of the 0x05. */ +static void +do_hash_public_key (gcry_md_hd_t md, PKT_public_key *pk, int use_v5) { unsigned int n; unsigned int nn[PUBKEY_MAX_NPKEY]; @@ -151,9 +153,8 @@ hash_public_key (gcry_md_hd_t md, PKT_public_key *pk) 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; + n = use_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 @@ -230,13 +231,14 @@ hash_public_key (gcry_md_hd_t md, PKT_public_key *pk) } } - if (is_v5) + if (use_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 ); + /* Note that the next byte may either be 4 or 5. */ gcry_md_putc ( md, pk->version ); } else @@ -253,7 +255,7 @@ hash_public_key (gcry_md_hd_t md, PKT_public_key *pk) gcry_md_putc ( md, pk->pubkey_algo ); - if (is_v5) + if (use_v5) { n -= 10; gcry_md_putc ( md, n >> 24 ); @@ -280,6 +282,15 @@ hash_public_key (gcry_md_hd_t md, PKT_public_key *pk) } +/* 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) +{ + do_hash_public_key (md, pk, pk->version); +} + + /* fixme: Check whether we can replace this function or if not describe why we need it. */ u32 @@ -888,6 +899,37 @@ fingerprint_from_pk (PKT_public_key *pk, byte *array, size_t *ret_len) } +/* + * Return a byte array with the fingerprint for the given PK/SK The + * length of the array is returned in ret_len. Caller must free the + * array or provide an array of length MAX_FINGERPRINT_LEN. This + * version creates a v5 fingerprint even vor v4 keys. + */ +byte * +v5_fingerprint_from_pk (PKT_public_key *pk, byte *array, size_t *ret_len) +{ + const byte *dp; + gcry_md_hd_t md; + + if (pk->version == 5) + return fingerprint_from_pk (pk, array, ret_len); + + if (gcry_md_open (&md, GCRY_MD_SHA256, 0)) + BUG (); + do_hash_public_key (md, pk, 1); + gcry_md_final (md); + dp = gcry_md_read (md, 0); + if (!array) + array = xmalloc (32); + memcpy (array, dp, 32); + gcry_md_close (md); + + if (ret_len) + *ret_len = 32; + return array; +} + + /* * Get FPR20 for the given PK/SK into ARRAY. * @@ -947,6 +989,30 @@ hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen) } +/* Same as hexfingerprint but returns a v5 fingerprint also for a v4 + * key. */ +char * +v5hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen) +{ + char fprbuf[32]; + + if (pk->version == 5) + return hexfingerprint (pk, buffer, buflen); + + if (!buffer) + { + buffer = xtrymalloc (2 * 32 + 1); + if (!buffer) + return NULL; + } + else if (buflen < 2 * 32 + 1) + log_fatal ("%s: buffer too short (%zu)\n", __func__, buflen); + + v5_fingerprint_from_pk (pk, fprbuf, NULL); + return bin2hex (fprbuf, 32, buffer); +} + + /* Pretty print a hex fingerprint. If BUFFER is NULL the result is a malloc'd string. If BUFFER is not NULL the result will be copied into this buffer. In the latter case BUFLEN describes the length diff --git a/g10/keylist.c b/g10/keylist.c index 8b7c597cb..d0ebfc86f 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -2413,6 +2413,12 @@ print_fingerprint (ctrl_t ctrl, estream_t override_fp, if (with_colons && !mode) { es_fprintf (fp, "fpr:::::::::%s:", hexfpr); + if (opt.with_v5_fingerprint && pk->version == 4) + { + char *v5fpr = v5hexfingerprint (pk, NULL, 0); + es_fprintf (fp, "\nfp2:::::::::%s:", v5fpr); + xfree (v5fpr); + } } else if (compact && !opt.fingerprint && !opt.with_fingerprint) { diff --git a/g10/options.h b/g10/options.h index 914c24849..e0ee99533 100644 --- a/g10/options.h +++ b/g10/options.h @@ -79,6 +79,7 @@ struct int with_colons; int with_key_data; int with_icao_spelling; /* Print ICAO spelling with fingerprints. */ + int with_v5_fingerprint; /* Option --with-v5-fingerprint active. */ int with_fingerprint; /* Option --with-fingerprint active. */ int with_subkey_fingerprint; /* Option --with-subkey-fingerprint active. */ int with_keygrip; /* Option --with-keygrip active. */ From 362a6dfb0a42c41604f173f24ac0f14b03165c6f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 5 Sep 2023 08:08:54 +0200 Subject: [PATCH 12/55] gpg: Fix last commit. * g10/keyid.c (hash_public_key): Do not pass the version. -- Fixes-commit: 1f76cbca35133969ccccfa324d633556e19a386c --- g10/keyid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/g10/keyid.c b/g10/keyid.c index b80ee320e..4a041ce0e 100644 --- a/g10/keyid.c +++ b/g10/keyid.c @@ -287,7 +287,7 @@ do_hash_public_key (gcry_md_hd_t md, PKT_public_key *pk, int use_v5) void hash_public_key (gcry_md_hd_t md, PKT_public_key *pk) { - do_hash_public_key (md, pk, pk->version); + do_hash_public_key (md, pk, (pk->version == 5)); } From 0aa32e2429bb4aaae4151567dc9556a01faea637 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Sep 2023 09:36:47 +0200 Subject: [PATCH 13/55] dirmngr: Allow conf files to disable default keyservers. * dirmngr/server.c (ensure_keyserver): Detect special value "none" (cmd_keyserver): Ignore "none" and "hkp://none". -- GnuPG-bug-id: 6708 --- NEWS | 3 +++ dirmngr/server.c | 22 ++++++++++++++++++---- doc/dirmngr.texi | 3 ++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index 94cd00c9f..f4ecb1d64 100644 --- a/NEWS +++ b/NEWS @@ -36,6 +36,9 @@ Noteworthy changes in version 2.4.3 (2023-07-04) * dirmngr: New option --ignore-crl-extensions. [T6545] + * dirmngr: Support config value "none" to disable the default + keyserver. [T6708] + * wkd: Use export-clean for gpg-wks-client's --mirror and --create commands. [rG2c7f7a5a27] diff --git a/dirmngr/server.c b/dirmngr/server.c index ee61f63d6..827c6207f 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -2202,6 +2202,7 @@ ensure_keyserver (ctrl_t ctrl) uri_item_t plain_items = NULL; uri_item_t ui; strlist_t sl; + int none_seen = 1; if (ctrl->server_local->keyservers) return 0; /* Already set for this session. */ @@ -2214,6 +2215,11 @@ ensure_keyserver (ctrl_t ctrl) for (sl = opt.keyserver; sl; sl = sl->next) { + if (!strcmp (sl->d, "none")) + { + none_seen = 1; + continue; + } err = make_keyserver_item (sl->d, &item); if (err) goto leave; @@ -2229,6 +2235,12 @@ ensure_keyserver (ctrl_t ctrl) } } + if (none_seen && !plain_items && !onion_items) + { + err = gpg_error (GPG_ERR_NO_KEYSERVER); + goto leave; + } + /* Decide which to use. Note that the session has no keyservers yet set. */ if (onion_items && !onion_items->next && plain_items && !plain_items->next) @@ -2299,8 +2311,7 @@ cmd_keyserver (assuan_context_t ctx, char *line) gpg_error_t err = 0; int clear_flag, add_flag, help_flag, host_flag, resolve_flag; int dead_flag, alive_flag; - uri_item_t item = NULL; /* gcc 4.4.5 is not able to detect that it - is always initialized. */ + uri_item_t item = NULL; clear_flag = has_option (line, "--clear"); help_flag = has_option (line, "--help"); @@ -2366,13 +2377,16 @@ cmd_keyserver (assuan_context_t ctx, char *line) if (add_flag) { - err = make_keyserver_item (line, &item); + if (!strcmp (line, "none") || !strcmp (line, "hkp://none")) + err = 0; + else + err = make_keyserver_item (line, &item); if (err) goto leave; } if (clear_flag) release_ctrl_keyservers (ctrl); - if (add_flag) + if (add_flag && item) { item->next = ctrl->server_local->keyservers; ctrl->server_local->keyservers = item; diff --git a/doc/dirmngr.texi b/doc/dirmngr.texi index fb448752a..398888e71 100644 --- a/doc/dirmngr.texi +++ b/doc/dirmngr.texi @@ -344,7 +344,8 @@ whether Tor is locally running or not. The check for a running Tor is done for each new connection. If no keyserver is explicitly configured, dirmngr will use the -built-in default of @code{https://keyserver.ubuntu.com}. +built-in default of @code{https://keyserver.ubuntu.com}. To avoid the +use of a default keyserver the value @code{none} can be used. Windows users with a keyserver running on their Active Directory may use the short form @code{ldap:///} for @var{name} to access this directory. From a02f3cc4e870bee97dfa54ba665d3db2721cdeb7 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 6 Sep 2023 12:09:55 +0200 Subject: [PATCH 14/55] gpg: Fix validity of re-imported keys. * g10/trustdb.c (tdb_clear_ownertrusts): Detect stale validity records. -- GnuPG-bug-id: 6399 This problem was introduced by an actually very useful patch 2002-12-13 David Shaw [...] * import.c (import_keys_internal): Used here so we don't rebuild the trustdb if it is still clean. (import_one, chk_self_sigs): Only mark trustdb dirty if the key that is being imported has any sigs other than self-sigs. Suggested by Adrian von Bidder. [the last part] The bug exhibited itself only after signing a key, deleting that key and then re-importing the original non-signed key. --- g10/trustdb.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/g10/trustdb.c b/g10/trustdb.c index 051a534f9..e846abe82 100644 --- a/g10/trustdb.c +++ b/g10/trustdb.c @@ -730,7 +730,7 @@ tdb_check_or_update (ctrl_t ctrl) if (opt.interactive) update_trustdb (ctrl); else if (!opt.no_auto_check_trustdb) - check_trustdb (ctrl); + check_trustdb (ctrl); } } @@ -983,6 +983,7 @@ update_min_ownertrust (ctrl_t ctrl, u32 *kid, unsigned int new_trust) /* * Clear the ownertrust and min_ownertrust values. + * Also schedule a revalidation if a stale validity record exists. * * Return: True if a change actually happened. */ @@ -1016,6 +1017,26 @@ tdb_clear_ownertrusts (ctrl_t ctrl, PKT_public_key *pk) do_sync (); return 1; } + else + { + /* Check whether we have a stale RECTYPE_VALID for that key + * and if its validity ist set, schedule a revalidation. */ + ulong recno = rec.r.trust.validlist; + while (recno) + { + read_record (recno, &rec, RECTYPE_VALID); + if (rec.r.valid.validity) + break; + recno = rec.r.valid.next; + } + if (recno) + { + if (DBG_TRUST) + log_debug ("stale validity value detected" + " - scheduling check\n"); + tdb_revalidation_mark (ctrl); + } + } } else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) { From 7f9e05d73f2ca1ecde1b7ba406d139a19d007998 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 7 Sep 2023 17:21:05 +0200 Subject: [PATCH 15/55] common: Never remove /dev/null. * common/sysutils.c (gnupg_remove): Detect /dev/null. -- GnuPG-bug-id: 6556 --- common/sysutils.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/sysutils.c b/common/sysutils.c index c88c02d36..90627b7c8 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -810,7 +810,12 @@ gnupg_remove (const char *fname) return -1; return 0; #else - return remove (fname); + /* It is common to use /dev/null for testing. We better don't + * remove that file. */ + if (fname && !strcmp (fname, "/dev/null")) + return 0; + else + return remove (fname); #endif } From 4fc745bc43a74f2aecd654b6b609ba188de76c25 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 11 Sep 2023 11:24:00 +0200 Subject: [PATCH 16/55] dirmngr: Relax the detection of the "none" keyserver. * dirmngr/server.c (cmd_keyserver): Ignore also hkps://none. (ensure_keyserver): Better ignore also "none" with a hkp or hpks scheme. -- GnuPG-bug-id: 6708 --- dirmngr/server.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dirmngr/server.c b/dirmngr/server.c index 827c6207f..1dbc87878 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -2215,7 +2215,11 @@ ensure_keyserver (ctrl_t ctrl) for (sl = opt.keyserver; sl; sl = sl->next) { - if (!strcmp (sl->d, "none")) + /* Frontends like Kleopatra may prefix option values without a + * scheme with "hkps://". Thus we need to check that too. + * Nobody will be mad enough to call a machine "none". */ + if (!strcmp (sl->d, "none") || !strcmp (sl->d, "hkp://none") + || !strcmp (sl->d, "hkps://none")) { none_seen = 1; continue; @@ -2377,7 +2381,8 @@ cmd_keyserver (assuan_context_t ctx, char *line) if (add_flag) { - if (!strcmp (line, "none") || !strcmp (line, "hkp://none")) + if (!strcmp (line, "none") || !strcmp (line, "hkp://none") + || !strcmp (line, "hkps://none")) err = 0; else err = make_keyserver_item (line, &item); From bf662d0f93af7524fff79116f7917d22f0259793 Mon Sep 17 00:00:00 2001 From: "Robin H. Johnson via Gnupg-devel" Date: Sat, 2 Sep 2023 09:59:43 -0700 Subject: [PATCH 17/55] gpg: Add --list-filter properties sig_expires/sig_expires_d Modelled after key_expires/key_expires_d. This should be useful to detect upcoming certification expiry, so the certifications can be renewed in advance of the expiry. Signed-off-by: Robin H. Johnson --- doc/gpg.texi | 6 ++++++ g10/import.c | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/doc/gpg.texi b/doc/gpg.texi index ce72afbf5..b666a72bc 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -2715,6 +2715,12 @@ The available properties are: second is the same but given as an ISO date string, e.g. "2016-08-17". (drop-sig) + @item sig_expires + @itemx sig_expires_d + The expiration time of a signature packet or 0 if it does not + expire. The second is the same but given as an ISO date string or + an empty string e.g. "2038-01-19". + @item sig_algo A number with the public key algorithm of a signature packet. (drop-sig) diff --git a/g10/import.c b/g10/import.c index d84a083cc..c1e76c3f0 100644 --- a/g10/import.c +++ b/g10/import.c @@ -1509,6 +1509,20 @@ impex_filter_getval (void *cookie, const char *propname) { result = dateonlystr_from_sig (sig); } + else if (!strcmp (propname, "sig_expires")) + { + snprintf (numbuf, sizeof numbuf, "%lu", (ulong)sig->expiredate); + result = numbuf; + } + else if (!strcmp (propname, "sig_expires_d")) + { + static char exdatestr[MK_DATESTR_SIZE]; + + if (sig->expiredate) + result = mk_datestr (exdatestr, sizeof exdatestr, sig->expiredate); + else + result = ""; + } else if (!strcmp (propname, "sig_algo")) { snprintf (numbuf, sizeof numbuf, "%d", sig->pubkey_algo); From 2a2846959f11053cb63c48626d6eda333868d033 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 18 Sep 2023 11:26:56 +0200 Subject: [PATCH 18/55] gpg: Fix --no-utf8-strings. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * g10/gpg.c (main): Ignore --no-utf8-strings only on Windows. -- Fixes-commit: 8c41b8aac3efb78178fe1eaf52d8d1bbc44941a8 Reported-by: Ingo Klöcker --- g10/gpg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/g10/gpg.c b/g10/gpg.c index cb6e42e3c..18df7de67 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -3489,7 +3489,7 @@ main (int argc, char **argv) break; case oUtf8Strings: utf8_strings = 1; break; case oNoUtf8Strings: -#ifdef HAVE_W32_SYSTEM +#ifndef HAVE_W32_SYSTEM utf8_strings = 0; #endif break; From 845d5e61d8e1ed4f25da424cfc5b0bb0fbb8678d Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 18 Sep 2023 17:37:42 +0200 Subject: [PATCH 19/55] dirmngr: Cleanup the http module. * configure.ac (NEED_NTBTLS_VERSION): Require at least 0.2.0 so that we can remove a conditional compilation. * dirmngr/http.c (struct proxy_info_s): New. (release_proxy_info): New to keep proxy information in one object. (send_request): Factor some code out to ... (get_proxy_for_url): this, (send_request_basic_checks): this, (send_request_set_sni): this, (run_ntbtls_handshake): this, (run_gnutls_handshake): and this. -- Note that this also removes some never used code. For example the NTBTLS handshake has code taken from GNUTLS which was never used due to the different ways on how the certificates are checked. The proxy code has been factored out to make to prepare further authentication methods. The proxy_info_t was introduced for the same reason. Tested against gnutls and ntbtls builds. No proxy tests yet done, because we need more sophisticated tests anyway. GnuPG-bug-id: 5768 --- configure.ac | 2 +- dirmngr/http.c | 760 +++++++++++++++++++++++++++++-------------------- dirmngr/http.h | 2 + 3 files changed, 448 insertions(+), 316 deletions(-) diff --git a/configure.ac b/configure.ac index 953e2616f..cfe6091b5 100644 --- a/configure.ac +++ b/configure.ac @@ -67,7 +67,7 @@ NEED_KSBA_API=1 NEED_KSBA_VERSION=1.6.3 NEED_NTBTLS_API=1 -NEED_NTBTLS_VERSION=0.1.0 +NEED_NTBTLS_VERSION=0.2.0 NEED_NPTH_API=1 NEED_NPTH_VERSION=1.2 diff --git a/dirmngr/http.c b/dirmngr/http.c index 8153fcef4..2189d7249 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -230,6 +230,14 @@ static es_cookie_io_functions_t simple_cookie_functions = }; #endif +/* An object to store information about a proxy. */ +struct proxy_info_s +{ + parsed_uri_t uri; /* The parsed proxy URL. */ + int is_http_proxy; /* This is an http proxy. */ +}; +typedef struct proxy_info_s *proxy_info_t; + #if SIZEOF_UNSIGNED_LONG == 8 # define HTTP_SESSION_MAGIC 0x0068545470534553 /* "hTTpSES" */ @@ -317,13 +325,13 @@ struct http_context_s static int opt_verbose; static int opt_debug; -/* The global callback for the verification function. */ +/* The global callback for the verification function for GNUTLS. */ static gpg_error_t (*tls_callback) (http_t, http_session_t, int); -/* The list of files with trusted CA certificates. */ +/* The list of files with trusted CA certificates for GNUTLS. */ static strlist_t tls_ca_certlist; -/* The list of files with extra trusted CA certificates. */ +/* The list of files with extra trusted CA certificates for GNUTLS. */ static strlist_t cfg_ca_certlist; /* The global callback for net activity. */ @@ -596,7 +604,7 @@ http_set_verbose (int verbose, int debug) /* Register a non-standard global TLS callback function. If no verification is desired a callback needs to be registered which - always returns NULL. */ + always returns NULL. Only used for GNUTLS. */ void http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int)) { @@ -607,7 +615,7 @@ http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int)) /* Register a CA certificate for future use. The certificate is expected to be in FNAME. PEM format is assume if FNAME has a suffix of ".pem". If FNAME is NULL the list of CA files is - removed. */ + removed. Only used for GNUTLS. */ void http_register_tls_ca (const char *fname) { @@ -636,7 +644,8 @@ http_register_tls_ca (const char *fname) * expected to be in FNAME. PEM format is assume if FNAME has a * suffix of ".pem". If FNAME is NULL the list of CA files is * removed. This is a variant of http_register_tls_ca which puts the - * certificate into a separate list enabled using HTTP_FLAG_TRUST_CFG. */ + * certificate into a separate list enabled using HTTP_FLAG_TRUST_CFG. + * Only used for GNUTLS. */ void http_register_cfg_ca (const char *fname) { @@ -786,6 +795,8 @@ http_session_new (http_session_t *r_session, int add_system_cas = !!(flags & HTTP_FLAG_TRUST_SYS); int is_hkps_pool; + (void)intended_hostname; + rc = gnutls_certificate_allocate_credentials (&sess->certcred); if (rc < 0) { @@ -1789,34 +1800,114 @@ is_hostname_port (const char *string) } -/* - * Send a HTTP request to the server - * Returns 0 if the request was successful - */ +/* Free the PROXY object. */ +static void +release_proxy_info (proxy_info_t proxy) +{ + if (!proxy) + return; + http_release_parsed_uri (proxy->uri); + xfree (proxy); +} + + +/* Return the proxy to be used for the URL or host specified in HD. + * If OVERRIDE_PROXY is not NULL and not empty, this proxy will be + * used instead of any configured or dynamically determined proxy. If + * the function runs into an error an error code is returned and NULL + * is stored at R_PROXY. If the fucntion was successful and a proxy + * is to be used, information on the procy is stored at R_PROXY; if no + * proxy shall be used R_PROXY is set to NULL. Caller should always + * use release_proxy_info on the value stored at R_PROXY. */ static gpg_error_t -send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, - const char *proxy, const char *srvtag, unsigned int timeout, - strlist_t headers) +get_proxy_for_url (http_t hd, const char *override_proxy, proxy_info_t *r_proxy) { gpg_error_t err; - const char *server; - char *request, *p; - unsigned short port; - const char *http_proxy = NULL; - char *proxy_authstr = NULL; - char *authstr = NULL; - assuan_fd_t sock; - int have_http_proxy = 0; + const char *proxystr, *s; + proxy_info_t proxy; + + r_proxy = NULL; + + if (override_proxy && *override_proxy) + proxystr = override_proxy; + else if (!(hd->flags & HTTP_FLAG_TRY_PROXY)) + return 0; /* --honor-http-proxy not active */ + else if ((s = getenv (HTTP_PROXY_ENV)) && *s) + proxystr = s; +#ifdef HAVE_W32_SYSTEM +#endif + else + return 0; /* No proxy known. */ + + proxy = xtrycalloc (1, sizeof *proxy); + if (!proxy) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory for proxy\n"); + return err; + } + + err = parse_uri (&proxy->uri, proxystr, 0, 0); + if (gpg_err_code (err) == GPG_ERR_INV_URI + && is_hostname_port (proxystr)) + { + /* Retry assuming a "hostname:port" string. */ + char *tmpname = strconcat ("http://", proxystr, NULL); + if (!tmpname) + err = gpg_error_from_syserror (); + else if (!parse_uri (&proxy->uri, tmpname, 0, 0)) + err = 0; + xfree (tmpname); + } + + if (!err) + { + /* Get rid of the escapes in the authstring. */ + if (proxy->uri->auth) + remove_escapes (proxy->uri->auth); + + if (!strcmp (proxy->uri->scheme, "http")) + proxy->is_http_proxy = 1; + else if (!strcmp (proxy->uri->scheme, "socks4") + || !strcmp (proxy->uri->scheme, "socks5h")) + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + else + err = gpg_error (GPG_ERR_INV_URI); + + if (err) + { + log_error ("invalid HTTP proxy (%s): %s\n", + proxystr, gpg_strerror (err)); + err = gpg_err_make (default_errsource, GPG_ERR_CONFIGURATION); + } + else if (opt_verbose) + log_info ("using '%s' to proxy '%s'\n", + proxystr, hd->uri? hd->uri->original : NULL); + } + + if (err) + xfree (proxy); + else + *r_proxy = proxy; + return err; +} + + +/* Some checks done by send_request. */ +static gpg_error_t +send_request_basic_checks (http_t hd) +{ + int mode; if (hd->uri->use_tls && !hd->session) { log_error ("TLS requested but no session object provided\n"); - return gpg_err_make (default_errsource, GPG_ERR_INTERNAL); + return gpg_error (GPG_ERR_INTERNAL); } if (hd->uri->use_tls && !hd->session->tls_session) { log_error ("TLS requested but no TLS context available\n"); - return gpg_err_make (default_errsource, GPG_ERR_INTERNAL); + return gpg_error (GPG_ERR_INTERNAL); } if (opt_debug) log_debug ("Using TLS library: %s %s\n", @@ -1827,37 +1918,35 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, #endif /*HTTP_USE_GNUTLS*/ ); - if ((hd->flags & HTTP_FLAG_FORCE_TOR)) + if ((hd->flags & HTTP_FLAG_FORCE_TOR) + && (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)) { - int mode; - - if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode) - { - log_error ("Tor support is not available\n"); - return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED); - } - /* Non-blocking connects do not work with our Tor proxy because - * we can't continue the Socks protocol after the EINPROGRESS. - * Disable the timeout to use a blocking connect. */ - timeout = 0; + log_error ("Tor support is not available\n"); + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); } - server = *hd->uri->host ? hd->uri->host : "localhost"; - port = hd->uri->port ? hd->uri->port : 80; + return 0; +} + + +/* Helper for send_request to set the servername. */ +static gpg_error_t +send_request_set_sni (http_t hd, const char *name) +{ + gpg_error_t err = 0; +# if HTTP_USE_GNUTLS + int rc; +# endif /* Try to use SNI. */ if (hd->uri->use_tls) { -#if HTTP_USE_GNUTLS - int rc; -#endif - xfree (hd->session->servername); - hd->session->servername = xtrystrdup (httphost? httphost : server); + hd->session->servername = xtrystrdup (name); if (!hd->session->servername) { - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - return err; + err = gpg_error_from_syserror (); + goto leave; } #if HTTP_USE_NTBTLS @@ -1866,7 +1955,7 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, if (err) { log_info ("ntbtls_set_hostname failed: %s\n", gpg_strerror (err)); - return err; + goto leave; } #elif HTTP_USE_GNUTLS rc = gnutls_server_name_set (hd->session->tls_session, @@ -1878,175 +1967,21 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, #endif /*HTTP_USE_GNUTLS*/ } - if ( (proxy && *proxy) - || ( (hd->flags & HTTP_FLAG_TRY_PROXY) - && (http_proxy = getenv (HTTP_PROXY_ENV)) - && *http_proxy )) - { - parsed_uri_t uri; + leave: + return err; +} - if (proxy) - http_proxy = proxy; - - err = parse_uri (&uri, http_proxy, 0, 0); - if (gpg_err_code (err) == GPG_ERR_INV_URI - && is_hostname_port (http_proxy)) - { - /* Retry assuming a "hostname:port" string. */ - char *tmpname = strconcat ("http://", http_proxy, NULL); - if (tmpname && !parse_uri (&uri, tmpname, 0, 0)) - err = 0; - xfree (tmpname); - } - - if (err) - ; - else if (!strcmp (uri->scheme, "http")) - have_http_proxy = 1; - else if (!strcmp (uri->scheme, "socks4") - || !strcmp (uri->scheme, "socks5h")) - err = gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED); - else - err = gpg_err_make (default_errsource, GPG_ERR_INV_URI); - - if (err) - { - log_error ("invalid HTTP proxy (%s): %s\n", - http_proxy, gpg_strerror (err)); - return gpg_err_make (default_errsource, GPG_ERR_CONFIGURATION); - } - - if (uri->auth) - { - remove_escapes (uri->auth); - proxy_authstr = make_header_line ("Proxy-Authorization: Basic ", - "\r\n", - uri->auth, strlen(uri->auth)); - if (!proxy_authstr) - { - err = gpg_err_make (default_errsource, - gpg_err_code_from_syserror ()); - http_release_parsed_uri (uri); - return err; - } - } - - err = connect_server (ctrl, - *uri->host ? uri->host : "localhost", - uri->port ? uri->port : 80, - hd->flags, NULL, timeout, &sock); - http_release_parsed_uri (uri); - } - else - { - err = connect_server (ctrl, - server, port, hd->flags, srvtag, timeout, &sock); - } - - if (err) - { - xfree (proxy_authstr); - return err; - } - hd->sock = my_socket_new (sock); - if (!hd->sock) - { - xfree (proxy_authstr); - return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - } - - if (have_http_proxy && hd->uri->use_tls) - { - int saved_flags; - cookie_t cookie; - - /* Try to use the CONNECT method to proxy our TLS stream. */ - request = es_bsprintf - ("CONNECT %s:%hu HTTP/1.0\r\nHost: %s:%hu\r\n%s", - httphost ? httphost : server, - port, - httphost ? httphost : server, - port, - proxy_authstr ? proxy_authstr : ""); - xfree (proxy_authstr); - proxy_authstr = NULL; - - if (! request) - return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - - if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) - log_debug_string (request, "http.c:request:"); - - cookie = xtrycalloc (1, sizeof *cookie); - if (! cookie) - { - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - xfree (request); - return err; - } - cookie->sock = my_socket_ref (hd->sock); - hd->write_cookie = cookie; - - hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); - if (! hd->fp_write) - { - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - my_socket_unref (cookie->sock, NULL, NULL); - xfree (cookie); - xfree (request); - hd->write_cookie = NULL; - return err; - } - else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - - xfree (request); - request = NULL; - - /* Make sure http_wait_response doesn't close the stream. */ - saved_flags = hd->flags; - hd->flags &= ~HTTP_FLAG_SHUTDOWN; - - /* Get the response. */ - err = http_wait_response (hd); - - /* Restore flags, destroy stream. */ - hd->flags = saved_flags; - es_fclose (hd->fp_read); - hd->fp_read = NULL; - hd->read_cookie = NULL; - - /* Reset state. */ - hd->in_data = 0; - - if (err) - return err; - - if (hd->status_code != 200) - { - request = es_bsprintf - ("CONNECT %s:%hu", - httphost ? httphost : server, - port); - - log_error (_("error accessing '%s': http status %u\n"), - request ? request : "out of core", - http_get_status_code (hd)); - - xfree (request); - return gpg_error (GPG_ERR_NO_DATA); - } - - /* We are done with the proxy, the code below will establish a - * TLS session and talk directly to the target server. */ - http_proxy = NULL; - } +/* Run the NTBTLS handshake if needed. */ #if HTTP_USE_NTBTLS +static gpg_error_t +run_ntbtls_handshake (http_t hd) +{ + gpg_error_t err; + estream_t in, out; + if (hd->uri->use_tls) { - estream_t in, out; - my_socket_ref (hd->sock); /* Until we support send/recv in estream under Windows we need @@ -2060,8 +1995,7 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, if (!in) { err = gpg_error_from_syserror (); - xfree (proxy_authstr); - return err; + goto leave; } # ifdef HAVE_W32_SYSTEM @@ -2074,8 +2008,7 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, { err = gpg_error_from_syserror (); es_fclose (in); - xfree (proxy_authstr); - return err; + goto leave; } err = ntbtls_set_transport (hd->session->tls_session, in, out); @@ -2083,8 +2016,9 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, { log_info ("TLS set_transport failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); - xfree (proxy_authstr); - return err; + es_fclose (in); + es_fclose (out); + goto leave; } if (hd->session->verify_cb) @@ -2095,71 +2029,62 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, { log_error ("ntbtls_set_verify_cb failed: %s\n", gpg_strerror (err)); - xfree (proxy_authstr); - return err; + goto leave; } } while ((err = ntbtls_handshake (hd->session->tls_session))) { -#if NTBTLS_VERSION_NUMBER >= 0x000200 unsigned int tlevel, ttype; - const char *s = ntbtls_get_last_alert (hd->session->tls_session, - &tlevel, &ttype); + const char *s; + + s = ntbtls_get_last_alert (hd->session->tls_session, &tlevel, &ttype); if (s) log_info ("TLS alert: %s (%u.%u)\n", s, tlevel, ttype); -#endif switch (err) { default: log_info ("TLS handshake failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); - xfree (proxy_authstr); - return err; + goto leave; } } hd->session->verify.done = 0; - /* Try the available verify callbacks until one returns success - * or a real error. Note that NTBTLS does the verification - * during the handshake via */ - err = 0; /* Fixme check that the CB has been called. */ + /* Note that in contrast to GNUTLS NTBTLS uses a registered + * callback to run the verification as part of the handshake. */ + err = 0; + /* FIXME: We could check that the CB has been called and if not + * error out with this warning: + * if (err) + * { + * log_info ("TLS connection authentication failed: %s <%s>\n", + * gpg_strerror (err), gpg_strsource (err)); + * goto leave; + * } + */ + } + else + err = 0; - if (hd->session->verify_cb - && gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR - && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED) - err = hd->session->verify_cb (hd->session->verify_cb_value, - hd, hd->session, - (hd->flags | hd->session->flags), - hd->session->tls_session); + leave: + return err; +} +#endif /*HTTP_USE_NTBTLS*/ - if (tls_callback - && gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR - && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED) - err = tls_callback (hd, hd->session, 0); - if (gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR - && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED) - err = http_verify_server_credentials (hd->session); - - if (err) - { - log_info ("TLS connection authentication failed: %s <%s>\n", - gpg_strerror (err), gpg_strsource (err)); - xfree (proxy_authstr); - return err; - } - - } - -#elif HTTP_USE_GNUTLS +/* Run the GNUTLS handshake if needed. */ +#if HTTP_USE_GNUTLS +static gpg_error_t +run_gnutls_handshake (http_t hd, const char *server) +{ + gpg_error_t err; + int rc; if (hd->uri->use_tls) { - int rc; - my_socket_ref (hd->sock); gnutls_transport_set_ptr (hd->session->tls_session, hd->sock); gnutls_transport_set_pull_function (hd->session->tls_session, @@ -2195,8 +2120,8 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, } else log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc)); - xfree (proxy_authstr); - return gpg_err_make (default_errsource, GPG_ERR_NETWORK); + err = gpg_error (GPG_ERR_NETWORK); + goto leave; } hd->session->verify.done = 0; @@ -2208,13 +2133,195 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, { log_info ("TLS connection authentication failed: %s\n", gpg_strerror (err)); - xfree (proxy_authstr); - return err; + goto leave; } } + else + err =0; + leave: + return err; +} #endif /*HTTP_USE_GNUTLS*/ + +/* + * Send a HTTP request to the server + * Returns 0 if the request was successful + */ +static gpg_error_t +send_request (ctrl_t ctrl, + http_t hd, const char *httphost, const char *auth, + const char *override_proxy, + const char *srvtag, unsigned int timeout, + strlist_t headers) +{ + gpg_error_t err; + const char *server; + char *request = NULL; + char *relpath = NULL; + unsigned short port; + int use_http_proxy = 0; + char *proxy_authstr = NULL; + char *authstr = NULL; + assuan_fd_t sock; + proxy_info_t proxy = NULL; + cookie_t cookie = NULL; + cookie_t cookie2 = NULL; + + err = send_request_basic_checks (hd); + if (err) + goto leave; + + if ((hd->flags & HTTP_FLAG_FORCE_TOR)) + { + /* Non-blocking connects do not work with our Tor proxy because + * we can't continue the Socks protocol after the EINPROGRESS. + * Disable the timeout to use a blocking connect. */ + timeout = 0; + } + + server = *hd->uri->host ? hd->uri->host : "localhost"; + port = hd->uri->port ? hd->uri->port : 80; + + if ((err = send_request_set_sni (hd, httphost? httphost : server))) + goto leave; + + if ((err = get_proxy_for_url (hd, override_proxy, &proxy))) + goto leave; + + if (proxy && proxy->is_http_proxy) + { + use_http_proxy = 1; /* We want to use a proxy for the conenction. */ + err = connect_server (ctrl, + *proxy->uri->host ? proxy->uri->host : "localhost", + proxy->uri->port ? proxy->uri->port : 80, + hd->flags, NULL, timeout, &sock); + } + else + { + err = connect_server (ctrl, + server, port, hd->flags, srvtag, timeout, &sock); + } + if (err) + goto leave; + + hd->sock = my_socket_new (sock); + if (!hd->sock) + { + err = gpg_error_from_syserror (); + goto leave; + } + +#if USE_TLS + if (use_http_proxy && hd->uri->use_tls) + { + int saved_flags; + + log_assert (!proxy_authstr); + if (proxy->uri->auth + && !(proxy_authstr = make_header_line ("Proxy-Authorization: Basic ", + "\r\n", + proxy->uri->auth, + strlen (proxy->uri->auth)))) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Try to use the CONNECT method to proxy our TLS stream. */ + xfree (request); + request = es_bsprintf + ("CONNECT %s:%hu HTTP/1.0\r\nHost: %s:%hu\r\n%s", + httphost ? httphost : server, + port, + httphost ? httphost : server, + port, + proxy_authstr ? proxy_authstr : ""); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) + log_debug_string (request, "http.c:request:"); + + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + { + err = gpg_error_from_syserror (); + goto leave; + } + cookie->sock = my_socket_ref (hd->sock); + hd->write_cookie = cookie; + + hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); + if (! hd->fp_write) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Make sure http_wait_response doesn't close the stream. */ + saved_flags = hd->flags; + hd->flags &= ~HTTP_FLAG_SHUTDOWN; + + /* Get the response and set hd->fp_read */ + err = http_wait_response (hd); + + /* Restore flags, destroy stream. */ + hd->flags = saved_flags; + es_fclose (hd->fp_read); + hd->fp_read = NULL; + hd->read_cookie = NULL; + + /* Reset state. */ + hd->in_data = 0; + + if (err) + goto leave; + + if (hd->status_code != 200) + { + xfree (request); + request = es_bsprintf + ("CONNECT %s:%hu", + httphost ? httphost : server, + port); + + log_error (_("error accessing '%s': http status %u\n"), + request ? request : "out of core", + http_get_status_code (hd)); + + err = gpg_error (GPG_ERR_NO_DATA); + goto leave; + } + + /* We are done with the proxy, the code below will establish a + * TLS session and talk directly to the target server. Thus we + * clear the flag to indicate this. */ + use_http_proxy = 0; + } +#endif /* USE_TLS */ + +#if HTTP_USE_NTBTLS + err = run_ntbtls_handshake (hd); +#elif HTTP_USE_GNUTLS + err = run_gnutls_handshake (hd, server); +#else + err = 0; +#endif + + if (err) + goto leave; + if (auth || hd->uri->auth) { char *myauth; @@ -2224,9 +2331,8 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, myauth = xtrystrdup (auth); if (!myauth) { - xfree (proxy_authstr); - return gpg_err_make (default_errsource, - gpg_err_code_from_syserror ()); + err = gpg_error_from_syserror (); + goto leave; } remove_escapes (myauth); } @@ -2238,27 +2344,38 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, authstr = make_header_line ("Authorization: Basic ", "\r\n", myauth, strlen (myauth)); - if (auth) + if (auth) /* (Was allocated.) */ xfree (myauth); if (!authstr) { - xfree (proxy_authstr); - return gpg_err_make (default_errsource, - gpg_err_code_from_syserror ()); + err = gpg_error_from_syserror (); + goto leave; } } - p = build_rel_path (hd->uri); - if (!p) + relpath = build_rel_path (hd->uri); + if (!relpath) { - xfree (authstr); - xfree (proxy_authstr); - return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + err = gpg_error_from_syserror (); + goto leave; } - if (http_proxy && *http_proxy) + if (use_http_proxy) { + xfree (proxy_authstr); + proxy_authstr = NULL; + if (proxy->uri->auth + && !(proxy_authstr = make_header_line ("Proxy-Authorization: Basic ", + "\r\n", + proxy->uri->auth, + strlen (proxy->uri->auth)))) + { + err = gpg_error_from_syserror (); + goto leave; + } + + xfree (request); request = es_bsprintf ("%s %s://%s:%hu%s%s HTTP/1.0\r\n%s%s", hd->req_type == HTTP_REQ_GET ? "GET" : @@ -2266,9 +2383,14 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", hd->uri->use_tls? "https" : "http", httphost? httphost : server, - port, *p == '/' ? "" : "/", p, + port, *relpath == '/' ? "" : "/", relpath, authstr ? authstr : "", proxy_authstr ? proxy_authstr : ""); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } } else { @@ -2279,23 +2401,21 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, else snprintf (portstr, sizeof portstr, ":%u", port); + xfree (request); request = es_bsprintf ("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s", hd->req_type == HTTP_REQ_GET ? "GET" : hd->req_type == HTTP_REQ_HEAD ? "HEAD" : hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", - *p == '/' ? "" : "/", p, + *relpath == '/' ? "" : "/", relpath, httphost? httphost : server, portstr, authstr? authstr:""); - } - xfree (p); - if (!request) - { - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - xfree (authstr); - xfree (proxy_authstr); - return err; + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } } if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) @@ -2304,53 +2424,63 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, /* First setup estream so that we can write even the first line using estream. This is also required for the sake of gnutls. */ { - cookie_t cookie; - - cookie = xtrycalloc (1, sizeof *cookie); - if (!cookie) + cookie2 = xtrycalloc (1, sizeof *cookie); + if (!cookie2) { - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + err = gpg_error_from_syserror (); goto leave; } - cookie->sock = my_socket_ref (hd->sock); - hd->write_cookie = cookie; - cookie->use_tls = hd->uri->use_tls; - cookie->session = http_session_ref (hd->session); + cookie2->sock = my_socket_ref (hd->sock); + hd->write_cookie = cookie2; + cookie2->use_tls = hd->uri->use_tls; + cookie2->session = http_session_ref (hd->session); hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); if (!hd->fp_write) { - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - my_socket_unref (cookie->sock, NULL, NULL); - xfree (cookie); - hd->write_cookie = NULL; + err = gpg_error_from_syserror (); + goto leave; + } + if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) + { + err = gpg_error_from_syserror (); + goto leave; } - else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - else - err = 0; - if (!err) - { - for (;headers; headers=headers->next) - { - if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) - log_debug_string (headers->d, "http.c:request-header:"); - if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write)) - || (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write))) - { - err = gpg_err_make (default_errsource, - gpg_err_code_from_syserror ()); - break; - } - } - } + for (;headers; headers=headers->next) + { + if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) + log_debug_string (headers->d, "http.c:request-header:"); + if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write)) + || (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write))) + { + err = gpg_error_from_syserror (); + goto leave; + } + } } leave: + if (cookie2) + { + my_socket_unref (cookie2->sock, NULL, NULL); + if (hd) + hd->write_cookie = NULL; + xfree (cookie2); + } + if (cookie) + { + my_socket_unref (cookie->sock, NULL, NULL); + if (hd) + hd->write_cookie = NULL; + xfree (cookie); + } + es_free (request); xfree (authstr); xfree (proxy_authstr); + xfree (relpath); + release_proxy_info (proxy); return err; } @@ -3447,7 +3577,7 @@ cookie_close (void *cookie) /* Verify the credentials of the server. Returns 0 on success and - store the result in the session object. */ + store the result in the session object. Only used by GNUTLS. */ gpg_error_t http_verify_server_credentials (http_session_t sess) { diff --git a/dirmngr/http.h b/dirmngr/http.h index e60212761..2994fdfad 100644 --- a/dirmngr/http.h +++ b/dirmngr/http.h @@ -132,9 +132,11 @@ typedef gpg_error_t (*http_verify_cb_t) (void *opaque, void http_set_verbose (int verbose, int debug); +/* The next three functions are only used with GNUTLS. */ void http_register_tls_callback (gpg_error_t (*cb)(http_t,http_session_t,int)); void http_register_tls_ca (const char *fname); void http_register_cfg_ca (const char *fname); + void http_register_netactivity_cb (void (*cb)(void)); From fed33baed1cb0c4b09c48277de73becb6aef4bb1 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 19 Sep 2023 12:49:04 +0200 Subject: [PATCH 20/55] dirmngr: Further simplify the http code and improve a message. * dirmngr/http.c (make_fp_write, make_fp_read): New. (http_raw_connect): Use new functions. (http_wait_response): Ditto. (send_request): Ditto. Change proxy error diagnostic. (connect_server): Improve error message for host not found. -- GnuPG-bug-id: 5768 --- dirmngr/http.c | 226 ++++++++++++++++++++++--------------------------- 1 file changed, 100 insertions(+), 126 deletions(-) diff --git a/dirmngr/http.c b/dirmngr/http.c index 2189d7249..e8b6ae4d8 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -746,6 +746,64 @@ http_session_release (http_session_t sess) } +/* Create a write stream and store it in the fp_write member. Also + * store the tls flag and the session. */ +static gpg_error_t +make_fp_write (http_t hd, int use_tls, http_session_t session) +{ + cookie_t cookie; + + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + return gpg_error_from_syserror (); + cookie->sock = my_socket_ref (hd->sock); + cookie->use_tls = use_tls; + if (session) + cookie->session = http_session_ref (session); + hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); + if (!hd->fp_write) + { + gpg_error_t err = gpg_error_from_syserror (); + my_socket_unref (cookie->sock, NULL, NULL); + if (session) + http_session_unref (cookie->session); + xfree (cookie); + return err; + } + hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE. */ + return 0; +} + + +/* Create a read stream and store it in the fp_read member. Also + * store the tls flag and the session. */ +static gpg_error_t +make_fp_read (http_t hd, int use_tls, http_session_t session) +{ + cookie_t cookie; + + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + return gpg_error_from_syserror (); + cookie->sock = my_socket_ref (hd->sock); + cookie->use_tls = use_tls; + if (session) + cookie->session = http_session_ref (session); + hd->fp_read = es_fopencookie (cookie, "r", cookie_functions); + if (!hd->fp_read) + { + gpg_error_t err = gpg_error_from_syserror (); + my_socket_unref (cookie->sock, NULL, NULL); + if (session) + http_session_unref (cookie->session); + xfree (cookie); + return err; + } + hd->read_cookie = cookie; /* Cookie now owned by FP_READ. */ + return 0; +} + + /* Create a new session object which is currently used to enable TLS * support. It may eventually allow reusing existing connections. * Valid values for FLAGS are: @@ -1038,7 +1096,6 @@ http_raw_connect (ctrl_t ctrl, http_t *r_hd, { gpg_error_t err = 0; http_t hd; - cookie_t cookie; *r_hd = NULL; @@ -1086,39 +1143,13 @@ http_raw_connect (ctrl_t ctrl, http_t *r_hd, } /* Setup estreams for reading and writing. */ - cookie = xtrycalloc (1, sizeof *cookie); - if (!cookie) - { - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - goto leave; - } - cookie->sock = my_socket_ref (hd->sock); - hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); - if (!hd->fp_write) - { - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - my_socket_unref (cookie->sock, NULL, NULL); - xfree (cookie); - goto leave; - } - hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE. */ + err = make_fp_write (hd, 0, NULL); + if (err) + goto leave; - cookie = xtrycalloc (1, sizeof *cookie); - if (!cookie) - { - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - goto leave; - } - cookie->sock = my_socket_ref (hd->sock); - hd->fp_read = es_fopencookie (cookie, "r", cookie_functions); - if (!hd->fp_read) - { - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - my_socket_unref (cookie->sock, NULL, NULL); - xfree (cookie); - goto leave; - } - hd->read_cookie = cookie; /* Cookie now owned by FP_READ. */ + err = make_fp_read (hd, 0, NULL); + if (err) + goto leave; /* Register close notification to interlock the use of es_fclose in http_close and in user code. */ @@ -1190,24 +1221,9 @@ http_wait_response (http_t hd) hd->in_data = 0; /* Create a new cookie and a stream for reading. */ - cookie = xtrycalloc (1, sizeof *cookie); - if (!cookie) - return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - cookie->sock = my_socket_ref (hd->sock); - cookie->session = http_session_ref (hd->session); - cookie->use_tls = use_tls; - - hd->read_cookie = cookie; - hd->fp_read = es_fopencookie (cookie, "r", cookie_functions); - if (!hd->fp_read) - { - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - my_socket_unref (cookie->sock, NULL, NULL); - http_session_unref (cookie->session); - xfree (cookie); - hd->read_cookie = NULL; - return err; - } + err = make_fp_read (hd, use_tls, hd->session); + if (err) + return err; err = parse_response (hd); @@ -2166,8 +2182,6 @@ send_request (ctrl_t ctrl, char *authstr = NULL; assuan_fd_t sock; proxy_info_t proxy = NULL; - cookie_t cookie = NULL; - cookie_t cookie2 = NULL; err = send_request_basic_checks (hd); if (err) @@ -2247,21 +2261,9 @@ send_request (ctrl_t ctrl, if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) log_debug_string (request, "http.c:request:"); - cookie = xtrycalloc (1, sizeof *cookie); - if (!cookie) - { - err = gpg_error_from_syserror (); - goto leave; - } - cookie->sock = my_socket_ref (hd->sock); - hd->write_cookie = cookie; - - hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); - if (! hd->fp_write) - { - err = gpg_error_from_syserror (); - goto leave; - } + err = make_fp_write (hd, 0, NULL); + if (err) + goto leave; if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) { @@ -2290,16 +2292,13 @@ send_request (ctrl_t ctrl, if (hd->status_code != 200) { - xfree (request); - request = es_bsprintf - ("CONNECT %s:%hu", - httphost ? httphost : server, - port); + char *tmpstr; + tmpstr = es_bsprintf ("%s:%hu", httphost ? httphost : server, port); log_error (_("error accessing '%s': http status %u\n"), - request ? request : "out of core", + tmpstr ? tmpstr : "out of core", http_get_status_code (hd)); - + xfree (tmpstr); err = gpg_error (GPG_ERR_NO_DATA); goto leave; } @@ -2318,7 +2317,6 @@ send_request (ctrl_t ctrl, #else err = 0; #endif - if (err) goto leave; @@ -2423,59 +2421,29 @@ send_request (ctrl_t ctrl, /* First setup estream so that we can write even the first line using estream. This is also required for the sake of gnutls. */ - { - cookie2 = xtrycalloc (1, sizeof *cookie); - if (!cookie2) - { - err = gpg_error_from_syserror (); - goto leave; - } - cookie2->sock = my_socket_ref (hd->sock); - hd->write_cookie = cookie2; - cookie2->use_tls = hd->uri->use_tls; - cookie2->session = http_session_ref (hd->session); + err = make_fp_write (hd, hd->uri->use_tls, hd->session); + if (err) + goto leave; - hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); - if (!hd->fp_write) - { - err = gpg_error_from_syserror (); - goto leave; - } - if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) - { - err = gpg_error_from_syserror (); - goto leave; - } + if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) + { + err = gpg_error_from_syserror (); + goto leave; + } - for (;headers; headers=headers->next) - { - if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) - log_debug_string (headers->d, "http.c:request-header:"); - if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write)) + for (;headers; headers=headers->next) + { + if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) + log_debug_string (headers->d, "http.c:request-header:"); + if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write)) || (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write))) - { - err = gpg_error_from_syserror (); - goto leave; - } - } - } + { + err = gpg_error_from_syserror (); + goto leave; + } + } leave: - if (cookie2) - { - my_socket_unref (cookie2->sock, NULL, NULL); - if (hd) - hd->write_cookie = NULL; - xfree (cookie2); - } - if (cookie) - { - my_socket_unref (cookie->sock, NULL, NULL); - if (hd) - hd->write_cookie = NULL; - xfree (cookie); - } - es_free (request); xfree (authstr); xfree (proxy_authstr); @@ -3231,8 +3199,14 @@ connect_server (ctrl_t ctrl, const char *server, unsigned short port, if (!connected) { if (!hostfound) - log_error ("can't connect to '%s': %s\n", - server, "host not found"); + { + log_error ("can't connect to '%s': %s\n", + server, "host not found"); + /* If the resolver told us "no name" translate this in this + * case to "unknown host". */ + if (gpg_err_code (last_err) == GPG_ERR_NO_NAME) + last_err = 0; + } else if (!anyhostaddr) log_error ("can't connect to '%s': %s\n", server, "no IP address for host"); From 1e120f5a8d529150cd0268eb104b8f0d84f7b5ae Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 19 Sep 2023 15:04:49 +0200 Subject: [PATCH 21/55] dirmngr: Implement automatic proxy detection on Windows. * dirmngr/http.c [W32]: Include winhttp.h (w32_get_internet_session): New. (w32_get_proxy): New. (get_proxy_for_url): Implement automatic proxy detection and fix error in last patch. (http_reinitialize): New. * dirmngr/dirmngr.c (dirmngr_sighup_action): Call reinitialize. * dirmngr/Makefile.am (NETLIBS) [W32]: Link with winhttp. -- GnuPG-bug-id: 5768 --- dirmngr/Makefile.am | 1 + dirmngr/dirmngr.c | 1 + dirmngr/http-common.h | 2 + dirmngr/http.c | 162 +++++++++++++++++++++++++++++++++++++++++- doc/dirmngr.texi | 4 +- 5 files changed, 166 insertions(+), 4 deletions(-) diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am index 3846fdf35..9665b5dfd 100644 --- a/dirmngr/Makefile.am +++ b/dirmngr/Makefile.am @@ -68,6 +68,7 @@ AM_CFLAGS = $(USE_C99_CFLAGS) \ if HAVE_W32_SYSTEM ldap_url = ldap-url.h ldap-url.c +NETLIBS += -lwinhttp else ldap_url = endif diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 97c2dc490..f79a0f877 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -2045,6 +2045,7 @@ dirmngr_sighup_action (void) crl_cache_deinit (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); + http_reinitialize (); reload_dns_stuff (0); ks_hkp_reload (); } diff --git a/dirmngr/http-common.h b/dirmngr/http-common.h index 5e6657b16..ddb340de6 100644 --- a/dirmngr/http-common.h +++ b/dirmngr/http-common.h @@ -22,4 +22,6 @@ const char *get_default_keyserver (int name_only); +void http_reinitialize (void); + #endif /* HTTP_COMMON_H */ diff --git a/dirmngr/http.c b/dirmngr/http.c index e8b6ae4d8..7cab1c2e5 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -64,6 +64,7 @@ # include # endif # include +# include # ifndef EHOSTUNREACH # define EHOSTUNREACH WSAEHOSTUNREACH # endif @@ -1827,6 +1828,141 @@ release_proxy_info (proxy_info_t proxy) } +/* Return an http session object. If clear is set, the object is + * destroyed. On error nULL is returned. */ +#ifdef HAVE_W32_SYSTEM +static HINTERNET +w32_get_internet_session (int clear) +{ + static HINTERNET session; + + if (clear) + { + if (session) + { + WinHttpCloseHandle (session); + session = NULL; + } + return NULL; + } + + if (!session) + { + session = WinHttpOpen (L"GnuPG dirmngr", + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + if (!session) + { + log_error ("WinHttpOpen failed: %s\n", w32_strerror (-1)); + return NULL; + } + } + + return session; +} +#endif /*HAVE_W32_SYSTEM*/ + + +/* Return a proxy using a Windows API. */ +#ifdef HAVE_W32_SYSTEM +static char * +w32_get_proxy (const char *url) +{ + WINHTTP_AUTOPROXY_OPTIONS options = {0}; + WINHTTP_PROXY_INFO info; + char *result = NULL; + char *p; + wchar_t *wurl; + int defaultcfg = 0; + + wurl = utf8_to_wchar (url); + if (!wurl) + { + log_error ("utf8_to_wchar failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + return NULL; + } + + options.dwFlags = (WINHTTP_AUTOPROXY_ALLOW_AUTOCONFIG + | WINHTTP_AUTOPROXY_ALLOW_CM + | WINHTTP_AUTOPROXY_ALLOW_STATIC + | WINHTTP_AUTOPROXY_AUTO_DETECT + | WINHTTP_AUTOPROXY_SORT_RESULTS); + options.dwAutoDetectFlags = (WINHTTP_AUTO_DETECT_TYPE_DHCP + | WINHTTP_AUTO_DETECT_TYPE_DNS_A); + options.fAutoLogonIfChallenged = TRUE; + + if (opt_debug) + log_debug ("calling WinHttpGetProxyForUrl (%s)\n", url); + if (!WinHttpGetProxyForUrl (w32_get_internet_session (0), + wurl, &options, &info)) + { + int ec = (int)GetLastError (); + if (ec == ERROR_WINHTTP_AUTODETECTION_FAILED) + { + if (opt_debug) + log_debug ("calling WinHttpGetDefaultProxyConfiguration\n"); + if (!WinHttpGetDefaultProxyConfiguration (&info)) + { + if (opt_verbose) + log_info ("WinHttpGetDefaultProxyConfiguration failed: " + "%s (%d)\n", w32_strerror (ec), ec); + xfree (wurl); + return NULL; + } + defaultcfg = 1; + } + else + { + if (opt_verbose) + log_info ("WinHttpGetProxyForUrl failed: %s (%d)\n", + w32_strerror (ec), ec); + xfree (wurl); + return NULL; + } + } + xfree (wurl); + + if (info.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY) + { + result = wchar_to_utf8 (info.lpszProxy); + if (!result) + log_error ("wchar_to_utf8 failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + else + { + if (opt_debug) + log_debug ("proxies to use: '%s'\n", result); + /* The returned proxies are delimited by whitespace or + * semicolons. We return only the first proxy. */ + for (p=result; *p; p++) + if (spacep (p) || *p == ';') + { + *p = 0; + break; + } + } + } + else if (info.dwAccessType == WINHTTP_ACCESS_TYPE_NO_PROXY) + { + /* No proxy shall be used. */ + } + else + log_error ("%s returned unexpected code %lu\n", + defaultcfg? "WinHttpGetDefaultProxyConfiguration" + :"WinHttpGetProxyForUrl", info.dwAccessType); + + if (info.lpszProxy) + GlobalFree (info.lpszProxy); + if (info.lpszProxyBypass) + GlobalFree (info.lpszProxyBypass); + return result; +} +#endif /*HAVE_W32_SYSTEM*/ + + /* Return the proxy to be used for the URL or host specified in HD. * If OVERRIDE_PROXY is not NULL and not empty, this proxy will be * used instead of any configured or dynamically determined proxy. If @@ -1838,11 +1974,14 @@ release_proxy_info (proxy_info_t proxy) static gpg_error_t get_proxy_for_url (http_t hd, const char *override_proxy, proxy_info_t *r_proxy) { - gpg_error_t err; + gpg_error_t err = 0; const char *proxystr, *s; proxy_info_t proxy; +#ifdef HAVE_W32_SYSTEM + char *proxystrbuf = NULL; +#endif - r_proxy = NULL; + *r_proxy = NULL; if (override_proxy && *override_proxy) proxystr = override_proxy; @@ -1851,6 +1990,9 @@ get_proxy_for_url (http_t hd, const char *override_proxy, proxy_info_t *r_proxy) else if ((s = getenv (HTTP_PROXY_ENV)) && *s) proxystr = s; #ifdef HAVE_W32_SYSTEM + else if (hd->uri && hd->uri->original + && (proxystrbuf = w32_get_proxy (hd->uri->original))) + proxystr = proxystrbuf; #endif else return 0; /* No proxy known. */ @@ -1860,7 +2002,7 @@ get_proxy_for_url (http_t hd, const char *override_proxy, proxy_info_t *r_proxy) { err = gpg_error_from_syserror (); log_error ("error allocating memory for proxy\n"); - return err; + goto leave; } err = parse_uri (&proxy->uri, proxystr, 0, 0); @@ -1901,6 +2043,10 @@ get_proxy_for_url (http_t hd, const char *override_proxy, proxy_info_t *r_proxy) proxystr, hd->uri? hd->uri->original : NULL); } + leave: +#ifdef HAVE_W32_SYSTEM + xfree (proxystrbuf); +#endif if (err) xfree (proxy); else @@ -3938,3 +4084,13 @@ http_status2string (unsigned int status) return ""; } + + +/* Fucntion called on SIGHUP to flush internal variables. */ +void +http_reinitialize (void) +{ +#ifdef HAVE_W32_SYSTEM + w32_get_internet_session (1); /* Clear our session. */ +#endif /*HAVE_W32_SYSTEM*/ +} diff --git a/doc/dirmngr.texi b/doc/dirmngr.texi index 398888e71..cd7969828 100644 --- a/doc/dirmngr.texi +++ b/doc/dirmngr.texi @@ -427,7 +427,9 @@ force the use of the default responder. @item --honor-http-proxy @opindex honor-http-proxy If the environment variable @env{http_proxy} has been set, use its -value to access HTTP servers. +value to access HTTP servers. If on Windows the option is used but +the environment variable is not set, the proxy settings are taken +from the system. @item --http-proxy @var{host}[:@var{port}] @opindex http-proxy From 668deeded9742e811a786f97a917c59793fcd9ff Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 19 Sep 2023 16:14:01 +0200 Subject: [PATCH 22/55] dirmngr: Improve error codes returned from http fetching. * dirmngr/ks-engine-http.c (ks_http_fetch): Return better error codes. * dirmngr/ks-engine-hkp.c (send_request): Ditto. * dirmngr/t-http.c (main): New option --try-proxy. --- dirmngr/ks-engine-hkp.c | 17 ++++++++--------- dirmngr/ks-engine-http.c | 12 +++++++----- dirmngr/t-http.c | 5 +++++ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index 66291bc02..a75cc1aee 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -1340,18 +1340,17 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr, } goto once_more; - case 501: - err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); - goto leave; - - case 413: /* Payload too large */ - err = gpg_error (GPG_ERR_TOO_LARGE); - goto leave; - default: log_error (_("error accessing '%s': http status %u\n"), request, http_get_status_code (http)); - err = gpg_error (GPG_ERR_NO_DATA); + switch (http_get_status_code (http)) + { + case 401: err = gpg_error (GPG_ERR_NO_AUTH); break; + case 407: err = gpg_error (GPG_ERR_BAD_AUTH); break; + case 413: err = gpg_error (GPG_ERR_TOO_LARGE); break; + case 501: err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); break; + default: err = gpg_error (GPG_ERR_NO_DATA); break; + } goto leave; } diff --git a/dirmngr/ks-engine-http.c b/dirmngr/ks-engine-http.c index 3dca80ee6..48a734786 100644 --- a/dirmngr/ks-engine-http.c +++ b/dirmngr/ks-engine-http.c @@ -193,14 +193,16 @@ ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags, } goto once_more; - case 413: /* Payload too large */ - err = gpg_error (GPG_ERR_TOO_LARGE); - goto leave; - default: log_error (_("error accessing '%s': http status %u\n"), url, http_get_status_code (http)); - err = gpg_error (GPG_ERR_NO_DATA); + switch (http_get_status_code (http)) + { + case 401: err = gpg_error (GPG_ERR_NO_AUTH); break; + case 407: err = gpg_error (GPG_ERR_BAD_AUTH); break; + case 413: err = gpg_error (GPG_ERR_TOO_LARGE); break; + default: err = gpg_error (GPG_ERR_NO_DATA); break; + } goto leave; } diff --git a/dirmngr/t-http.c b/dirmngr/t-http.c index 7f3aa005d..f9c59783f 100644 --- a/dirmngr/t-http.c +++ b/dirmngr/t-http.c @@ -288,6 +288,11 @@ main (int argc, char **argv) my_http_flags |= HTTP_FLAG_FORCE_TOR; argc--; argv++; } + else if (!strcmp (*argv, "--try-proxy")) + { + my_http_flags |= HTTP_FLAG_TRY_PROXY; + argc--; argv++; + } else if (!strcmp (*argv, "--no-out")) { no_out = 1; From 3054016db9da31f3c18aed8158f764b14e021754 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 21 Sep 2023 13:32:56 +0200 Subject: [PATCH 23/55] dirmngr: Require gnutls 3.2 * dirmngr/http.c: Remove gnutls version specific code. (send_request): Factor some code out to ... (run_proxy_connect): new. (mk_proxy_request): new. (mk_std_request): new. * configure.ac (NEED_GNUTLS_VERSION): Require 3.2. -- This patch is to factor out some code and also to remove support for legacy gnutls versions. Note that gnutls 3.2 was released 10 years ago. --- configure.ac | 2 +- dirmngr/http.c | 331 ++++++++++++++++++++++++++++--------------------- 2 files changed, 194 insertions(+), 139 deletions(-) diff --git a/configure.ac b/configure.ac index cfe6091b5..6f544bf98 100644 --- a/configure.ac +++ b/configure.ac @@ -73,7 +73,7 @@ NEED_NPTH_API=1 NEED_NPTH_VERSION=1.2 -NEED_GNUTLS_VERSION=3.0 +NEED_GNUTLS_VERSION=3.2 NEED_SQLITE_VERSION=3.27 diff --git a/dirmngr/http.c b/dirmngr/http.c index 7cab1c2e5..33eb69804 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -920,7 +920,6 @@ http_session_new (http_session_t *r_session, /* Add system certificates to the session. */ if (add_system_cas) { -#if GNUTLS_VERSION_NUMBER >= 0x030014 static int shown; rc = gnutls_certificate_set_x509_system_trust (sess->certcred); @@ -931,7 +930,6 @@ http_session_new (http_session_t *r_session, shown = 1; log_info ("number of system provided CAs: %d\n", rc); } -#endif /* gnutls >= 3.0.20 */ } /* Add other configured certificates to the session. */ @@ -2307,6 +2305,184 @@ run_gnutls_handshake (http_t hd, const char *server) #endif /*HTTP_USE_GNUTLS*/ +/* Use the CONNECT method to proxy our TLS stream. */ +#ifdef USE_TLS +static gpg_error_t +run_proxy_connect (http_t hd, proxy_info_t proxy, + const char *httphost, const char *server, + unsigned short port) +{ + gpg_error_t err; + int saved_flags; + char *authhdr = NULL; + char *request = NULL; + + if (proxy->uri->auth + && !(authhdr = make_header_line ("Proxy-Authorization: Basic ", + "\r\n", + proxy->uri->auth, + strlen (proxy->uri->auth)))) + { + err = gpg_error_from_syserror (); + goto leave; + } + + request = es_bsprintf ("CONNECT %s:%hu HTTP/1.0\r\nHost: %s:%hu\r\n%s", + httphost ? httphost : server, + port, + httphost ? httphost : server, + port, + authhdr ? authhdr : ""); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) + log_debug_with_string (request, "http.c:proxy:request:"); + + err = make_fp_write (hd, 0, NULL); + if (err) + goto leave; + + if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Make sure http_wait_response doesn't close the stream. */ + saved_flags = hd->flags; + hd->flags &= ~HTTP_FLAG_SHUTDOWN; + + /* Get the response and set hd->fp_read */ + err = http_wait_response (hd); + + /* Restore flags, destroy stream. */ + hd->flags = saved_flags; + es_fclose (hd->fp_read); + hd->fp_read = NULL; + hd->read_cookie = NULL; + + /* Reset state. */ + hd->in_data = 0; + + if (err) + goto leave; + + if (hd->status_code != 200) + { + char *tmpstr; + + tmpstr = es_bsprintf ("%s:%hu", httphost ? httphost : server, port); + log_error (_("error accessing '%s': http status %u\n"), + tmpstr ? tmpstr : "out of core", + http_get_status_code (hd)); + xfree (tmpstr); + err = gpg_error (GPG_ERR_NO_DATA); + goto leave; + } + + leave: + xfree (request); + xfree (authhdr); + return err; +} +#endif /*USE_TLS*/ + + +/* Make a request string using a standard proxy. On success the + * request is stored at R_REQUEST (and will never be NULL). */ +static gpg_error_t +mk_proxy_request (http_t hd, proxy_info_t proxy, + const char *httphost, const char *server, + unsigned short port, const char *relpath, + const char *authstr, + char **r_request) +{ + gpg_error_t err = 0; + char *authhdr = NULL; + char *request = NULL; + + *r_request = NULL; + + if (proxy->uri->auth + && !(authhdr = make_header_line ("Proxy-Authorization: Basic ", + "\r\n", + proxy->uri->auth, + strlen (proxy->uri->auth)))) + { + err = gpg_error_from_syserror (); + goto leave; + } + + request = es_bsprintf ("%s %s://%s:%hu%s%s HTTP/1.0\r\n%s%s", + hd->req_type == HTTP_REQ_GET ? "GET" : + hd->req_type == HTTP_REQ_HEAD ? "HEAD" : + hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", + hd->uri->use_tls? "https" : "http", + httphost? httphost : server, + port, *relpath == '/' ? "" : "/", relpath, + authstr ? authstr : "", + authhdr ? authhdr : ""); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } + *r_request = request; + request = NULL; + + leave: + xfree (request); + xfree (authhdr); + return err; +} + + +/* Make a request string using. On success the request is stored at + * R_REQUEST (and will never be NULL). */ +static gpg_error_t +mk_std_request (http_t hd, + const char *httphost, const char *server, + unsigned short port, const char *relpath, + const char *authstr, + char **r_request) +{ + gpg_error_t err = 0; + char portstr[35]; + char *request = NULL; + + *r_request = NULL; + + if (port == (hd->uri->use_tls? 443 : 80)) + *portstr = 0; + else + snprintf (portstr, sizeof portstr, ":%u", port); + + request = es_bsprintf ("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s", + hd->req_type == HTTP_REQ_GET ? "GET" : + hd->req_type == HTTP_REQ_HEAD ? "HEAD" : + hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", + *relpath == '/' ? "" : "/", relpath, + httphost? httphost : server, + portstr, + authstr? authstr:""); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } + *r_request = request; + request = NULL; + + leave: + xfree (request); + return err; +} + + /* * Send a HTTP request to the server * Returns 0 if the request was successful @@ -2376,79 +2552,10 @@ send_request (ctrl_t ctrl, #if USE_TLS if (use_http_proxy && hd->uri->use_tls) { - int saved_flags; - - log_assert (!proxy_authstr); - if (proxy->uri->auth - && !(proxy_authstr = make_header_line ("Proxy-Authorization: Basic ", - "\r\n", - proxy->uri->auth, - strlen (proxy->uri->auth)))) - { - err = gpg_error_from_syserror (); - goto leave; - } - - /* Try to use the CONNECT method to proxy our TLS stream. */ - xfree (request); - request = es_bsprintf - ("CONNECT %s:%hu HTTP/1.0\r\nHost: %s:%hu\r\n%s", - httphost ? httphost : server, - port, - httphost ? httphost : server, - port, - proxy_authstr ? proxy_authstr : ""); - if (!request) - { - err = gpg_error_from_syserror (); - goto leave; - } - - if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) - log_debug_string (request, "http.c:request:"); - - err = make_fp_write (hd, 0, NULL); + err = run_proxy_connect (hd, proxy, httphost, server, port); if (err) goto leave; - if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) - { - err = gpg_error_from_syserror (); - goto leave; - } - - /* Make sure http_wait_response doesn't close the stream. */ - saved_flags = hd->flags; - hd->flags &= ~HTTP_FLAG_SHUTDOWN; - - /* Get the response and set hd->fp_read */ - err = http_wait_response (hd); - - /* Restore flags, destroy stream. */ - hd->flags = saved_flags; - es_fclose (hd->fp_read); - hd->fp_read = NULL; - hd->read_cookie = NULL; - - /* Reset state. */ - hd->in_data = 0; - - if (err) - goto leave; - - if (hd->status_code != 200) - { - char *tmpstr; - - tmpstr = es_bsprintf ("%s:%hu", httphost ? httphost : server, port); - log_error (_("error accessing '%s': http status %u\n"), - tmpstr ? tmpstr : "out of core", - http_get_status_code (hd)); - xfree (tmpstr); - err = gpg_error (GPG_ERR_NO_DATA); - goto leave; - } - /* We are done with the proxy, the code below will establish a * TLS session and talk directly to the target server. Thus we * clear the flag to indicate this. */ @@ -2506,61 +2613,13 @@ send_request (ctrl_t ctrl, } if (use_http_proxy) - { - xfree (proxy_authstr); - proxy_authstr = NULL; - if (proxy->uri->auth - && !(proxy_authstr = make_header_line ("Proxy-Authorization: Basic ", - "\r\n", - proxy->uri->auth, - strlen (proxy->uri->auth)))) - { - err = gpg_error_from_syserror (); - goto leave; - } - - xfree (request); - request = es_bsprintf - ("%s %s://%s:%hu%s%s HTTP/1.0\r\n%s%s", - hd->req_type == HTTP_REQ_GET ? "GET" : - hd->req_type == HTTP_REQ_HEAD ? "HEAD" : - hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", - hd->uri->use_tls? "https" : "http", - httphost? httphost : server, - port, *relpath == '/' ? "" : "/", relpath, - authstr ? authstr : "", - proxy_authstr ? proxy_authstr : ""); - if (!request) - { - err = gpg_error_from_syserror (); - goto leave; - } - } + err = mk_proxy_request (hd, proxy, httphost, server, port, + relpath, authstr, &request); else - { - char portstr[35]; - - if (port == (hd->uri->use_tls? 443 : 80)) - *portstr = 0; - else - snprintf (portstr, sizeof portstr, ":%u", port); - - xfree (request); - request = es_bsprintf - ("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s", - hd->req_type == HTTP_REQ_GET ? "GET" : - hd->req_type == HTTP_REQ_HEAD ? "HEAD" : - hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", - *relpath == '/' ? "" : "/", relpath, - httphost? httphost : server, - portstr, - authstr? authstr:""); - if (!request) - { - err = gpg_error_from_syserror (); - goto leave; - } - } + err = mk_std_request (hd, httphost, server, port, + relpath, authstr, &request); + if (err) + goto leave; if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) log_debug_string (request, "http.c:request:"); @@ -3731,19 +3790,15 @@ http_verify_server_credentials (http_session_t sess) } else if (status) { - log_error ("%s: status=0x%04x\n", errprefix, status); -#if GNUTLS_VERSION_NUMBER >= 0x030104 - { - gnutls_datum_t statusdat; + gnutls_datum_t statusdat; - if (!gnutls_certificate_verification_status_print - (status, GNUTLS_CRT_X509, &statusdat, 0)) - { - log_info ("%s: %s\n", errprefix, statusdat.data); - gnutls_free (statusdat.data); - } - } -#endif /*gnutls >= 3.1.4*/ + log_error ("%s: status=0x%04x\n", errprefix, status); + if (!gnutls_certificate_verification_status_print + (status, GNUTLS_CRT_X509, &statusdat, 0)) + { + log_info ("%s: %s\n", errprefix, statusdat.data); + gnutls_free (statusdat.data); + } sess->verify.status = status; if (!err) From c91f759bafcae2a19808b642316d1e2447b6073d Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 26 Sep 2023 09:35:25 +0200 Subject: [PATCH 24/55] common: Add gnupg_memstr to replace static versions. * common/stringhelp.c (gnupg_memstr): New. * common/mbox-util.c (my_memstr): Remove. (is_valid_mailbox_mem): Use gnupg_memstr. * common/recsel.c (my_memstr): Remove. (recsel_select): Use gnupg_memstr. --- common/mbox-util.c | 31 +------------------------------ common/recsel.c | 33 +-------------------------------- common/stringhelp.c | 29 +++++++++++++++++++++++++++++ common/stringhelp.h | 1 + 4 files changed, 32 insertions(+), 62 deletions(-) diff --git a/common/mbox-util.c b/common/mbox-util.c index a9086a3f5..fb6d06780 100644 --- a/common/mbox-util.c +++ b/common/mbox-util.c @@ -57,35 +57,6 @@ mem_count_chr (const void *buffer, int c, size_t length) } -/* This is a case-sensitive version of our memistr. I wonder why no - standard function memstr exists but I better do not use the name - memstr to avoid future conflicts. */ -static const char * -my_memstr (const void *buffer, size_t buflen, const char *sub) -{ - const unsigned char *buf = buffer; - const unsigned char *t = (const unsigned char *)buf; - const unsigned char *s = (const unsigned char *)sub; - size_t n = buflen; - - for ( ; n ; t++, n-- ) - { - if (*t == *s) - { - for (buf = t++, buflen = n--, s++; n && *t ==*s; t++, s++, n--) - ; - if (!*s) - return (const char*)buf; - t = (const unsigned char *)buf; - s = (const unsigned char *)sub ; - n = buflen; - } - } - return NULL; -} - - - static int string_has_ctrl_or_space (const char *string) { @@ -159,7 +130,7 @@ is_valid_mailbox_mem (const void *name_arg, size_t namelen) || *name == '@' || name[namelen-1] == '@' || name[namelen-1] == '.' - || my_memstr (name, namelen, "..")); + || gnupg_memstr (name, namelen, "..")); } diff --git a/common/recsel.c b/common/recsel.c index ea0858c84..fa3debaaf 100644 --- a/common/recsel.c +++ b/common/recsel.c @@ -85,37 +85,6 @@ my_error (gpg_err_code_t ec) } -/* This is a case-sensitive version of our memistr. I wonder why no - * standard function memstr exists but I better do not use the name - * memstr to avoid future conflicts. - * - * FIXME: Move this to a stringhelp.c - */ -static const char * -my_memstr (const void *buffer, size_t buflen, const char *sub) -{ - const unsigned char *buf = buffer; - const unsigned char *t = (const unsigned char *)buf; - const unsigned char *s = (const unsigned char *)sub; - size_t n = buflen; - - for ( ; n ; t++, n-- ) - { - if (*t == *s) - { - for (buf = t++, buflen = n--, s++; n && *t ==*s; t++, s++, n--) - ; - if (!*s) - return (const char*)buf; - t = (const unsigned char *)buf; - s = (const unsigned char *)sub ; - n = buflen; - } - } - return NULL; -} - - /* Return a pointer to the next logical connection operator or NULL if * none. */ static char * @@ -560,7 +529,7 @@ recsel_select (recsel_expr_t selector, break; case SELECT_SUB: if (se->xcase) - result = !!my_memstr (value, valuelen, se->value); + result = !!gnupg_memstr (value, valuelen, se->value); else result = !!memistr (value, valuelen, se->value); break; diff --git a/common/stringhelp.c b/common/stringhelp.c index 1049c78e2..5407653de 100644 --- a/common/stringhelp.c +++ b/common/stringhelp.c @@ -161,6 +161,35 @@ ascii_memistr ( const void *buffer, size_t buflen, const char *sub ) } +/* This is a case-sensitive version of our memistr. I wonder why no + * standard function memstr exists but we better do not use the name + * memstr to avoid future conflicts. + */ +const char * +gnupg_memstr (const void *buffer, size_t buflen, const char *sub) +{ + const unsigned char *buf = buffer; + const unsigned char *t = (const unsigned char *)buf; + const unsigned char *s = (const unsigned char *)sub; + size_t n = buflen; + + for ( ; n ; t++, n-- ) + { + if (*t == *s) + { + for (buf = t++, buflen = n--, s++; n && *t ==*s; t++, s++, n--) + ; + if (!*s) + return (const char*)buf; + t = (const unsigned char *)buf; + s = (const unsigned char *)sub ; + n = buflen; + } + } + return NULL; +} + + /* This function is similar to strncpy(). However it won't copy more * than N - 1 characters and makes sure that a '\0' is appended. With * N given as 0, nothing will happen. With DEST given as NULL, memory diff --git a/common/stringhelp.h b/common/stringhelp.h index cd185e49a..d93373ec5 100644 --- a/common/stringhelp.h +++ b/common/stringhelp.h @@ -40,6 +40,7 @@ char *has_leading_keyword (const char *string, const char *keyword); const char *memistr (const void *buf, size_t buflen, const char *sub); +const char *gnupg_memstr (const void *buffer, size_t buflen, const char *sub); char *mem2str( char *, const void *, size_t); char *trim_spaces( char *string ); char *ascii_trim_spaces (char *string); From a5e33618f4211557e60c1b2d013ea8c8d1923e46 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 26 Sep 2023 12:33:09 +0200 Subject: [PATCH 25/55] dirmngr: Fix handling of the HTTP Content-Length * dirmngr/http.c (cookie_s): Add fields pending, up_to_empty_line, last_was_lf, and last_was_lfcr. (http_context_s): Add field keep-alive. (http_wait_response): Set up_to_empty_line. Take care of keep_alive flag. (coookie_read): Implement detection of empty lines. (cookie_write): Free the pending buffer. -- The problem we fix here is that we already buffered stuff beyond the empty line which marks the start of the content-length counting. Thus we tried to wait for more bytes despite that everything had already been read. This bug might have showed up more often in the real world since the we changed the BUFSIZ on Windows from 512 byte to 8k. It also depends on the length of the headers and whether the server closed the connection so that we ignored the Content-Length. The bug was introduced earlier than 2010 and could have the effect that a connection got stuck until the network layer timed out. Note that the keep-alive parts of the patch are not yet used. --- dirmngr/http.c | 156 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 140 insertions(+), 16 deletions(-) diff --git a/dirmngr/http.c b/dirmngr/http.c index 33eb69804..a44b0d244 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -2,7 +2,7 @@ * Copyright (C) 1999, 2001-2004, 2006, 2009, 2010, * 2011 Free Software Foundation, Inc. * Copyright (C) 1999, 2001-2004, 2006, 2009, 2010, 2011, 2014 Werner Koch - * Copyright (C) 2015-2017, 2021 g10 Code GmbH + * Copyright (C) 2015-2017, 2021, 2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -211,10 +211,29 @@ struct cookie_s /* True if TLS is to be used. */ int use_tls; + /* Optional malloced buffer holding pending bytes for the read + * function. LEN gives the used length, SIZE the allocated length. + * Used by the up_to_empty_line machinery. */ + struct { + size_t size; + size_t len; + char *data; + } pending; + /* The remaining content length and a flag telling whether to use the content length. */ uint64_t content_length; unsigned int content_length_valid:1; + + /* If the next flag is set the read function will limit the returned + * buffer to an empty line. That is the the pattern "\n\r\n" is + * detected and any further bytes are not returned to the caller. + * The flag is then reset. For technical reason we might have + * already read more which will be then saved for the next call in + * the PENDING buffer. */ + unsigned int up_to_empty_line:1; + unsigned int last_was_lf:1; /* Helper to detect empty line. */ + unsigned int last_was_lfcr:1; /* Helper to detect empty line. */ }; typedef struct cookie_s *cookie_t; @@ -306,6 +325,7 @@ struct http_context_s my_socket_t sock; unsigned int in_data:1; unsigned int is_http_0_9:1; + unsigned int keep_alive:1; /* Keep the connection alive. */ estream_t fp_read; estream_t fp_write; void *write_cookie; @@ -1180,7 +1200,7 @@ http_start_data (http_t hd) if (!hd->in_data) { if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) - log_debug_string ("\r\n", "http.c:request-header:"); + log_debug ("http.c:request-header:start_data:\n"); es_fputs ("\r\n", hd->fp_write); es_fflush (hd->fp_write); hd->in_data = 1; @@ -1196,6 +1216,7 @@ http_wait_response (http_t hd) gpg_error_t err; cookie_t cookie; int use_tls; + int newfpread; /* Make sure that we are in the data. */ http_start_data (hd); @@ -1207,26 +1228,36 @@ http_wait_response (http_t hd) return gpg_err_make (default_errsource, GPG_ERR_INTERNAL); use_tls = cookie->use_tls; - es_fclose (hd->fp_write); - hd->fp_write = NULL; - /* The close has released the cookie and thus we better set it to NULL. */ - hd->write_cookie = NULL; + if (!hd->keep_alive) + { + es_fclose (hd->fp_write); + hd->fp_write = NULL; + /* The close has released the cookie and thus we better set it + * to NULL. */ + hd->write_cookie = NULL; + } /* Shutdown one end of the socket is desired. As per HTTP/1.0 this is not required but some very old servers (e.g. the original pksd keyserver didn't worked without it. */ - if ((hd->flags & HTTP_FLAG_SHUTDOWN)) + if (!hd->keep_alive && (hd->flags & HTTP_FLAG_SHUTDOWN)) shutdown (FD2INT (hd->sock->fd), 1); hd->in_data = 0; /* Create a new cookie and a stream for reading. */ - err = make_fp_read (hd, use_tls, hd->session); - if (err) - return err; + newfpread = 0; + if (!hd->keep_alive || !hd->fp_read) + { + err = make_fp_read (hd, use_tls, hd->session); + if (err) + return err; + newfpread = 1; + ((cookie_t)(hd->read_cookie))->up_to_empty_line = 1; + } err = parse_response (hd); - if (!err) + if (!err && newfpread) err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd); return err; @@ -3532,31 +3563,48 @@ cookie_read (void *cookie, void *buffer, size_t size) { cookie_t c = cookie; int nread; + size_t offset = 0; if (c->content_length_valid) { if (!c->content_length) - return 0; /* EOF */ + { + c->content_length_valid = 0; + return 0; /* EOF */ + } if (c->content_length < size) size = c->content_length; } + if (c->pending.len) + { + offset = c->pending.len > size? size : c->pending.len; + memcpy (buffer, c->pending.data, offset); + c->pending.len -= offset; + } + + if (offset >= size) + nread = offset; + else #if HTTP_USE_NTBTLS if (c->use_tls && c->session && c->session->tls_session) { estream_t in, out; ntbtls_get_stream (c->session->tls_session, &in, &out); - nread = es_fread (buffer, 1, size, in); + nread = es_fread ((char*)buffer+offset, 1, size-offset, in); if (opt_debug) - log_debug ("TLS network read: %d/%zu\n", nread, size); + log_debug ("TLS network read: %d/%zu\n", nread, size-offset); + if (nread >= 0) + nread += offset; } else #elif HTTP_USE_GNUTLS if (c->use_tls && c->session && c->session->tls_session) { again: - nread = gnutls_record_recv (c->session->tls_session, buffer, size); + nread = gnutls_record_recv (c->session->tls_session, + (char*)buffer+offset, size-offset); if (nread < 0) { if (nread == GNUTLS_E_INTERRUPTED) @@ -3583,11 +3631,86 @@ cookie_read (void *cookie, void *buffer, size_t size) gpg_err_set_errno (EIO); return -1; } + if (nread >= 0) + nread += offset; } else #endif /*HTTP_USE_GNUTLS*/ { - nread = read_server (c->sock->fd, buffer, size); + nread = read_server (c->sock->fd, (char*)buffer+offset, size-offset); + if (opt_debug) + log_debug ("network read: %d/%zu\n", nread, size); + if (nread >= 0) + nread += offset; + } + + if (nread > 0 && c->up_to_empty_line) + { + gpg_error_t err; + const char *s; + size_t n; + int extra; + int lfcr_pending = 0; + char *bufp = buffer; + + if (c->last_was_lf && nread > 1 && bufp[0] == '\r' && bufp[1] == '\n') + { + s = buffer; + extra = 2; + } + else if (c->last_was_lf && bufp[0] == '\r') + { + lfcr_pending = 1; + s = buffer; /* Only to avoid the call to gnupg_memstr. */ + } + else if (c->last_was_lfcr && bufp[0] == '\n') + { + s = buffer; + extra = 1; + } + else + s = NULL; + + c->last_was_lfcr = c->last_was_lf = 0; + + if (!s) + { + s = gnupg_memstr (buffer, nread, "\n\r\n"); + extra = 3; + } + + if (lfcr_pending) + c->last_was_lfcr = 1; + else if (s) + { + /* Save away the rest and return up to the LF. */ + log_assert (!c->pending.len); + n = (s+extra) - bufp; + log_assert (n <= nread); + c->pending.len = nread - n; + if (!c->pending.data || c->pending.len >= c->pending.size) + { + xfree (c->pending.data); + c->pending.size = c->pending.len + 256; /* Some extra space. */ + c->pending.data = xtrymalloc (c->pending.size); + if (!c->pending.data) + { + err = gpg_error_from_syserror (); + log_error ("error allocating network read buffer: %s\n", + gpg_strerror (err)); + return -1; + } + memcpy (c->pending.data, bufp + n, c->pending.len); + } + else + memcpy (c->pending.data, bufp + n, c->pending.len); + nread = n; /* Return everything up to the empty line. */ + c->up_to_empty_line = 0; + } + else if (bufp[nread-1] == '\n') + c->last_was_lf = 1; + else if (nread > 1 && bufp[nread-2] == '\n' && bufp[nread-1] == '\r') + c->last_was_lfcr = 1; } if (c->content_length_valid && nread > 0) @@ -3748,6 +3871,7 @@ cookie_close (void *cookie) if (c->session) http_session_unref (c->session); + xfree (c->pending.data); xfree (c); return 0; } From 52b7a60cf9f3cd2e5900396b0e3e65cbd335bc23 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 29 Sep 2023 11:34:06 +0200 Subject: [PATCH 26/55] common: Add new function b64decode. * common/b64dec.c (b64decode): New. * common/t-b64.c: Change license to LGPL. (oops): New macro. (hex2buffer): New. (test_b64decode): New. (main): Default to run the new test. * common/Makefile.am (module_maint_tests): Move t-b64 to ... (module_tests): here. -- Sometimes we have a short base64 encoded string we need todecode. This function makes it simpler. License change of the test module justified because I am the single author of the code. --- common/Makefile.am | 4 +- common/b64dec.c | 45 +++++++++++++++ common/b64enc.c | 1 + common/t-b64.c | 134 +++++++++++++++++++++++++++++++++++++++------ common/util.h | 2 + 5 files changed, 168 insertions(+), 18 deletions(-) diff --git a/common/Makefile.am b/common/Makefile.am index d5ab038bf..7c78708da 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -161,7 +161,7 @@ module_tests = t-stringhelp t-timestuff \ t-convert t-percent t-gettime t-sysutils t-sexputil \ t-session-env t-openpgp-oid t-ssh-utils \ t-mapstrings t-zb32 t-mbox-util t-iobuf t-strlist \ - t-name-value t-ccparray t-recsel t-w32-cmdline + t-name-value t-ccparray t-recsel t-w32-cmdline t-b64 if HAVE_W32_SYSTEM module_tests += t-w32-reg else @@ -169,7 +169,7 @@ module_tests += t-exechelp t-exectool endif if MAINTAINER_MODE -module_maint_tests = t-helpfile t-b64 +module_maint_tests = t-helpfile else module_maint_tests = endif diff --git a/common/b64dec.c b/common/b64dec.c index 6af494b79..2904b0471 100644 --- a/common/b64dec.c +++ b/common/b64dec.c @@ -16,6 +16,7 @@ * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . + * SPDX-License-Identifier: LGPL-2.1-or-later */ #include @@ -252,3 +253,47 @@ b64dec_finish (struct b64state *state) return state->invalid_encoding? gpg_error(GPG_ERR_BAD_DATA): 0; } + + +/* Convert STRING consisting of base64 characters into its binary + * representation and store the result in a newly allocated buffer at + * R_BUFFER with its length at R_BUFLEN. If TITLE is NULL a plain + * base64 decoding is done. If it is the empty string the decoder + * will skip everything until a "-----BEGIN " line has been seen, + * decoding then ends at a "----END " line. On failure the function + * returns an error code and sets R_BUFFER to NULL. If the decoded + * data has a length of 0 a dummy buffer will still be allocated and + * the length is set to 0. */ +gpg_error_t +b64decode (const char *string, const char *title, + void **r_buffer, size_t *r_buflen) +{ + gpg_error_t err; + struct b64state state; + size_t nbytes; + char *buffer; + + *r_buffer = NULL; + *r_buflen = 0; + + buffer = xtrystrdup (string); + if (!buffer) + return gpg_error_from_syserror(); + + err = b64dec_start (&state, title); + if (err) + { + xfree (buffer); + return err; + } + b64dec_proc (&state, buffer, strlen (buffer), &nbytes); + err = b64dec_finish (&state); + if (err) + xfree (buffer); + else + { + *r_buffer = buffer; + *r_buflen = nbytes; + } + return err; +} diff --git a/common/b64enc.c b/common/b64enc.c index d633048ea..7846dcb3e 100644 --- a/common/b64enc.c +++ b/common/b64enc.c @@ -18,6 +18,7 @@ * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . + * SPDX-License-Identifier: LGPL-2.1-or-later */ #include diff --git a/common/t-b64.c b/common/t-b64.c index 3b6387246..16c079d1d 100644 --- a/common/t-b64.c +++ b/common/t-b64.c @@ -1,30 +1,24 @@ /* t-b64.c - Module tests for b64enc.c and b64dec.c - * Copyright (C) 2008 Free Software Foundation, Inc. + * Copyright (C) 2008 Free Software Foundation, Inc. + * Copyright (C) 2008, 2023 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. + * 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. * - * GnuPG is distributed in the hope that it will be useful, + * 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 + * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . + * SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - - As of now this is only a test program for manual tests. - - */ - - - #include #include #include @@ -36,10 +30,112 @@ __FILE__,__LINE__, (a)); \ errcount++; \ } while(0) +#define oops() do { fprintf (stderr, "%s:%d: ooops\n", \ + __FILE__,__LINE__); \ + exit (2); \ + } while(0) static int verbose; static int errcount; + +/* Convert STRING consisting of hex characters into its binary + * representation and return it as an allocated buffer. The valid + * length of the buffer is returned at R_LENGTH. The string is + * delimited by end of string. The function returns NULL on + * error. */ +static void * +hex2buffer (const char *string, size_t *r_length) +{ + const char *s; + unsigned char *buffer; + size_t length; + + buffer = xmalloc (strlen(string)/2+1); + length = 0; + for (s=string; *s; s +=2 ) + { + if (!hexdigitp (s) || !hexdigitp (s+1)) + return NULL; /* Invalid hex digits. */ + ((unsigned char*)buffer)[length++] = xtoi_2 (s); + } + *r_length = length; + return buffer; +} + + +static void +test_b64decode (void) +{ + static struct { + const char *string; /* String to test. */ + const char *title; /* title parameter. */ + gpg_error_t err; /* expected error. */ + const char *datastr; /* Expected data (hex encoded) */ + } tests[] = { + { "YQ==", NULL, 0, + "61" }, + { "YWE==", NULL, 0, + "6161" }, + { "YWFh", NULL, 0, + "616161" }, + { "YWFhYQ==", NULL, 0, + "61616161" }, + { "YWJjZA==", NULL, 0, + "61626364" }, + { "AA=", NULL, 0, + "00" }, + { "AAEA=", NULL, 0, + "000100" }, + { "/w==", NULL, 0, + "ff" }, + { "oRQwEqADCgEDoQsGCSqGSIL3EgECAg==", NULL, 0, + "a1143012a0030a0103a10b06092a864882f712010202" }, + { "oRQwEqADCgEDoQsGCSqGSIL3EgECA-==", NULL, GPG_ERR_BAD_DATA, + "a1143012a0030a0103a10b06092a864882f712010202" }, + { "oRQwEqADCgEDoQsGCSqGSIL3EgECAg==", "", 0, + "" }, + { "-----BEGIN PGP\n\n" + "oRQwEqADCgEDoQsGCSqGSIL3EgECAg==\n" + "-----END PGP\n", "", 0, + "a1143012a0030a0103a10b06092a864882f712010202" }, + + { "", NULL, 0, + "" } + }; + int tidx; + gpg_error_t err; + void *data = NULL; + size_t datalen; + char *wantdata = NULL; + size_t wantdatalen; + + for (tidx = 0; tidx < DIM(tests); tidx++) + { + xfree (wantdata); + if (!(wantdata = hex2buffer (tests[tidx].datastr, &wantdatalen))) + oops (); + xfree (data); + err = b64decode (tests[tidx].string, tests[tidx].title, &data, &datalen); + if (verbose) + fprintf (stderr, "%s:%d: test %d, err=%d, datalen=%zu\n", + __FILE__, __LINE__, tidx, err, datalen); + if (gpg_err_code (err) != tests[tidx].err) + fail (tidx); + else if (err) + pass (); + else if (wantdatalen != datalen) + fail (tidx); + else if (memcmp (wantdata, data, datalen)) + fail (tidx); + else + pass (); + } + xfree (wantdata); + xfree (data); +} + + static void test_b64enc_pgp (const char *string) { @@ -101,6 +197,7 @@ test_b64enc_file (const char *fname) pass (); } + static void test_b64dec_file (const char *fname) { @@ -150,6 +247,7 @@ main (int argc, char **argv) { int do_encode = 0; int do_decode = 0; + int do_pgpdecode = 0; if (argc) { argc--; argv++; } @@ -169,13 +267,17 @@ main (int argc, char **argv) do_decode = 1; argc--; argv++; } + else if (argc) + do_pgpdecode = 1; if (do_encode) test_b64enc_file (argc? *argv: NULL); else if (do_decode) test_b64dec_file (argc? *argv: NULL); - else + else if (do_pgpdecode) test_b64enc_pgp (argc? *argv: NULL); + else + test_b64decode (); return !!errcount; } diff --git a/common/util.h b/common/util.h index aa24e39e6..83882caf2 100644 --- a/common/util.h +++ b/common/util.h @@ -171,6 +171,8 @@ gpg_error_t b64dec_start (struct b64state *state, const char *title); gpg_error_t b64dec_proc (struct b64state *state, void *buffer, size_t length, size_t *r_nbytes); gpg_error_t b64dec_finish (struct b64state *state); +gpg_error_t b64decode (const char *string, const char *title, + void **r_buffer, size_t *r_buflen); /*-- sexputil.c */ char *canon_sexp_to_string (const unsigned char *canon, size_t canonlen); From 53bdb7440cbe18f73548169528167190d70998ed Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 2 Oct 2023 12:53:41 +0200 Subject: [PATCH 27/55] dirmngr: Extended the http_get_header function. * dirmngr/http.c (send_request): Add arg 'skip'. Adjust all callers. -- GnuPG-bug-id: 6719 --- dirmngr/http.c | 25 +++++++++++++++++-------- dirmngr/http.h | 2 +- dirmngr/ks-engine-hkp.c | 2 +- dirmngr/ks-engine-http.c | 2 +- dirmngr/ocsp.c | 2 +- dirmngr/t-http.c | 4 ++-- 6 files changed, 23 insertions(+), 14 deletions(-) diff --git a/dirmngr/http.c b/dirmngr/http.c index a44b0d244..32e6a6cb8 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -2845,18 +2845,27 @@ store_header (http_t hd, char *line) /* Return the header NAME from the last response. The returned value - is valid as along as HD has not been closed and no other request - has been send. If the header was not found, NULL is returned. NAME - must be canonicalized, that is the first letter of each dash - delimited part must be uppercase and all other letters lowercase. */ + * is valid as along as HD has not been closed and no other request + * has been send. If the header was not found, NULL is returned. NAME + * must be canonicalized, that is the first letter of each dash + * delimited part must be uppercase and all other letters lowercase. + * SKIP gives the number of entries of the requested NAME to skip + * before returning; this can be used to enumerate headers with the + * same name (see store_header). +*/ const char * -http_get_header (http_t hd, const char *name) +http_get_header (http_t hd, const char *name, unsigned int skip) { header_t h; for (h=hd->headers; h; h = h->next) - if ( !strcmp (h->name, name) ) - return h->value; + if (!strcmp (h->name, name)) + { + if (skip) + skip--; + else + return h->value; + } return NULL; } @@ -2979,7 +2988,7 @@ parse_response (http_t hd) cookie->content_length_valid = 0; if (!(hd->flags & HTTP_FLAG_IGNORE_CL)) { - s = http_get_header (hd, "Content-Length"); + s = http_get_header (hd, "Content-Length", 0); if (s) { cookie->content_length_valid = 1; diff --git a/dirmngr/http.h b/dirmngr/http.h index 2994fdfad..28406694e 100644 --- a/dirmngr/http.h +++ b/dirmngr/http.h @@ -195,7 +195,7 @@ estream_t http_get_read_ptr (http_t hd); estream_t http_get_write_ptr (http_t hd); unsigned int http_get_status_code (http_t hd); const char *http_get_tls_info (http_t hd, const char *what); -const char *http_get_header (http_t hd, const char *name); +const char *http_get_header (http_t hd, const char *name, unsigned int skip); const char **http_get_header_names (http_t hd); gpg_error_t http_verify_server_credentials (http_session_t sess); diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index a75cc1aee..75fe19987 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -1327,7 +1327,7 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr, { xfree (request_buffer); err = http_prepare_redirect (&redirinfo, http_get_status_code (http), - http_get_header (http, "Location"), + http_get_header (http, "Location", 0), &request_buffer); if (err) goto leave; diff --git a/dirmngr/ks-engine-http.c b/dirmngr/ks-engine-http.c index 48a734786..5091ddf27 100644 --- a/dirmngr/ks-engine-http.c +++ b/dirmngr/ks-engine-http.c @@ -180,7 +180,7 @@ ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags, { xfree (request_buffer); err = http_prepare_redirect (&redirinfo, http_get_status_code (http), - http_get_header (http, "Location"), + http_get_header (http, "Location", 0), &request_buffer); if (err) goto leave; diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c index 483b6f32d..ad7ed962a 100644 --- a/dirmngr/ocsp.c +++ b/dirmngr/ocsp.c @@ -227,7 +227,7 @@ do_ocsp_request (ctrl_t ctrl, ksba_ocsp_t ocsp, case 301: case 302: { - const char *s = http_get_header (http, "Location"); + const char *s = http_get_header (http, "Location", 0); log_info (_("URL '%s' redirected to '%s' (%u)\n"), url, s?s:"[none]", http_get_status_code (http)); diff --git a/dirmngr/t-http.c b/dirmngr/t-http.c index f9c59783f..3cc4be23a 100644 --- a/dirmngr/t-http.c +++ b/dirmngr/t-http.c @@ -463,7 +463,7 @@ main (int argc, char **argv) log_fatal ("http_get_header_names failed: %s\n", gpg_strerror (gpg_error_from_syserror ())); for (i = 0; names[i]; i++) - printf ("HDR: %s: %s\n", names[i], http_get_header (hd, names[i])); + printf ("HDR: %s: %s\n", names[i], http_get_header (hd, names[i], 0)); xfree (names); } fflush (stdout); @@ -489,7 +489,7 @@ main (int argc, char **argv) case 301: case 302: case 307: - log_info ("Redirected to: %s\n", http_get_header (hd, "Location")); + log_info ("Redirected to: %s\n", http_get_header (hd, "Location", 0)); break; } http_close (hd, 0); From d7a1577a252466c89a87a547bc7f3e9a3d3a2a76 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 2 Oct 2023 13:00:35 +0200 Subject: [PATCH 28/55] dirmngr: Add code to support the negotiation auth method. * dirmngr/http.c (enum auth_negotiate_states): New. (struct proxy_info_s): Add new fields. (release_proxy_info): Free Windows stuff. (proxy_get_token): New. Implemented only for Windows for now. (run_proxy_connect): Add support for auth method Negotiation. (store_header): Keep some header lines separate. -- The code does something but I have not yet been able to test it due to problems setting up Squid with AD authentication. As of now it will respond with a failure but that should not be worse than not to implement Negotiation. Supporting Negotiation using GSS for Unix should eventually also be done. GnuPG-bug-id: 6719 --- dirmngr/Makefile.am | 2 +- dirmngr/http.c | 410 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 371 insertions(+), 41 deletions(-) diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am index 9665b5dfd..a0f8d5a79 100644 --- a/dirmngr/Makefile.am +++ b/dirmngr/Makefile.am @@ -68,7 +68,7 @@ AM_CFLAGS = $(USE_C99_CFLAGS) \ if HAVE_W32_SYSTEM ldap_url = ldap-url.h ldap-url.c -NETLIBS += -lwinhttp +NETLIBS += -lwinhttp -lsecurity else ldap_url = endif diff --git a/dirmngr/http.c b/dirmngr/http.c index 32e6a6cb8..4899a5d55 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -65,12 +65,8 @@ # endif # include # include -# ifndef EHOSTUNREACH -# define EHOSTUNREACH WSAEHOSTUNREACH -# endif -# ifndef EAFNOSUPPORT -# define EAFNOSUPPORT WSAEAFNOSUPPORT -# endif +# define SECURITY_WIN32 1 +# include #else /*!HAVE_W32_SYSTEM*/ # include # include @@ -250,11 +246,30 @@ static es_cookie_io_functions_t simple_cookie_functions = }; #endif +enum auth_negotiate_states + { + AUTH_NGT_NONE = 0, + AUTH_NGT_RCVD = 1, + AUTH_NGT_SENT = 2 + }; + /* An object to store information about a proxy. */ struct proxy_info_s { parsed_uri_t uri; /* The parsed proxy URL. */ int is_http_proxy; /* This is an http proxy. */ + +#ifdef HAVE_W32_SYSTEM + CredHandle cred_handle; /* Credential handle. */ + wchar_t *spn; /* Service principal name. */ + CtxtHandle ctxt_handle; /* Security context. */ + unsigned long token_size; /* Max. length of a token. */ + unsigned int cred_handle_valid:1; + unsigned int ctxt_handle_valid:1; +#endif /*HAVE_W32_SYSTEM*/ + + unsigned char *outtoken; /* The output token allocated with token_size. */ + unsigned long outtoklen; /* The current length of the token. */ }; typedef struct proxy_info_s *proxy_info_t; @@ -1853,6 +1868,13 @@ release_proxy_info (proxy_info_t proxy) if (!proxy) return; http_release_parsed_uri (proxy->uri); + xfree (proxy->outtoken); +#ifdef HAVE_W32_SYSTEM + if (proxy->ctxt_handle_valid) + DeleteSecurityContext (&proxy->ctxt_handle); + if (proxy->cred_handle_valid) + FreeCredentialsHandle (&proxy->cred_handle); +#endif xfree (proxy); } @@ -2336,6 +2358,181 @@ run_gnutls_handshake (http_t hd, const char *server) #endif /*HTTP_USE_GNUTLS*/ +/* It INPUTSTRING is NULL get the intial token. If INPUTSTRING is not + * NULL, decode the string and use this as input from teh server. On + * success the final output token is stored at PROXY->OUTTOKEN and + * OUTTOKLEN. IF the authentication succeeded OUTTOKLEN is zero. */ +#ifdef USE_TLS +static gpg_error_t +proxy_get_token (proxy_info_t proxy, const char *inputstring) +{ +#ifdef HAVE_W32_SYSTEM + gpg_error_t err; + int rc; + SecBuffer chlg_buf; /* challenge buffer */ + SecBufferDesc chlg_desc; /* challenge descriptor */ + SecBuffer resp_buf; /* response buffer */ + SecBufferDesc resp_desc; /* response descriptor */ + unsigned long attrs; + TimeStamp expiry; /* (value not used) */ + void *intoken = NULL; + size_t intoklen; + + if (inputstring) + { + /* The input is expected in the token parameter but the paremter + * name is often forgotten. Thus we simply detect the parameter + * name and skip it, assuming no other parameters are given. */ + if (!strncmp (inputstring, "token=", 6)) + inputstring += 6; + + err = b64decode (inputstring, NULL, &intoken, &intoklen); + /* Just to be safe that we don't overflow an ulong we check the + * actual size against an arbitrary limit. */ + if (!err && intoklen > 65535) + err = gpg_error (GPG_ERR_ERANGE); + if (err || !intoklen) + { + log_error ("error decoding received auth token: %s\n", + err? gpg_strerror (err):"empty challenge token received"); + if (!err) + err = gpg_error (GPG_ERR_BAD_AUTH); + goto leave; + } + } + + if (!proxy->spn) + { + char *buffer = strconcat ("HTTP/", (*proxy->uri->host + ?proxy->uri->host:"localhost"), NULL); + if (!buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (opt_debug) + log_debug ("http.c:proxy_connect: using '%s' as SPN\n", buffer); + proxy->spn = utf8_to_wchar (buffer); + xfree (buffer); + if (!proxy->spn) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + if (!proxy->token_size || !proxy->outtoken) /* Not yet initialized. */ + { + PSecPkgInfoW pinfo; + + rc = QuerySecurityPackageInfoW (NEGOSSP_NAME_W, &pinfo); + if (rc) + { + log_error ("QSPI(Negotiate) failed: %s (%d)\n", + w32_strerror (rc), rc); + err = gpg_error (GPG_ERR_BAD_AUTH); + goto leave; + } + proxy->token_size = pinfo->cbMaxToken; + FreeContextBuffer (pinfo); + + proxy->outtoken = xtrymalloc (proxy->token_size); + if (!proxy->outtoken) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + if (!proxy->cred_handle_valid) + { + rc = AcquireCredentialsHandleW (NULL, NEGOSSP_NAME_W, + SECPKG_CRED_OUTBOUND, NULL, + NULL, /* Current user */ + NULL, /* reserved */ + NULL, /* reserved */ + &proxy->cred_handle, + NULL /* expiry */); + if (rc) + { + log_error ("ACH(Negotiate) failed: %s (%d)\n", w32_strerror (rc), rc); + err = gpg_error (GPG_ERR_NO_AUTH); + goto leave; + } + proxy->cred_handle_valid = 1; + } + + /* Now generate our challenge-response message. */ + if (intoken) + { + chlg_buf.BufferType = SECBUFFER_TOKEN; + chlg_buf.pvBuffer = intoken; + chlg_buf.cbBuffer = intoklen; + chlg_desc.ulVersion = SECBUFFER_VERSION; + chlg_desc.cBuffers = 1; + chlg_desc.pBuffers = &chlg_buf; + } + + resp_buf.BufferType = SECBUFFER_TOKEN; + resp_buf.pvBuffer = proxy->outtoken; + resp_buf.cbBuffer = proxy->token_size; + resp_desc.ulVersion = SECBUFFER_VERSION; + resp_desc.cBuffers = 1; + resp_desc.pBuffers = &resp_buf; + rc = InitializeSecurityContextW (&proxy->cred_handle, + (intoken && proxy->ctxt_handle_valid) + ? &proxy->ctxt_handle : NULL, + proxy->spn, /* service principal name */ + ISC_REQ_CONFIDENTIALITY, + 0, /* reserved */ + SECURITY_NATIVE_DREP, + intoken? &chlg_desc : NULL, + 0, /* reserved */ + &proxy->ctxt_handle, /* new context */ + &resp_desc, /* the output. */ + &attrs, /* attribs of the context. */ + &expiry); + switch (rc) + { + case SEC_E_OK: /* All done and no more ISC expected. */ + break; + + case SEC_I_COMPLETE_AND_CONTINUE: /* Need to call CompleteAuthToken. */ + case SEC_I_COMPLETE_NEEDED: + rc = CompleteAuthToken (&proxy->ctxt_handle, &resp_desc); + log_error ("CompleteAuthToken failed: %s (%d)\n", w32_strerror (rc), rc); + err = gpg_error (GPG_ERR_NO_AUTH); + goto leave; + break; + + case SEC_I_CONTINUE_NEEDED: /* Send the new token to the client. */ + break; + + default: + log_error ("ISC(Negotiate) failed: %s (%d)\n", w32_strerror (rc), rc); + err = gpg_error (GPG_ERR_NO_AUTH); + goto leave; + } + + proxy->outtoklen = resp_buf.cbBuffer; + proxy->ctxt_handle_valid = 1; + err = 0; + + leave: + xfree (intoken); + return err; + +#else /*!HAVE_W32_SYSTEM*/ + + (void)proxy; + (void)inputstring; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + +#endif /*!HAVE_W32_SYSTEM*/ +} +#endif /*USE_TLS*/ + + /* Use the CONNECT method to proxy our TLS stream. */ #ifdef USE_TLS static gpg_error_t @@ -2344,11 +2541,24 @@ run_proxy_connect (http_t hd, proxy_info_t proxy, unsigned short port) { gpg_error_t err; - int saved_flags; + int saved_flags = hd->flags; char *authhdr = NULL; char *request = NULL; + char *tmpstr = NULL; + const char *s, *parms; + unsigned int idx; + int auth_basic = 0; + enum auth_negotiate_states authstate = 0; + unsigned int authpasses = 0; - if (proxy->uri->auth + /* Authentication methods implemented here: + * RFC-2617 - HTTP Authentication: Basic and Digest Access Authentication + * RFC-4559 - SPNEGO-based Kerberos and NTLM HTTP Authentication + */ + auth_basic = !!proxy->uri->auth; + + /* For basic authentication we need to send just one request. */ + if (auth_basic && !(authhdr = make_header_line ("Proxy-Authorization: Basic ", "\r\n", proxy->uri->auth, @@ -2358,24 +2568,32 @@ run_proxy_connect (http_t hd, proxy_info_t proxy, goto leave; } - request = es_bsprintf ("CONNECT %s:%hu HTTP/1.0\r\nHost: %s:%hu\r\n%s", + again: + xfree (request); + request = es_bsprintf ("CONNECT %s:%hu HTTP/1.%c\r\nHost: %s:%hu\r\n%s%s", httphost ? httphost : server, port, + auth_basic? '0' : '1', httphost ? httphost : server, port, - authhdr ? authhdr : ""); + authhdr ? authhdr : "", + auth_basic? "" : "Connection: keep-alive\r\n"); if (!request) { err = gpg_error_from_syserror (); goto leave; } + hd->keep_alive = !auth_basic; /* We may need to send more requests. */ if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) log_debug_with_string (request, "http.c:proxy:request:"); - err = make_fp_write (hd, 0, NULL); - if (err) - goto leave; + if (!hd->fp_write) + { + err = make_fp_write (hd, 0, NULL); + if (err) + goto leave; + } if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) { @@ -2389,35 +2607,140 @@ run_proxy_connect (http_t hd, proxy_info_t proxy, /* Get the response and set hd->fp_read */ err = http_wait_response (hd); - - /* Restore flags, destroy stream. */ - hd->flags = saved_flags; - es_fclose (hd->fp_read); - hd->fp_read = NULL; - hd->read_cookie = NULL; - - /* Reset state. */ - hd->in_data = 0; - if (err) goto leave; - if (hd->status_code != 200) - { - char *tmpstr; + { + unsigned long count = 0; + while (es_getc (hd->fp_read) != EOF) + count++; + if (opt_debug) + log_debug ("http.c:proxy_connect: skipped %lu bytes of response-body\n", + count); + } + + /* Reset state. */ + es_clearerr (hd->fp_read); + ((cookie_t)(hd->read_cookie))->up_to_empty_line = 1; + hd->in_data = 0; + + if (hd->status_code >= 200 && hd->status_code < 300 ) + err = 0; /* Success. */ + else if (hd->status_code == 407) + { + if (opt_debug) + log_debug ("http.c:proxy_connect: 407 seen\n"); + parms = NULL; + for (idx=0; (s = http_get_header (hd, "Proxy-Authenticate", idx)); idx++) + { + if (opt_debug) + log_debug ("http.c:proxy_connect: method=%s\n", s); + if (!parms) + parms = has_leading_keyword (s, "Negotiate"); + } + if (!parms) + authstate = AUTH_NGT_NONE; + else if (authstate == AUTH_NGT_NONE) + authstate = AUTH_NGT_RCVD; + + switch (authstate) + { + case AUTH_NGT_NONE: + if (opt_debug) + log_debug ("http.c:proxy_connect: no supported auth method\n"); + err = gpg_error (GPG_ERR_NO_AUTH); + break; + + case AUTH_NGT_RCVD: + if (opt_debug) + log_debug ("http.c:proxy_connect: using negotiate - init\n"); + err = proxy_get_token (proxy, NULL); + if (err) + goto leave; + if (proxy->outtoklen) /* Authentication needs to continue. */ + { + xfree (authhdr); + authhdr = make_header_line ("Proxy-Authorization: Negotiate ", + "\r\n", + proxy->outtoken, proxy->outtoklen); + if (!authhdr) + { + err = gpg_error_from_syserror (); + goto leave; + } + authstate = AUTH_NGT_SENT; + authpasses++; + goto again; + } + break; + + case AUTH_NGT_SENT: + if (opt_debug) + log_debug ("http.c:proxy_connect: using negotiate - next\n"); + if (!*parms) + { + log_debug ("proxy authentication failed" + " due to server not accepting our challenge\n"); + err = gpg_error (GPG_ERR_BAD_AUTH); + goto leave; + } + if (authpasses > 5) + { + log_error ("proxy authentication failed" + " due to too many passes\n"); + err = gpg_error (GPG_ERR_BAD_AUTH); + goto leave; + + } + err = proxy_get_token (proxy, parms); + if (err) + goto leave; + if (proxy->outtoklen) /* Authentication needs to continue. */ + { + xfree (authhdr); + authhdr = make_header_line ("Proxy-Authorization: Negotiate ", + "\r\n", + proxy->outtoken, proxy->outtoklen); + if (!authhdr) + { + err = gpg_error_from_syserror (); + goto leave; + } + authpasses++; + goto again; + } + break; + + default: + BUG(); + } + } + else + err = gpg_error (GPG_ERR_NO_DATA); + + if (err) + { + xfree (tmpstr); tmpstr = es_bsprintf ("%s:%hu", httphost ? httphost : server, port); log_error (_("error accessing '%s': http status %u\n"), tmpstr ? tmpstr : "out of core", http_get_status_code (hd)); - xfree (tmpstr); - err = gpg_error (GPG_ERR_NO_DATA); goto leave; } leave: + /* Restore flags, destroy stream, reset state. */ + hd->flags = saved_flags; + es_fclose (hd->fp_read); + hd->fp_read = NULL; + hd->read_cookie = NULL; + hd->keep_alive = 0; + hd->in_data = 0; + xfree (request); xfree (authhdr); + xfree (tmpstr); return err; } #endif /*USE_TLS*/ @@ -2810,19 +3133,26 @@ store_header (http_t hd, char *line) p++; value = p; - for (h=hd->headers; h; h = h->next) - if ( !strcmp (h->name, line) ) - break; - if (h) + /* Check whether we have already seen a line with that name. In + * that case we assume it is a comma separated list and merge + * them. Of course there are a few exceptions. */ + if (!strcmp (line, "Proxy-Authenticate") + || !strcmp (line, "Www-Authenticate")) + ; /* Better to have them separate. */ + else { - /* We have already seen a line with that name. Thus we assume - * it is a comma separated list and merge them. */ - p = strconcat (h->value, ",", value, NULL); - if (!p) - return gpg_err_code_from_syserror (); - xfree (h->value); - h->value = p; - return 0; + for (h=hd->headers; h; h = h->next) + if ( !strcmp (h->name, line) ) + break; + if (h) + { + p = strconcat (h->value, ",", value, NULL); + if (!p) + return gpg_err_code_from_syserror (); + xfree (h->value); + h->value = p; + return 0; + } } /* Append a new header. */ From 9a3e41c151febbbe5506283e2edef85d1d4ea94f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 2 Oct 2023 12:53:41 +0200 Subject: [PATCH 29/55] common: Improve lock strategy for dotlock. * common/dotlock.c (next_wait_interval): New. (dotlock_take_unix): Use new function. (dotlock_take_w32): Ditto. -- In particular when using a dotlock file for protecting the spawning and several processes try to spawn the agent or another component, we often run into long delays. The solution is to is to exponential backoff and also to reduce the initial delay from 50ms to 4ms. We further limit the maximum wait period to about 2 seconds and then repeat at intervals of 512, 1024 and 2048ms. In the wait-forever case we add a small random value to have different intervals per process. GnuPG-bug-id: 3380 For testing this code snippet in the spawning function might be useful: const char *s; if ((s=getenv("hold_gpg_file"))) while (!gnupg_access (s, F_OK)) gnupg_sleep (1); --- common/dotlock.c | 90 ++++++++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 38 deletions(-) diff --git a/common/dotlock.c b/common/dotlock.c index ab0a5a6a3..74186b776 100644 --- a/common/dotlock.c +++ b/common/dotlock.c @@ -1011,6 +1011,48 @@ dotlock_destroy (dotlock_t h) } +/* Return true if H has been taken. */ +int +dotlock_is_locked (dotlock_t h) +{ + return h && !!h->locked; +} + + +/* Return the next interval to wait. WTIME and TIMEOUT are pointers + * to the current state and are updated by this function. The + * returned value might be different from the value of WTIME. */ +static int +next_wait_interval (int *wtime, long *timeout) +{ + int result; + + /* Wait until lock has been released. We use retry intervals of 4, + * 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 512, 1024, 2048ms, and + * so on. If wait-forever was requested we add a small random value + * to have different timeouts per process. */ + if (!*wtime) + *wtime = 4; + else if (*wtime < 2048) + *wtime *= 2; + else + *wtime = 512; + + result = *wtime; + if (*wtime > 8 && *timeout < 0) + result += ((unsigned int)getpid() % 37); + + if (*timeout > 0) + { + if (result > *timeout) + result = *timeout; + *timeout -= result; + } + + return result; +} + + #ifdef HAVE_POSIX_SYSTEM /* Unix specific code of make_dotlock. Returns 0 on success and -1 on @@ -1170,27 +1212,14 @@ dotlock_take_unix (dotlock_t h, long timeout) if (timeout) { struct timeval tv; + int wtimereal; - /* Wait until lock has been released. We use increasing retry - intervals of 50ms, 100ms, 200ms, 400ms, 800ms, 2s, 4s and 8s - but reset it if the lock owner meanwhile changed. */ - if (!wtime || ownerchanged) - wtime = 50; - else if (wtime < 800) - wtime *= 2; - else if (wtime == 800) - wtime = 2000; - else if (wtime < 8000) - wtime *= 2; + if (ownerchanged) + wtime = 0; /* Reset because owner chnaged. */ - if (timeout > 0) - { - if (wtime > timeout) - wtime = timeout; - timeout -= wtime; - } + wtimereal = next_wait_interval (&wtime, &timeout); - sumtime += wtime; + sumtime += wtimereal; if (sumtime >= 1500) { sumtime = 0; @@ -1198,9 +1227,8 @@ dotlock_take_unix (dotlock_t h, long timeout) pid, maybe_dead, maybe_deadlock(h)? _("(deadlock?) "):""); } - - tv.tv_sec = wtime / 1000; - tv.tv_usec = (wtime % 1000) * 1000; + tv.tv_sec = wtimereal / 1000; + tv.tv_usec = (wtimereal % 1000) * 1000; select (0, NULL, NULL, NULL, &tv); goto again; } @@ -1242,28 +1270,14 @@ dotlock_take_w32 (dotlock_t h, long timeout) if (timeout) { - /* Wait until lock has been released. We use retry intervals of - 50ms, 100ms, 200ms, 400ms, 800ms, 2s, 4s and 8s. */ - if (!wtime) - wtime = 50; - else if (wtime < 800) - wtime *= 2; - else if (wtime == 800) - wtime = 2000; - else if (wtime < 8000) - wtime *= 2; + int wtimereal; - if (timeout > 0) - { - if (wtime > timeout) - wtime = timeout; - timeout -= wtime; - } + wtimereal = next_wait_interval (&wtime, &timeout); if (wtime >= 800) my_info_1 (_("waiting for lock %s...\n"), h->lockname); - Sleep (wtime); + Sleep (wtimereal); goto again; } From 68b7aff9ce345c1f73f84d6b1106eab956d75510 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 4 Oct 2023 10:23:30 +0900 Subject: [PATCH 30/55] agent: Fix agent_update_private_key. * agent/findkey.c (agent_update_private_key): Check FNAME0. -- Cherry-pick master commit of: 08e529fa7cfa8f55256337dd525fe8724c78cd92 Fixes-commit: a216e9c028ee389c4bf0250b822d567ffe9ad85e Signed-off-by: NIIBE Yutaka --- agent/findkey.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/findkey.c b/agent/findkey.c index 41f911cf8..eb2b7a17a 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -333,7 +333,7 @@ agent_update_private_key (const unsigned char *grip, nvc_t pk) int blocksigs = 0; fname0 = fname_from_keygrip (grip, 0); - if (!fname) + if (!fname0) { err = gpg_error_from_syserror (); goto leave; From c1f78634ec3927ddcfdc4687bc6e408c658a0ece Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 5 Oct 2023 10:02:59 +0200 Subject: [PATCH 31/55] sm: Improve the octet string cramming for pkcs#12 * sm/minip12.c (need_octet_string_cramming): New. (tlv_expect_object, tlv_expect_octet_string): Run the test before cramming. * sm/minip12.c (ENABLE_DER_STRUCT_DUMPING): New but undefined macro for debug purposes. (bag_decrypted_data_p, bag_data_p): Use macro to allow dumping. -- This bug was exhibited by importing a gpgsm exported EC certificate. We use an extra test instead of retrying to allow retruning an error from malloc failure. And well, for easier reading of the code. GnuPG-bug-id: 6536 --- sm/minip12.c | 79 ++++++++++++++---- tests/cms/Makefile.am | 1 + tests/cms/samplekeys/Description-p12 | 10 +++ .../edward.tester@demo.gnupg.com.p12 | Bin 0 -> 1561 bytes 4 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 tests/cms/samplekeys/edward.tester@demo.gnupg.com.p12 diff --git a/sm/minip12.c b/sm/minip12.c index 265243f3e..ed80534b6 100644 --- a/sm/minip12.c +++ b/sm/minip12.c @@ -50,6 +50,8 @@ #define DIM(v) (sizeof(v)/sizeof((v)[0])) #endif +/* Enable the next macro to dump stuff for debugging. */ +#undef ENABLE_DER_STRUCT_DUMPING static unsigned char const oid_data[9] = { @@ -111,6 +113,8 @@ static unsigned char const data_mactemplate[51] = { #define DATA_MACTEMPLATE_MAC_OFF 17 #define DATA_MACTEMPLATE_SALT_OFF 39 +/* Note that the BMP String in this template reads: + * "GnuPG exported certificate ffffffff" */ static unsigned char const data_attrtemplate[106] = { 0x31, 0x7c, 0x30, 0x55, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x14, 0x31, @@ -210,6 +214,8 @@ static int opt_verbose; static unsigned char *cram_octet_string (const unsigned char *input, size_t length, size_t *r_newlength); +static int need_octet_string_cramming (const unsigned char *input, + size_t length); @@ -560,7 +566,7 @@ tlv_expect_sequence (struct tlv_ctx_s *tlv) return _tlv_push (tlv); } -/* Variant of tlv_expect_sequence to be used for the ouyter sequence +/* Variant of tlv_expect_sequence to be used for the outer sequence * of an object which might have padding after the ASN.1 data. */ static gpg_error_t tlv_expect_top_sequence (struct tlv_ctx_s *tlv) @@ -618,7 +624,8 @@ tlv_expect_object (struct tlv_ctx_s *tlv, int class, int tag, if (!tlv->ti.length) return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); - if (class == CLASS_CONTEXT && tag == 0 && tlv->ti.is_constructed) + if (class == CLASS_CONTEXT && tag == 0 && tlv->ti.is_constructed + && need_octet_string_cramming (p, tlv->ti.length)) { char *newbuffer; @@ -665,7 +672,8 @@ tlv_expect_octet_string (struct tlv_ctx_s *tlv, int encapsulates, if (!(n=tlv->ti.length)) return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); - if (encapsulates && tlv->ti.is_constructed) + if (encapsulates && tlv->ti.is_constructed + && need_octet_string_cramming (p, n)) { char *newbuffer; @@ -859,6 +867,39 @@ cram_octet_string (const unsigned char *input, size_t length, } +/* Return true if (INPUT,LENGTH) is a structure which should be passed + * to cram_octet_string. This is basically the same loop as in + * cram_octet_string but without any actual copying. */ +static int +need_octet_string_cramming (const unsigned char *input, size_t length) +{ + const unsigned char *s = input; + size_t n = length; + struct tag_info ti; + + if (!length) + return 0; + + while (n) + { + if (parse_tag (&s, &n, &ti)) + return 0; + if (ti.class == CLASS_UNIVERSAL && ti.tag == TAG_OCTET_STRING + && !ti.ndef && !ti.is_constructed) + { + s += ti.length; + n -= ti.length; + } + else if (ti.class == CLASS_UNIVERSAL && !ti.tag && !ti.is_constructed) + break; /* Ready */ + else + return 0; + } + + return 1; +} + + static int string_to_key (int id, char *salt, size_t saltlen, int iter, const char *pw, int req_keylen, unsigned char *keybuf) @@ -1173,13 +1214,15 @@ bag_decrypted_data_p (const void *plaintext, size_t length) const unsigned char *p = plaintext; size_t n = length; - /* { */ - /* # warning debug code is enabled */ - /* FILE *fp = fopen ("tmp-minip12-plain-data.der", "wb"); */ - /* if (!fp || fwrite (p, n, 1, fp) != 1) */ - /* exit (2); */ - /* fclose (fp); */ - /* } */ +#ifdef ENABLE_DER_STRUCT_DUMPING + { + # warning debug code is enabled + FILE *fp = fopen ("tmp-minip12-plain-data.der", "wb"); + if (!fp || fwrite (p, n, 1, fp) != 1) + exit (2); + fclose (fp); + } +#endif /*ENABLE_DER_STRUCT_DUMPING*/ if (parse_tag (&p, &n, &ti)) return 0; @@ -1696,13 +1739,15 @@ bag_data_p (const void *plaintext, size_t length) const unsigned char *p = plaintext; size_t n = length; -/* { */ -/* # warning debug code is enabled */ -/* FILE *fp = fopen ("tmp-minip12-plain-key.der", "wb"); */ -/* if (!fp || fwrite (p, n, 1, fp) != 1) */ -/* exit (2); */ -/* fclose (fp); */ -/* } */ +#ifdef ENABLE_DER_STRUCT_DUMPING + { +# warning debug code is enabled + FILE *fp = fopen ("tmp-minip12-plain-key.der", "wb"); + if (!fp || fwrite (p, n, 1, fp) != 1) + exit (2); + fclose (fp); + } +#endif /*ENABLE_DER_STRUCT_DUMPING*/ if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) return 0; diff --git a/tests/cms/Makefile.am b/tests/cms/Makefile.am index 7efdf37b1..d5d753902 100644 --- a/tests/cms/Makefile.am +++ b/tests/cms/Makefile.am @@ -99,6 +99,7 @@ EXTRA_DIST = $(XTESTS) $(KEYS) $(CERTS) $(TEST_FILES) \ samplekeys/opensc-test.p12 \ samplekeys/t5793-openssl.pfx \ samplekeys/t5793-test.pfx \ + samplekeys/edward.tester@demo.gnupg.com.p12 \ samplemsgs/pwri-sample.cbc.p7m \ samplemsgs/pwri-sample.cbc-2.p7m \ samplemsgs/pwri-sample.gcm.p7m \ diff --git a/tests/cms/samplekeys/Description-p12 b/tests/cms/samplekeys/Description-p12 index f882de9ea..6fbbd82cf 100644 --- a/tests/cms/samplekeys/Description-p12 +++ b/tests/cms/samplekeys/Description-p12 @@ -1,4 +1,6 @@ # Description-p12 - Machine readable description of our P12 test vectors +# The Cert line gives the SHA1 fingerprint of the certificate +# The Key line gives a hash of the key parameters as returned by minip12.c Name: ov-user.p12 Desc: Private test key from www.openvalidation.org @@ -30,3 +32,11 @@ Desc: QuaVadis format of t5793-openssl Pass: test Cert: 80348a438e4b803b99e708da0b7fdd0659dedd15 Key: c271e44ab4fb19ca1aae71102ea4d7292ccc981d + +Name: edward.tester@demo.gnupg.com.p12 +Desc: GnuPG exported Brainpool certificate +Pass: abc,123456 +Cert: ff810b9281a43c394aa138e9c7fd4c0193216fa6 +Key: 94c6d0b067370a8f2a09ae43cfe8d700bbd61e75 + +# eof # diff --git a/tests/cms/samplekeys/edward.tester@demo.gnupg.com.p12 b/tests/cms/samplekeys/edward.tester@demo.gnupg.com.p12 new file mode 100644 index 0000000000000000000000000000000000000000..a6f983780f6ca06c9be9cef235d46fa945430bfd GIT binary patch literal 1561 zcmY+Ec{G%39LL|6*VrOqG8Yv}$|&#ThRHfeldWm&-Lfy)W;8~cO3Vz}kfKZ3#uCO7 zQ{#>pTlN~n4TVN&Q#WM`t?szz-rK!@{Lc4zKHqbG=RD6JADDqaih<%VLt+det($~T zT9N}LK^8+o05K%GMYtViK;Azp1d9O?M94u51Vpj-M+3s@2${b(tN{_QJS2svr)|=m zXowOMlLF!y5bC#sLZVkTReZX8&qUSxi_My6vA!!roY*}T@#+H3*@VoaW6FKzE;|Mb zmo(&6X+6vM0r;)S$fvjj^wb(2LCTYBKSJt@+#Xy;ZoEI=o6NO(MMzPCVx@D}FJ@rM z5>Q=68)e`?qF*DU17o`vpeP&`eE9HqDh1FIO(ArY-O{uO-_Stt5 zeM86wllQr?PDY8zmhb-1u?VJ83*8h=cQX`!^^Kj>WqW;I;JlT-_7n5-sdsaFk~E=V zUcpO?<(4luR5$uTIO&x3oI%B?tgrp0j^FD)=8bQO{L(thQ`d??3blGC`;n^JTPHd~ zj13RZI@*~>jw__y)poHGs(0P-9avYJk63>}{KUj#LiC6~ABm?z^^r6!Gn7JP&7&MH zdF>RuWy*@$@BTJkjwbS#XMwxthbct1C<_Z<1U5NHn zGs&n{R4v95E{CL@h%4pw+YBQeSh_A=?Al(*^aq-)d~aWIS{_ z^qRpX*6+Wp{v2%+J=dJHRNQp zPEzm5ZP=-saMtsiC3$)9{ixJzLw#dJj{5WxBww8w!sxD?iQX}8`nDo9dpYO;N=?b2 zxG~!PgA0B_*U6nm3!S8#PQ&|W zhlK10y=}B5;lSJiey5Mjym~G{HV&FUD2~qwD-r;RgOLkSc=38- zcl06`AD8TB8|R_WuQG@jXJ#KysJ(QLb!#%qM#pVo<;=CE%1Z{6snZ~zTxD9`U5aQ6-u$8242812P=A@KBo7rh z%gzTXmI=_Uud#quW1~xiq_@Lu$7-ENv*mavUd%O!jV6Mg)F9(7X6iJiThj*K^wfXa zgh|zFj;qUOa_DoU`TWTq!OQdEtSZ8EOKP%oZEm9Glcc5(6dymewU!Nx2IsvhO<_CQ zv6$1a>%UKwR>t5r+5-Ck5)cg#M5zG;0w;h-fDBLos^}abBL1RWy@dclqVWfO|7tV< zEMNfa0x*D~i2DMD@a7+Dk(l+6@(Nw{3cc7AYgb_Wrt6ynn661KfibWqERB#r%ZrPt oN&t{@c(F$V$7b0aj9<|~l_+pDVo;5c)T>djsne-Jb@dPb1p3sgLjV8( literal 0 HcmV?d00001 From b4449ffabc10faa5f532be22738f2ef61828c33b Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 5 Oct 2023 11:07:16 +0200 Subject: [PATCH 32/55] gpg-card: Give a hint on how to get help for the "yubikey" command. * tools/card-yubikey.c (yubikey_commands): Print a hint. --- tools/card-yubikey.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/card-yubikey.c b/tools/card-yubikey.c index ece7edc48..63d49c762 100644 --- a/tools/card-yubikey.c +++ b/tools/card-yubikey.c @@ -332,6 +332,8 @@ yubikey_commands (card_info_t info, estream_t fp, int argc, const char *argv[]) cmd = ykDISABLE; else { + log_info ("Please use \"%s\" to list the available sub-commands\n", + "help yubikey"); err = gpg_error (GPG_ERR_UNKNOWN_COMMAND); goto leave; } From 19caa5c267a53d248efea5a09b0bd10962fd8003 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 26 Sep 2023 13:42:37 +0900 Subject: [PATCH 33/55] agent: Initialize FP for the case of error return. * agent/findkey.c (agent_write_private_key): Initialize FP. -- Cherry-picked from master commit of: a8618fdccdab228a8bbe3efeb87223a68fa57219 Signed-off-by: NIIBE Yutaka --- agent/findkey.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/findkey.c b/agent/findkey.c index eb2b7a17a..a5f022574 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -118,7 +118,7 @@ agent_write_private_key (const unsigned char *grip, gpg_error_t err; char *fname = NULL; char *tmpfname = NULL; - estream_t fp; + estream_t fp = NULL; int newkey; nvc_t pk = NULL; gcry_sexp_t key = NULL; From 9909f622f69e2b5775099931406dce2d35011281 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 28 Sep 2023 11:59:14 +0900 Subject: [PATCH 34/55] agent: fix tpm2d keytotpm handling * agent/divert-tpm2.c (agent_write_tpm2_shadow_key): Call agent_delete_key before agent_write_private_key. Recover from an error. -- Cherry-picked from master commit of: eda3997b439e415f1bebaa3be20c8bdb43d3a1d0 Fixes-commit: a1015bf2fc07dabb1200eab5fa41f13e7bf98202 Signed-off-by: James Bottomley --- agent/divert-tpm2.c | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/agent/divert-tpm2.c b/agent/divert-tpm2.c index b2f884f93..e7c6a8aae 100644 --- a/agent/divert-tpm2.c +++ b/agent/divert-tpm2.c @@ -26,9 +26,10 @@ divert_tpm2_pksign (ctrl_t ctrl, static gpg_error_t agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip, - unsigned char *shadow_info) + unsigned char *shadow_info, + gcry_sexp_t s_key) { - gpg_error_t err; + gpg_error_t err, err1; unsigned char *shdkey; unsigned char *pkbuf; size_t len; @@ -44,7 +45,14 @@ agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip, xfree (pkbuf); if (err) { - log_error ("shadowing the key failed: %s\n", gpg_strerror (err)); + log_error ("shadowing the tpm key failed: %s\n", gpg_strerror (err)); + return err; + } + + err = agent_delete_key (ctrl, NULL, grip, 1, 0); + if (err) + { + log_error ("failed to delete unshadowed key: %s\n", gpg_strerror (err)); return err; } @@ -53,7 +61,22 @@ agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip, NULL, NULL, NULL, 0); xfree (shdkey); if (err) - log_error ("error writing key: %s\n", gpg_strerror (err)); + { + log_error ("error writing tpm key: %s\n", gpg_strerror (err)); + + len = gcry_sexp_sprint(s_key, GCRYSEXP_FMT_CANON, NULL, 0); + pkbuf = xtrymalloc(len); + if (!pkbuf) + return GPG_ERR_ENOMEM; + + gcry_sexp_sprint(s_key, GCRYSEXP_FMT_CANON, pkbuf, len); + err1 = agent_write_private_key (grip, pkbuf, len, 1 /*force*/, + NULL, NULL, NULL, 0); + xfree(pkbuf); + if (err1) + log_error ("error trying to restore private key: %s\n", + gpg_strerror (err1)); + } return err; } @@ -68,7 +91,7 @@ divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, ret = agent_tpm2d_writekey(ctrl, &shadow_info, s_skey); if (!ret) { - ret = agent_write_tpm2_shadow_key (ctrl, grip, shadow_info); + ret = agent_write_tpm2_shadow_key (ctrl, grip, shadow_info, s_skey); xfree (shadow_info); } return ret; From 8d0819346db8943b519ea7685569382c56776d15 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 28 Sep 2023 13:26:52 +0900 Subject: [PATCH 35/55] tpm2d: Check SWTPM environment variable for swtpm support. * tpm2d/intel-tss.h (TSS_Create): Check SWTPM. -- Cherry-picked from master commit of: 1da40db03eba4aa056f7cdf8ef90292272a4147d Signed-off-by: James Bottomley --- tpm2d/intel-tss.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tpm2d/intel-tss.h b/tpm2d/intel-tss.h index 615f81e2f..53da5cee2 100644 --- a/tpm2d/intel-tss.h +++ b/tpm2d/intel-tss.h @@ -285,9 +285,15 @@ TSS_Create(TSS_CONTEXT **tssContext) */ if (intType) { - if (strcmp("socsim", intType) == 0) { - tctildr = "mssim"; - } + if (strcmp("socsim", intType) == 0) + { + char *swtpm = getenv("SWTPM"); + + if (!swtpm || strlen(swtpm) == 0) + tctildr = "mssim"; + else + tctildr = "swtpm"; + } else if (strcmp("dev", intType) == 0) { tctildr = "device"; From d17efdcd6f755f13c9ff9b7a3127c13496ab7055 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 3 Oct 2023 11:53:00 +0900 Subject: [PATCH 36/55] tests:tpm2dtests: Fix tests with TPM2D. * tests/tpm2dtests/Makefile.am (TESTS_ENVIRONMENT): Fix. * tests/tpm2dtests/all-tests.scm: Follow the change of gpgscm. * tests/tpm2dtests/run-tests.scm: Likewise. -- Cherry-picked from master commit of: 321f9c0a3f2873cb3007e2bc2a542bbd0b2cc974 GnuPG-bug-id: 6052 Signed-off-by: NIIBE Yutaka --- tests/tpm2dtests/Makefile.am | 4 ++-- tests/tpm2dtests/all-tests.scm | 21 ++++++++++++--------- tests/tpm2dtests/run-tests.scm | 2 ++ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/tests/tpm2dtests/Makefile.am b/tests/tpm2dtests/Makefile.am index 72ad11d9b..6048d201c 100644 --- a/tests/tpm2dtests/Makefile.am +++ b/tests/tpm2dtests/Makefile.am @@ -34,10 +34,10 @@ TESTS_ENVIRONMENT = LC_ALL=C \ PATH="../gpgscm:$(PATH)" \ abs_top_srcdir="$(abs_top_srcdir)" \ objdir="$(abs_top_builddir)" \ - TPMSERVER="$(TPMSERVER)" \ + TPMSERVER="$(TPMSERVER)" TSSSTARTUP="$(TSSSTARTUP)" \ SWTPM="$(SWTPM)" \ SWTPM_IOCTL="$(SWTPM_IOCTL)" \ - GNUPG_BUILD_ROOT="$(abs_top_builddir)/tests" \ + GNUPG_BUILD_ROOT="$(abs_top_builddir)" \ GNUPG_IN_TEST_SUITE=fact \ GPGSCM_PATH="$(abs_top_srcdir)/tests/gpgscm" diff --git a/tests/tpm2dtests/all-tests.scm b/tests/tpm2dtests/all-tests.scm index bf7a981ca..8934f01f2 100644 --- a/tests/tpm2dtests/all-tests.scm +++ b/tests/tpm2dtests/all-tests.scm @@ -30,8 +30,9 @@ (make-environment-cache (test::scm #f - (path-join "tests" "openpgp" "setup.scm") - (in-srcdir "tests" "openpgp" "setup.scm")))) + #f + (path-join "tests" "tpm2dtests" "setup.scm") + (in-srcdir "tests" "tpm2dtests" "setup.scm")))) (define (qualify path variant) (string-append "<" variant ">" path)) @@ -40,8 +41,9 @@ (make-environment-cache (test::scm #f - (qualify (path-join "tests" "openpgp" "setup.scm") variant) - (in-srcdir "tests" "openpgp" "setup.scm") + variant + (path-join "tests" "tpm2dtests" "setup.scm") + (in-srcdir "tests" "tpm2dtests" "setup.scm") (string-append "--" variant)))) (define setup-use-keyring (setup* "use-keyring")) @@ -55,7 +57,8 @@ (define tests (map (lambda (name) (test::scm setup - (qualify (path-join "tests" "tpm2dtests" name) "standard") + "standards" + (path-join "tests" "tpm2dtests" name) (in-srcdir "tests" "tpm2dtests" name))) all-tests)) (when *run-all-tests* @@ -65,15 +68,15 @@ ;; The second pass uses the keyboxd (map (lambda (name) (test::scm setup-use-keyboxd - (qualify (path-join "tests" "tpm2dtests" name) - "keyboxd") + "keyboxd" + (path-join "tests" "tpm2dtests" name) (in-srcdir "tests" "tpm2dtests" name) "--use-keyboxd")) all-tests) ;; The third pass uses the legact pubring.gpg (map (lambda (name) (test::scm setup-use-keyring - (qualify (path-join "tests" "tpm2dtests" name) - "keyring") + "keyring" + (path-join "tests" "tpm2dtests" name) (in-srcdir "tests" "tpm2dtests" name) "--use-keyring")) all-tests) ))) diff --git a/tests/tpm2dtests/run-tests.scm b/tests/tpm2dtests/run-tests.scm index fdf1859a8..638d3a8a1 100644 --- a/tests/tpm2dtests/run-tests.scm +++ b/tests/tpm2dtests/run-tests.scm @@ -29,6 +29,7 @@ (define setup (make-environment-cache (test::scm #f + #f (path-join "tests" "tpm2dtests" "setup.scm") (in-srcdir "tests" "tpm2dtests" "setup.scm")))) @@ -38,6 +39,7 @@ (load-tests "tests" "tpm2dtests") (map (lambda (name) (test::scm setup + #f (path-join "tests" "tpm2dtests" name) (in-srcdir "tests" "tpm2dtests" name) "--use-keyring")) tests))) From e783866f414097e38fb1cf0061005989d11c468b Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 3 Oct 2023 16:51:46 +0900 Subject: [PATCH 37/55] tools: Add TPM2DAEMON_SOCK_NAME for --remove-socketdir. * tools/gpgconf.c (main): Care about tpm2d. Emit correct ERR. -- Cherry-picked from master commit of: 25c84ffd1078e6619761aa731a82dbaf4175c02e Signed-off-by: NIIBE Yutaka --- tools/gpgconf.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/gpgconf.c b/tools/gpgconf.c index 6dcdc9f3c..f34248c40 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -963,7 +963,8 @@ main (int argc, char **argv) GPG_AGENT_SSH_SOCK_NAME, SCDAEMON_SOCK_NAME, KEYBOXD_SOCK_NAME, - DIRMNGR_SOCK_NAME + DIRMNGR_SOCK_NAME, + TPM2DAEMON_SOCK_NAME }; int i; char *p; @@ -976,8 +977,11 @@ main (int argc, char **argv) xfree (p); } if (gnupg_rmdir (socketdir)) - gc_error (1, 0, "error removing '%s': %s", - socketdir, gpg_strerror (err)); + { + err = gpg_error_from_syserror (); + gc_error (1, 0, "error removing '%s': %s", + socketdir, gpg_strerror (err)); + } } else if (gpg_err_code (err) == GPG_ERR_ENOENT) gc_error (0, 0, "warning: removing '%s' failed: %s", From 0494ec8f4d6399336f3202a23144f4afe734aede Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Tue, 3 Oct 2023 16:55:02 +0900 Subject: [PATCH 38/55] build: Simplify detecting a TPM emulator. * configure.ac (TPMSERVER): Don't supply hard-coded path. (SWTPM, SWTPM_IOCTL, TSSSTARTUP): Likewise. -- Cherry-picked from master commit of: f2ca727978da5b1ed84f97bf37d604e8a4e60091 Having hard-coded path has bad side effect; It may not be detected even if it's available with PATH. Signed-off-by: NIIBE Yutaka --- configure.ac | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index 6f544bf98..6536810f1 100644 --- a/configure.ac +++ b/configure.ac @@ -1613,10 +1613,10 @@ if test "$build_tpm2d" = "yes"; then if test "$have_libtss" != no; then AC_DEFINE(HAVE_LIBTSS, 1, [Defined if we have TPM2 support library]) # look for a TPM emulator for testing - AC_PATH_PROG(TPMSERVER, tpm_server,,/bin:/usr/bin:/usr/lib/ibmtss:/usr/libexec/ibmtss) - AC_PATH_PROG(SWTPM, swtpm,,/bin:/usr/bin:/usr/lib/ibmtss:/usr/libexec/ibmtss) - AC_PATH_PROG(SWTPM_IOCTL, swtpm_ioctl,,/bin:/usr/bin:/usr/lib/ibmtss:/usr/libexec/ibmtss) - AC_PATH_PROG(TSSSTARTUP, tssstartup,,/bin:/usr/bin:/usr/lib/ibmtss:/usr/libexec/ibmtss) + AC_PATH_PROG(TPMSERVER, tpm_server) + AC_PATH_PROG(SWTPM, swtpm) + AC_PATH_PROG(SWTPM_IOCTL, swtpm_ioctl) + AC_PATH_PROG(TSSSTARTUP, tssstartup) fi fi if test "$have_libtss" = no; then From 0e200f2187e005d8c52d8efb5ef89e4709eabcc1 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 4 Oct 2023 18:30:33 +0900 Subject: [PATCH 39/55] tests:tpm2dtests: Fix tests with SWTPM. * configure.ac (TEST_LIBTSS): Fix the condition with SWTPM. * tests/tpm2dtests/start_sw_tpm.sh: Use --daemon and --pid to run SWTPM. -- Cherry-picked from master commit of: 98dd6f7af6aa3dcce19f20c22e3f825676e6b184 GnuPG-bug-id: 6052 Signed-off-by: NIIBE Yutaka --- configure.ac | 2 +- tests/tpm2dtests/start_sw_tpm.sh | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index 6536810f1..72c99b1be 100644 --- a/configure.ac +++ b/configure.ac @@ -1625,7 +1625,7 @@ fi AC_SUBST(LIBTSS_LIBS) AC_SUBST(LIBTSS_CFLAGS) AM_CONDITIONAL(HAVE_LIBTSS, test "$have_libtss" != no) -AM_CONDITIONAL(TEST_LIBTSS, test -n "$TPMSERVER" || test -n "$SWTPM" && test -n "$TSSSTARTUP") +AM_CONDITIONAL(TEST_LIBTSS, test -n "$TPMSERVER" -o -n "$SWTPM" -a -n "$TSSSTARTUP" -a -n "$SWTPM_IOCTL") AC_SUBST(HAVE_LIBTSS) # diff --git a/tests/tpm2dtests/start_sw_tpm.sh b/tests/tpm2dtests/start_sw_tpm.sh index 36e1a806e..fc86801e2 100755 --- a/tests/tpm2dtests/start_sw_tpm.sh +++ b/tests/tpm2dtests/start_sw_tpm.sh @@ -3,12 +3,15 @@ # remove any prior TPM contents rm -f NVChip h*.bin *.permall if [ -x "${SWTPM}" ]; then - ${SWTPM} socket --tpm2 --server type=tcp,port=2321 \ - --ctrl type=tcp,port=2322 --tpmstate dir=`pwd` & + ${SWTPM} socket --tpm2 --daemon \ + --pid file=swtpm.pid \ + --server type=tcp,port=2321 \ + --ctrl type=tcp,port=2322 --tpmstate dir=`pwd` + pid=$(cat swtpm.pid) else ${TPMSERVER} > /dev/null 2>&1 & + pid=$! fi -pid=$! ## # This powers on the tpm and starts it # then we derive the RSA version of the storage seed and From 9353dc811a04cf47f2445bb1e1f0401ea5f3d044 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 5 Oct 2023 10:21:35 +0900 Subject: [PATCH 40/55] tests:tpm2dtests: Modify tests with SWTPM and relax the condition. * configure.ac (SWTPM_IOCTL): Remove. (TEST_LIBTSS): Fix the condition. * tests/tpm2dtests/Makefile.am (TESTS_ENVIRONMENT): Remove SWTPM_IOCTL. * tests/tpm2dtests/start_sw_tpm.sh: Add --flags to invoke SWTPM, not requiring SWTPM_IOCTL and TSSSTARTUP any more. -- Cherry-picked from master commit of: 227b3b14f4be2f33ed721818c2186e7fca4cebdf GnuPG-bug-id: 6052 Signed-off-by: NIIBE Yutaka --- configure.ac | 5 ++- tests/tpm2dtests/Makefile.am | 1 - tests/tpm2dtests/start_sw_tpm.sh | 55 ++++++++++++++++---------------- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/configure.ac b/configure.ac index 72c99b1be..fc0590c14 100644 --- a/configure.ac +++ b/configure.ac @@ -1614,9 +1614,8 @@ if test "$build_tpm2d" = "yes"; then AC_DEFINE(HAVE_LIBTSS, 1, [Defined if we have TPM2 support library]) # look for a TPM emulator for testing AC_PATH_PROG(TPMSERVER, tpm_server) - AC_PATH_PROG(SWTPM, swtpm) - AC_PATH_PROG(SWTPM_IOCTL, swtpm_ioctl) AC_PATH_PROG(TSSSTARTUP, tssstartup) + AC_PATH_PROG(SWTPM, swtpm) fi fi if test "$have_libtss" = no; then @@ -1625,7 +1624,7 @@ fi AC_SUBST(LIBTSS_LIBS) AC_SUBST(LIBTSS_CFLAGS) AM_CONDITIONAL(HAVE_LIBTSS, test "$have_libtss" != no) -AM_CONDITIONAL(TEST_LIBTSS, test -n "$TPMSERVER" -o -n "$SWTPM" -a -n "$TSSSTARTUP" -a -n "$SWTPM_IOCTL") +AM_CONDITIONAL(TEST_LIBTSS, test -n "$SWTPM" -o -n "$TPMSERVER" -a -n "$TSSSTARTUP") AC_SUBST(HAVE_LIBTSS) # diff --git a/tests/tpm2dtests/Makefile.am b/tests/tpm2dtests/Makefile.am index 6048d201c..ceaf56420 100644 --- a/tests/tpm2dtests/Makefile.am +++ b/tests/tpm2dtests/Makefile.am @@ -36,7 +36,6 @@ TESTS_ENVIRONMENT = LC_ALL=C \ objdir="$(abs_top_builddir)" \ TPMSERVER="$(TPMSERVER)" TSSSTARTUP="$(TSSSTARTUP)" \ SWTPM="$(SWTPM)" \ - SWTPM_IOCTL="$(SWTPM_IOCTL)" \ GNUPG_BUILD_ROOT="$(abs_top_builddir)" \ GNUPG_IN_TEST_SUITE=fact \ GPGSCM_PATH="$(abs_top_srcdir)/tests/gpgscm" diff --git a/tests/tpm2dtests/start_sw_tpm.sh b/tests/tpm2dtests/start_sw_tpm.sh index fc86801e2..a44833e28 100755 --- a/tests/tpm2dtests/start_sw_tpm.sh +++ b/tests/tpm2dtests/start_sw_tpm.sh @@ -3,36 +3,35 @@ # remove any prior TPM contents rm -f NVChip h*.bin *.permall if [ -x "${SWTPM}" ]; then - ${SWTPM} socket --tpm2 --daemon \ - --pid file=swtpm.pid \ - --server type=tcp,port=2321 \ - --ctrl type=tcp,port=2322 --tpmstate dir=`pwd` - pid=$(cat swtpm.pid) + ${SWTPM} socket --tpm2 --daemon \ + --pid file=swtpm.pid \ + --server type=tcp,port=2321 \ + --ctrl type=tcp,port=2322 \ + --flags not-need-init,startup-clear \ + --tpmstate dir=`pwd` + cat swtpm.pid else ${TPMSERVER} > /dev/null 2>&1 & pid=$! -fi -## -# This powers on the tpm and starts it -# then we derive the RSA version of the storage seed and -# store it permanently at handle 81000001 and flush the transient -## -a=0; while [ $a -lt 10 ]; do - if [ -x "${SWTPM_IOCTL}" ]; then - ${SWTPM_IOCTL} --tcp 127.0.0.1:2322 -i > /dev/null 2>&1 - else - tsspowerup > /dev/null 2>&1 + ## + # This powers on the tpm and starts it + # then we derive the RSA version of the storage seed and + # store it permanently at handle 81000001 and flush the transient + ## + a=0 + while [ $a -lt 10 ]; do + tsspowerup > /dev/null 2>&1 + if [ $? -eq 0 ]; then + break; + fi + sleep 1 + a=$[$a+1] + done + if [ $a -eq 10 ]; then + echo "Waited 10s for tpm_server to come up; exiting" + exit 1 fi - if [ $? -eq 0 ]; then - break; - fi - sleep 1 - a=$[$a+1] -done -if [ $a -eq 10 ]; then - echo "Waited 10s for tpm_server to come up; exiting" - exit 1 -fi -tssstartup || exit 1 -echo -n $pid + ${TSSSTARTUP} || exit 1 + echo -n $pid +fi From 24b3a5a5794db4bb69b38a1df099d5e59cccf2b3 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 6 Oct 2023 10:57:12 +0200 Subject: [PATCH 41/55] sm: Support more HMAC algos in the pkcs#12 parser. * sm/minip12.c (oid_hmacWithSHA1): New. Also for the SHA-2 algos. (digest_algo_from_oid): New. (set_key_iv_pbes2): Add arg digest_algo. (crypt_block): Ditto. (decrypt_block): Ditto. (parse_bag_encrypted_data): Parse the optional prf part and get the hmac algorithm. (parse_shrouded_key_bag): Ditto. (p12_build): Pass SHA1 for digest_algo. * sm/t-minip12.c (run_one_test): Print failed values in verbose mode. * tests/cms/samplekeys/nistp256-openssl-self-signed.p12: New. * tests/cms/samplekeys/Description-p12: Add this one. * tests/cms/Makefile.am (EXTRA_DIST): Ditto. -- This supports the modern algorithms, i.e. using SHA256 for the KDF which is the default in openssl unless the -legacy option is used. GnuPG-bug-id: 6536 --- sm/minip12.c | 131 ++++++++++++++++-- sm/t-minip12.c | 12 +- tests/cms/Makefile.am | 1 + tests/cms/samplekeys/Description-p12 | 6 + .../nistp256-openssl-self-signed.p12 | Bin 0 -> 1232 bytes 5 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 tests/cms/samplekeys/nistp256-openssl-self-signed.p12 diff --git a/sm/minip12.c b/sm/minip12.c index ed80534b6..e63d9a95d 100644 --- a/sm/minip12.c +++ b/sm/minip12.c @@ -83,6 +83,17 @@ static unsigned char const oid_aes128_CBC[9] = { static unsigned char const oid_aes256_CBC[9] = { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2A }; +static unsigned char const oid_hmacWithSHA1[8] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x07 }; +static unsigned char const oid_hmacWithSHA224[8] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x08 }; +static unsigned char const oid_hmacWithSHA256[8] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x09 }; +static unsigned char const oid_hmacWithSHA384[8] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x0A }; +static unsigned char const oid_hmacWithSHA512[8] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x0B }; + static unsigned char const oid_rsaEncryption[9] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }; static unsigned char const oid_pcPublicKey[7] = { @@ -241,6 +252,32 @@ dump_tag_info (const char *text, struct tag_info *ti) } +static int +digest_algo_from_oid (unsigned char const *oid, size_t oidlen) +{ + int algo; + + if (oidlen == DIM(oid_hmacWithSHA1) && + !memcmp (oid, oid_hmacWithSHA1, oidlen)) + algo = GCRY_MD_SHA1; + else if (oidlen == DIM(oid_hmacWithSHA224) && + !memcmp (oid, oid_hmacWithSHA224, oidlen)) + algo = GCRY_MD_SHA224; + else if (oidlen == DIM(oid_hmacWithSHA256) && + !memcmp (oid, oid_hmacWithSHA256, oidlen)) + algo = GCRY_MD_SHA256; + else if (oidlen == DIM(oid_hmacWithSHA384) && + !memcmp (oid, oid_hmacWithSHA384, oidlen)) + algo = GCRY_MD_SHA384; + else if (oidlen == DIM(oid_hmacWithSHA512) && + !memcmp (oid, oid_hmacWithSHA512, oidlen)) + algo = GCRY_MD_SHA512; + else + algo = 0; + return algo; +} + + /* Wrapper around tlv_builder_add_ptr to add an OID. When we * eventually put the whole tlv_builder stuff into Libksba, we can add * such a function there. Right now we don't do this to avoid a @@ -1029,13 +1066,14 @@ set_key_iv (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter, static int set_key_iv_pbes2 (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter, - const void *iv, size_t ivlen, const char *pw, int algo) + const void *iv, size_t ivlen, const char *pw, + int cipher_algo, int digest_algo) { unsigned char *keybuf; size_t keylen; int rc; - keylen = gcry_cipher_get_algo_keylen (algo); + keylen = gcry_cipher_get_algo_keylen (cipher_algo); if (!keylen) return -1; keybuf = gcry_malloc_secure (keylen); @@ -1043,7 +1081,7 @@ set_key_iv_pbes2 (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter, return -1; rc = gcry_kdf_derive (pw, strlen (pw), - GCRY_KDF_PBKDF2, GCRY_MD_SHA1, + GCRY_KDF_PBKDF2, digest_algo, salt, saltlen, iter, keylen, keybuf); if (rc) { @@ -1074,7 +1112,7 @@ set_key_iv_pbes2 (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter, static void crypt_block (unsigned char *buffer, size_t length, char *salt, size_t saltlen, int iter, const void *iv, size_t ivlen, - const char *pw, int cipher_algo, int encrypt) + const char *pw, int cipher_algo, int digest_algo, int encrypt) { gcry_cipher_hd_t chd; int rc; @@ -1088,7 +1126,8 @@ crypt_block (unsigned char *buffer, size_t length, char *salt, size_t saltlen, } if ((cipher_algo == GCRY_CIPHER_AES128 || cipher_algo == GCRY_CIPHER_AES256) - ? set_key_iv_pbes2 (chd, salt, saltlen, iter, iv, ivlen, pw, cipher_algo) + ? set_key_iv_pbes2 (chd, salt, saltlen, iter, iv, ivlen, pw, + cipher_algo, digest_algo) : set_key_iv (chd, salt, saltlen, iter, pw, cipher_algo == GCRY_CIPHER_RFC2268_40? 5:24)) { @@ -1125,7 +1164,7 @@ static void decrypt_block (const void *ciphertext, unsigned char *plaintext, size_t length, char *salt, size_t saltlen, int iter, const void *iv, size_t ivlen, - const char *pw, int cipher_algo, + const char *pw, int cipher_algo, int digest_algo, int (*check_fnc) (const void *, size_t)) { static const char * const charsets[] = { @@ -1197,7 +1236,7 @@ decrypt_block (const void *ciphertext, unsigned char *plaintext, size_t length, } memcpy (plaintext, ciphertext, length); crypt_block (plaintext, length, salt, saltlen, iter, iv, ivlen, - convertedpw? convertedpw:pw, cipher_algo, 0); + convertedpw? convertedpw:pw, cipher_algo, digest_algo, 0); if (check_fnc (plaintext, length)) break; /* Decryption succeeded. */ } @@ -1257,6 +1296,7 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) int renewed_tlv = 0; int loopcount; unsigned int startlevel; + int digest_algo = GCRY_MD_SHA1; where = "bag.encryptedData"; if (opt_verbose) @@ -1326,6 +1366,8 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) /*FIXME: This code is duplicated in parse_shrouded_key_bag. */ if (is_pbes2) { + size_t parmlen; /* Remaining length of the parameter sequence. */ + where = "pkcs5PBES2-params"; if (tlv_next (tlv)) goto bailout; @@ -1352,11 +1394,13 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) goto bailout; if (tlv_expect_sequence (tlv)) goto bailout; + parmlen = tlv->ti.length; if (tlv_next (tlv)) goto bailout; if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) goto bailout; + parmlen -= tlv->ti.length + tlv->ti.nhdr; if (datalen < 8 || datalen > sizeof salt) { log_info ("bad length of salt (%zu)\n", datalen); @@ -1370,6 +1414,7 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) goto bailout; if ((err = tlv_expect_integer (tlv, &intval))) goto bailout; + parmlen -= tlv->ti.length + tlv->ti.nhdr; if (!intval) /* Not a valid iteration count. */ { err = gpg_error (GPG_ERR_INV_VALUE); @@ -1377,8 +1422,34 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) } iter = intval; - /* Note: We don't support the optional parameters but assume - that the algorithmIdentifier follows. */ + if (parmlen > 2) /* There is the optional prf. */ + { + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_sequence (tlv)) + goto bailout; + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_object_id (tlv, &oid, &oidlen)) + goto bailout; + digest_algo = digest_algo_from_oid (oid, oidlen); + if (!digest_algo) + { + gpgrt_log_printhex (oid, oidlen, "kdf digest algo:"); + err = gpg_error (GPG_ERR_DIGEST_ALGO); + goto bailout; + } + if (opt_verbose > 1) + log_debug ("kdf digest algo = %d\n", digest_algo); + + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_null (tlv)) + tlv_set_pending (tlv); /* NULL tag missing - ignore this. */ + } + else + digest_algo = GCRY_MD_SHA1; + if (tlv_next (tlv)) goto bailout; if (tlv_expect_sequence (tlv)) @@ -1468,6 +1539,7 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) iv, is_pbes2?16:0, ctx->password, is_pbes2 ? (is_aes256?GCRY_CIPHER_AES256:GCRY_CIPHER_AES128) : is_3des ? GCRY_CIPHER_3DES : GCRY_CIPHER_RFC2268_40, + digest_algo, bag_decrypted_data_p); /* We do not need the TLV anymore and allocated a new one. */ @@ -1778,6 +1850,7 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) unsigned char *plain = NULL; int is_pbes2 = 0; int is_aes256 = 0; + int digest_algo = GCRY_MD_SHA1; where = "shrouded_key_bag"; if (opt_verbose) @@ -1819,6 +1892,8 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) if (is_pbes2) { + size_t parmlen; /* Remaining length of the parameter sequence. */ + where = "shrouded_key_bag.pkcs5PBES2-params"; if (tlv_next (tlv)) goto bailout; @@ -1842,11 +1917,13 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) goto bailout; if (tlv_expect_sequence (tlv)) goto bailout; + parmlen = tlv->ti.length; if (tlv_next (tlv)) goto bailout; if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) goto bailout; + parmlen -= tlv->ti.length + tlv->ti.nhdr; if (datalen < 8 || datalen > sizeof salt) { log_info ("bad length of salt (%zu) for AES\n", datalen); @@ -1860,6 +1937,7 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) goto bailout; if ((err = tlv_expect_integer (tlv, &intval))) goto bailout; + parmlen -= tlv->ti.length + tlv->ti.nhdr; if (!intval) /* Not a valid iteration count. */ { err = gpg_error (GPG_ERR_INV_VALUE); @@ -1867,8 +1945,34 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) } iter = intval; - /* Note: We don't support the optional parameters but assume - that the algorithmIdentifier follows. */ + if (parmlen > 2) /* There is the optional prf. */ + { + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_sequence (tlv)) + goto bailout; + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_object_id (tlv, &oid, &oidlen)) + goto bailout; + digest_algo = digest_algo_from_oid (oid, oidlen); + if (!digest_algo) + { + gpgrt_log_printhex (oid, oidlen, "kdf digest algo:"); + err = gpg_error (GPG_ERR_DIGEST_ALGO); + goto bailout; + } + if (opt_verbose > 1) + log_debug ("kdf digest algo = %d\n", digest_algo); + + if (tlv_next (tlv)) + goto bailout; + if (tlv_expect_null (tlv)) + tlv_set_pending (tlv); /* NULL tag missing - ignore this. */ + } + else + digest_algo = GCRY_MD_SHA1; + if (tlv_next (tlv)) goto bailout; if (tlv_expect_sequence (tlv)) @@ -1954,6 +2058,7 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) iv, is_pbes2? 16:0, ctx->password, is_pbes2 ? (is_aes256?GCRY_CIPHER_AES256:GCRY_CIPHER_AES128) : GCRY_CIPHER_3DES, + digest_algo, bag_data_p); @@ -3468,7 +3573,7 @@ p12_build (gcry_mpi_t *kparms, const void *cert, size_t certlen, /* Encrypt it. */ gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); crypt_block (buffer, buflen, salt, 8, 2048, NULL, 0, pw, - GCRY_CIPHER_RFC2268_40, 1); + GCRY_CIPHER_RFC2268_40, GCRY_MD_SHA1, 1); /* Encode the encrypted stuff into a bag. */ seqlist[seqlistidx].buffer = build_cert_bag (buffer, buflen, salt, &n); @@ -3500,7 +3605,7 @@ p12_build (gcry_mpi_t *kparms, const void *cert, size_t certlen, /* Encrypt it. */ gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); crypt_block (buffer, buflen, salt, 8, 2048, NULL, 0, - pw, GCRY_CIPHER_3DES, 1); + pw, GCRY_CIPHER_3DES, GCRY_MD_SHA1, 1); /* Encode the encrypted stuff into a bag. */ if (cert && certlen) diff --git a/sm/t-minip12.c b/sm/t-minip12.c index de6b7e5cc..bf3177ea0 100644 --- a/sm/t-minip12.c +++ b/sm/t-minip12.c @@ -559,13 +559,21 @@ run_one_test (const char *name, const char *desc, const char *pass, else if (!certexpected && certstr) printresult ("FAIL: %s - no certs expected but got one\n", name); else if (certexpected && certstr && strcmp (certexpected, certstr)) - printresult ("FAIL: %s - certs not as expected\n", name); + { + printresult ("FAIL: %s - certs not as expected\n", name); + inf ("cert(exp)=%s", certexpected); + inf ("cert(got)=%s", certstr? certstr:"[null]"); + } else if (keyexpected && !resulthash) printresult ("FAIL: %s - expected key but got none\n", name); else if (!keyexpected && resulthash) printresult ("FAIL: %s - key not expected but got one\n", name); else if (keyexpected && resulthash && strcmp (keyexpected, resulthash)) - printresult ("FAIL: %s - keys not as expected\n", name); + { + printresult ("FAIL: %s - keys not as expected\n", name); + inf ("key(exp)=%s", keyexpected); + inf ("key(got)=%s", resulthash? resulthash:"[null]"); + } else { printresult ("PASS: %s\n", name); diff --git a/tests/cms/Makefile.am b/tests/cms/Makefile.am index d5d753902..b43fb1c91 100644 --- a/tests/cms/Makefile.am +++ b/tests/cms/Makefile.am @@ -100,6 +100,7 @@ EXTRA_DIST = $(XTESTS) $(KEYS) $(CERTS) $(TEST_FILES) \ samplekeys/t5793-openssl.pfx \ samplekeys/t5793-test.pfx \ samplekeys/edward.tester@demo.gnupg.com.p12 \ + samplekeys/nistp256-openssl-self-signed.p12 \ samplemsgs/pwri-sample.cbc.p7m \ samplemsgs/pwri-sample.cbc-2.p7m \ samplemsgs/pwri-sample.gcm.p7m \ diff --git a/tests/cms/samplekeys/Description-p12 b/tests/cms/samplekeys/Description-p12 index 6fbbd82cf..a73998fac 100644 --- a/tests/cms/samplekeys/Description-p12 +++ b/tests/cms/samplekeys/Description-p12 @@ -39,4 +39,10 @@ Pass: abc,123456 Cert: ff810b9281a43c394aa138e9c7fd4c0193216fa6 Key: 94c6d0b067370a8f2a09ae43cfe8d700bbd61e75 +Name: nistp256-openssl-self-signed.p12 +Desc: OpenSSL generated self-signed nistp256 key+cert +Pass: abc +Cert: 5cea0c5bf09ccd92535267c662fc098f6c81c27e +Key: 3cb2fba95d1976df69eb7aa8c65ac5354e15af32 + # eof # diff --git a/tests/cms/samplekeys/nistp256-openssl-self-signed.p12 b/tests/cms/samplekeys/nistp256-openssl-self-signed.p12 new file mode 100644 index 0000000000000000000000000000000000000000..9eeebdae335a133abb7e66166d2691d8999196a2 GIT binary patch literal 1232 zcmXqLVmZUa$ZXKW(!|E8)#lOmotKfFaX}MHF-sFmK2SK@pov)tA;q?!iCGvZ#LvXY z0Hin&GK>b{a1JZ4ftP_Mg3Dtd!@^-9y3zLRjF>_uCJqJz9ySh$ZA_f33Inm3|n4o`T_UqNf1#0qZXKuW{x%%a{Ly!6Xsfp>R#c^6Wrkz~w=Mln|8FOOc z!MHPXA6EQ%zOja7(b5p9&&M~;)_FNu=uKwLnUuS$PaiuuyC8L~k;WAkz7HRcdd^wr zZ==6^%6U`&b!i_PMZ5wmxq{{VaMI007dx~!=2@m|w?Q3RudG?8&)$jEsGLO$@i#k?c@aoL` zM{_1_eHA>h&u~v$)SkI#EUOl-wm8jp+EnE00fVlYw?(+hZpZojKX=Rh{d$306K5yS z6)*U*VCfT?)#tkt#w>bG;>JBmEN%CqQU{Dp1%_Pu5}bL%MI-S;Ucbd&i%%$HDk zKUsa&_N>O2C+zOtiM&!RGylZ{-Tn(t&w3;Yo2EWbpC-2Yb4HbD+w_20hkEkgG>IMR zZT~H$@hI_h_q5G7zH?OB16WP*@9y)!4Y9d5edOk!eBWdxOR|gtE(= z@0mY$bZ(tQFuQ~VyZG%D{JwMc!J-G;Vk=l!8YkS8Ja|s4`A7_FW8twQJ0%3`Hg;Z9 zdHrh6J%&kqc@Kp=R+lkIDXkXKt}0m@)YSFOv*zowyMM(R1b(kyeab%chOy~GQ-y1( zd!t?l#RNy*XFsBOwmItQI-MfLbszTqR`_TX>QG}R@#na~k?9ZRyM=$trw8n1TX|&a zZox Date: Fri, 6 Oct 2023 12:04:00 +0200 Subject: [PATCH 42/55] scd:openpgp: Return better error codes for the Reset Code. * scd/app-openpgp.c (do_change_pin): Use GPG_ERR_BAD_RESET_CODE where appropriate. * common/util.h: Add error codes missing in gpgrt 1.46. * agent/call-pinentry.c (unlock_pinentry): Handle GPG_ERR_BAD_RESET_CODE. (agent_askpin): Ditlo. Also simply condition. (agent_get_passphrase): Ditto. * g10/call-agent.c (status_sc_op_failure): Handle GPG_ERR_BAD_RESET_CODE. * g10/card-util.c (write_sc_op_status): Ditto. * tools/card-call-scd.c (status_sc_op_failure): Ditto. --- agent/call-pinentry.c | 15 +++++++++------ common/util.h | 5 +++++ g10/call-agent.c | 1 + g10/card-util.c | 1 + scd/app-openpgp.c | 6 +++--- tools/card-call-scd.c | 1 + 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index 656d5f623..d236e1107 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -174,6 +174,7 @@ unlock_pinentry (ctrl_t ctrl, gpg_error_t rc) case GPG_ERR_NO_PASSPHRASE: case GPG_ERR_BAD_PASSPHRASE: case GPG_ERR_BAD_PIN: + case GPG_ERR_BAD_RESET_CODE: break; case GPG_ERR_CORRUPTED_PROTECTION: @@ -1621,12 +1622,13 @@ agent_askpin (ctrl_t ctrl, && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) return unlock_pinentry (ctrl, rc); - if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) + if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE + || gpg_err_code (rc) == GPG_ERR_BAD_PIN + || gpg_err_code (rc) == GPG_ERR_BAD_RESET_CODE) { 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) + else errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); } else if (rc) @@ -1894,12 +1896,13 @@ agent_get_passphrase (ctrl_t ctrl, if (rc && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) return unlock_pinentry (ctrl, rc); - if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) + if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE + || gpg_err_code (rc) == GPG_ERR_BAD_PIN + || gpg_err_code (rc) == GPG_ERR_BAD_RESET_CODE) { 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) + else errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); } else if (rc) diff --git a/common/util.h b/common/util.h index 83882caf2..875969187 100644 --- a/common/util.h +++ b/common/util.h @@ -39,6 +39,11 @@ * libgpg-error version. Define them here. * Example: (#if GPG_ERROR_VERSION_NUMBER < 0x011500 // 1.21) */ +#if GPG_ERROR_VERSION_NUMBER < 0x012f00 /* 1.47 */ +# define GPG_ERR_BAD_PUK 320 +# define GPG_ERR_NO_RESET_CODE 321 +# define GPG_ERR_BAD_RESET_CODE 322 +#endif #ifndef EXTERN_UNLESS_MAIN_MODULE # if !defined (INCLUDED_BY_MAIN_MODULE) diff --git a/g10/call-agent.c b/g10/call-agent.c index b0bccc0a5..eb9f8e29b 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -130,6 +130,7 @@ status_sc_op_failure (int rc) write_status_text (STATUS_SC_OP_FAILURE, "1"); break; case GPG_ERR_BAD_PIN: + case GPG_ERR_BAD_RESET_CODE: write_status_text (STATUS_SC_OP_FAILURE, "2"); break; default: diff --git a/g10/card-util.c b/g10/card-util.c index d680c4d0a..b83472285 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -62,6 +62,7 @@ write_sc_op_status (gpg_error_t err) write_status_text (STATUS_SC_OP_FAILURE, "1"); break; case GPG_ERR_BAD_PIN: + case GPG_ERR_BAD_RESET_CODE: write_status_text (STATUS_SC_OP_FAILURE, "2"); break; default: diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 66ec9f4a9..fd9ce554c 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -3453,7 +3453,7 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, if (!remaining) { log_error (_("Reset Code not or not anymore available\n")); - rc = gpg_error (GPG_ERR_BAD_PIN); + rc = gpg_error (GPG_ERR_NO_RESET_CODE); goto leave; } @@ -3470,7 +3470,7 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, { log_info (_("Reset Code is too short; minimum length is %d\n"), minlen); - rc = gpg_error (GPG_ERR_BAD_PIN); + rc = gpg_error (GPG_ERR_BAD_RESET_CODE); goto leave; } } @@ -3538,7 +3538,7 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, if (bufferlen != 0 && bufferlen < 8) { log_error (_("Reset Code is too short; minimum length is %d\n"), 8); - rc = gpg_error (GPG_ERR_BAD_PIN); + rc = gpg_error (GPG_ERR_BAD_RESET_CODE); } else { diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 27d8ad961..98d3ddeb7 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -235,6 +235,7 @@ status_sc_op_failure (gpg_error_t err) gnupg_status_printf (STATUS_SC_OP_FAILURE, "1"); break; case GPG_ERR_BAD_PIN: + case GPG_ERR_BAD_RESET_CODE: gnupg_status_printf (STATUS_SC_OP_FAILURE, "2"); break; default: From 5601f5db9862e23140bccc0e603e42164fe02296 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 10 Oct 2023 11:36:26 +0200 Subject: [PATCH 43/55] gpgsm: Improvements for NDEF in the pkcs#12 parser * sm/minip12.c (_tlv_push): Handle NDEF more correctly. (tlv_expect_octet_string): Do not bail out on NDEF. (dump_tag_info): Print some more infos. -- We do not have a complete test case for this. We need to further analyze T6752 to see what Mozilla is doing here. In any case with this patch we get a bit further and don't bail out at the ndef. GnuPG-bug-id: 6536, 6752 --- sm/minip12.c | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/sm/minip12.c b/sm/minip12.c index e63d9a95d..3705f3770 100644 --- a/sm/minip12.c +++ b/sm/minip12.c @@ -241,14 +241,21 @@ p12_set_verbosity (int verbose, int debug) static void -dump_tag_info (const char *text, struct tag_info *ti) +dump_tag_info (const char *text, struct tlv_ctx_s *tlv) { - if (opt_verbose > 1) - log_debug ("p12_parse(%s): ti.class=%d tag=%lu len=%zu nhdr=%zu %s%s\n", - text, - ti->class, ti->tag, ti->length, ti->nhdr, - ti->is_constructed?" cons":"", - ti->ndef?" ndef":""); + struct tag_info *ti; + + if (opt_verbose < 2) + return; + + ti = &tlv->ti; + log_debug ("p12_parse(%s): ti.class=%-2d tag=%-2lu len=%-4zu nhdr=%zu %s %s" + " (%u:%zu.%zu)\n", + text, + ti->class, ti->tag, ti->length, ti->nhdr, + ti->is_constructed?"cons":" ", + ti->ndef?"ndef":" ", + tlv->stacklen, tlv->bufsize, tlv->offset); } @@ -473,7 +480,13 @@ _tlv_push (struct tlv_ctx_s *tlv) tlv->stack[tlv->stacklen].in_ndef = tlv->in_ndef; tlv->stacklen++; tlv->buffer += tlv->offset; - tlv->bufsize = tlv->ti.length; + if (tlv->ti.ndef) + { + log_assert (tlv->bufsize >= tlv->offset); + tlv->bufsize -= tlv->offset; + } + else + tlv->bufsize = tlv->ti.length; tlv->offset = 0; tlv->in_ndef = tlv->ti.ndef; return 0; @@ -551,7 +564,7 @@ tlv_next (struct tlv_ctx_s *tlv) /* Set offset to the value of the TLV. */ tlv->offset += tlv->bufsize - tlv->offset - n; - dump_tag_info ("tlv_next", &tlv->ti); + dump_tag_info ("tlv_next", tlv); return 0; } @@ -706,7 +719,7 @@ tlv_expect_octet_string (struct tlv_ctx_s *tlv, int encapsulates, && (!tlv->ti.is_constructed || encapsulates))) return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); p = tlv->buffer + tlv->offset; - if (!(n=tlv->ti.length)) + if (!(n=tlv->ti.length) && !tlv->ti.ndef) return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); if (encapsulates && tlv->ti.is_constructed From a17363e992943244987dbab754b112c77d938b5d Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sat, 14 Oct 2023 17:06:51 +0200 Subject: [PATCH 44/55] common: New function scan_secondsstr. * common/gettime.c (scan_secondsstr): New. * common/t-gettime.c (test_scan_secondsstr): (main): Call it. --- common/gettime.c | 23 ++++++++++++++++++++ common/gettime.h | 1 + common/stringhelp.c | 2 +- common/t-gettime.c | 52 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/common/gettime.c b/common/gettime.c index 2a9b71779..c3b0c6c6c 100644 --- a/common/gettime.c +++ b/common/gettime.c @@ -37,6 +37,7 @@ #ifdef HAVE_LANGINFO_H #include #endif +#include /* We use uint64_t. */ #include "util.h" #include "i18n.h" @@ -172,6 +173,28 @@ make_timestamp (void) } +/* Specialized version of atoi which returns an u32 instead of an int + * and caps the result at 2^32-2. Leading white space is skipped, + * scanning stops at at the first non-convertable byte. Note that we + * do not cap at 2^32-1 because that value is often used as error + * return. */ +u32 +scan_secondsstr (const char *string) +{ + uint64_t value = 0; + + while (*string == ' ' || *string == '\t') + string++; + for (; *string >= '0' && *string <= '9'; string++) + { + value *= 10; + value += atoi_1 (string); + if (value >= (u32)(-1)) + return (u32)(-1) - 1; + } + return (u32)value; +} + /**************** * Scan a date string and return a timestamp. diff --git a/common/gettime.h b/common/gettime.h index 4f7199f92..18f65ab1a 100644 --- a/common/gettime.h +++ b/common/gettime.h @@ -51,6 +51,7 @@ int gnupg_faked_time_p (void); u32 make_timestamp (void); char *elapsed_time_string (time_t since, time_t now); +u32 scan_secondsstr (const char *string); u32 scan_isodatestr (const char *string); int isotime_p (const char *string); int isotime_human_p (const char *string, int date_only); diff --git a/common/stringhelp.c b/common/stringhelp.c index 5407653de..9a2265258 100644 --- a/common/stringhelp.c +++ b/common/stringhelp.c @@ -725,7 +725,7 @@ compare_filenames (const char *a, const char *b) /* Convert a base-10 number in STRING into a 64 bit unsigned int * value. Leading white spaces are skipped but no error checking is - * done. Thus it is similar to atoi(). */ + * done. Thus it is similar to atoi(). See also scan_secondsstr. */ uint64_t string_to_u64 (const char *string) { diff --git a/common/t-gettime.c b/common/t-gettime.c index 13cb1a2f7..76c305204 100644 --- a/common/t-gettime.c +++ b/common/t-gettime.c @@ -43,6 +43,56 @@ static int errcount; #define INVALID ((time_t)(-1)) +static void +test_scan_secondsstr (void) +{ + struct { const char *string; u32 expected; } array [] = { + { "", 0 }, + { "0", 0 }, + { " 0", 0 }, + { " 0x", 0 }, + { " 1", 1 }, + { "-1", 0 }, + { " -1", 0 }, + { "2", 2 }, + { "11", 11 }, + { "011", 11 }, + { "3600 ", 3600 }, + { "65535", 65535 }, + { "65536", 65536 }, + { "65537", 65537 }, + { "4294967289", 4294967289 }, + { "4294967290", 4294967290 }, + { "4294967293", 4294967293 }, + { "4294967295", 4294967294 }, + { "4294967296", 4294967294 }, + { "4294967297", 4294967294 }, + { "4294967298", 4294967294 }, + { "4294967299", 4294967294 }, + { "4294967300", 4294967294 }, + { "5294967300", 4294967294 }, + { "9999999999", 4294967294 }, + { "99999999999",4294967294 }, + { NULL, 0 } + }; + int idx; + u32 val; + + for (idx=0; array[idx].string; idx++) + { + val = scan_secondsstr (array[idx].string); + if (val != array[idx].expected ) + { + fail (idx); + if (verbose) + fprintf (stderr, "string '%s' exp: %ld got: %ld\n", + array[idx].string, (unsigned long)array[idx].expected, + (unsigned long)val); + } + } +} + + static void test_isotime2epoch (void) { @@ -103,7 +153,6 @@ test_isotime2epoch (void) } - static void test_string2isotime (void) { @@ -269,6 +318,7 @@ main (int argc, char **argv) if (argc > 1 && !strcmp (argv[1], "--verbose")) verbose = 1; + test_scan_secondsstr (); test_isotime2epoch (); test_string2isotime (); test_isodate_human_to_tm (); From 606933dfb48ddd3113bc60eb8b18126112b3b8a4 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sat, 14 Oct 2023 17:23:42 +0200 Subject: [PATCH 45/55] gpg: Allow to specify seconds since Epoch beyond 2038. * g10/keygen.c (parse_expire_string_with_ct): Use new function scan_secondsstr. (parse_creation_string): Ditto. -- Noet that we cap the seconds at the year 2106. GnuPG-bug-id: 6736 --- g10/keygen.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/g10/keygen.c b/g10/keygen.c index c252b0de4..06fc39aa1 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -2759,14 +2759,21 @@ parse_expire_string_with_ct (const char *string, u32 creation_time) || !strcmp (string, "never") || !strcmp (string, "-")) seconds = 0; else if (!strncmp (string, "seconds=", 8)) - seconds = atoi (string+8); + seconds = scan_secondsstr (string+8); else if ((abs_date = scan_isodatestr(string)) && (abs_date+86400/2) > curtime) seconds = (abs_date+86400/2) - curtime; else if ((tt = isotime2epoch (string)) != (time_t)(-1)) seconds = (u32)tt - curtime; else if ((mult = check_valid_days (string))) - seconds = atoi (string) * 86400L * mult; + { + uint64_t tmp64; + tmp64 = scan_secondsstr (string) * 86400L * mult; + if (tmp64 >= (u32)(-1)) + seconds = (u32)(-1) - 1; /* cap value. */ + else + seconds = (u32)tmp64; + } else seconds = (u32)(-1); @@ -2790,7 +2797,7 @@ parse_creation_string (const char *string) if (!*string) seconds = 0; else if ( !strncmp (string, "seconds=", 8) ) - seconds = atoi (string+8); + seconds = scan_secondsstr (string+8); else if ( !(seconds = scan_isodatestr (string))) { time_t tmp = isotime2epoch (string); From 956b1e1c26aa1c7b253096af50be3400ace43e4c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 16 Oct 2023 16:31:08 +0200 Subject: [PATCH 46/55] build: Extend autobuild diagnostics by the username * m4/autobuild.m4 (AB_INIT): Add username. -- The old autobuild diagnostics show up in build logs. What they are missing is an information on the user who triggered a build. EMAIL is a common thing to denote the actual user using a service account. --- m4/autobuild.m4 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/m4/autobuild.m4 b/m4/autobuild.m4 index ceed46494..20086ae9f 100644 --- a/m4/autobuild.m4 +++ b/m4/autobuild.m4 @@ -23,6 +23,9 @@ AC_DEFUN([AB_INIT], if test "$hostname"; then AC_MSG_NOTICE([autobuild hostname... $hostname]) fi + if test "$EMAIL"; then + AC_MSG_NOTICE([autobuild username... $EMAIL]) + fi ifelse([$1],[],,[AC_MSG_NOTICE([autobuild mode... $1])]) date=`date +%Y%m%d-%H%M%S` if test "$?" != 0; then From 873b2b0da1086f9c493527a46815f68f5dac1bcd Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 18 Oct 2023 15:43:22 +0200 Subject: [PATCH 47/55] doc: Minor typo fixes. -- --- sm/keylist.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sm/keylist.c b/sm/keylist.c index fabd82224..d6eccfc45 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -54,7 +54,7 @@ struct list_external_parm_s #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. */ +/* The extension can be printed as a hex string. */ #define OID_FLAG_HEX 4 /* Define if this specififies a key purpose. */ #define OID_FLAG_KP 8 @@ -208,6 +208,8 @@ static struct { "1.3.6.1.4.1.311.21.6", "ms-keyRecovery", OID_FLAG_KP }, { "1.3.6.1.4.1.311.21.19", "ms-dsEmailReplication", OID_FLAG_KP }, + /* BSI policies. */ + /* Other vendor extensions. */ { "1.3.6.1.4.1.30205.13.1.1", "trusted-disk", OID_FLAG_KP }, { "1.2.840.113583.1.1.5", "pdfAuthenticDocumentsTrust", OID_FLAG_KP }, From 7661d2fbc6eb533016df63a86ec3e35bf00cfb1f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 24 Oct 2023 09:22:13 +0200 Subject: [PATCH 48/55] sm: Another partly rewrite of minip12.c * sm/minip12.c (struct tlv_ctx_s): Add origbuffer and origbufsize. Remove pop_count. Rename offset to length. (dump_tag_info, _dump_tag_info): Rewrite. (dump_tlv_ctx, _dump_tlv_ctx): Rewrite. (tlv_new): Init origbuffer. (_tlv_peek): Add arg ti. (tlv_peek): New. (tlv_peek_null): New. (_tlv_push): Rewrite. (_tlv_pop): Rewrite. (tlv_next): New macro. Move old code to ... (_tlv_next): this. Add arg lno. Pop remaining end tags. (tlv_popped): Remove. (tlv_expect_object): Handle ndef. (tlv_expect_octet_string): Ditto. (parse_bag_encrypted_data): Use nesting level to control the inner loop. (parse_shrouded_key_bag): Likewise. (parse_bag_data): Handle surplus octet strings. (p12_parse): Ditto. * sm/minip12.c (decrypt_block): Strip the padding. (tlv_expect_top_sequence): Remove. Replace callers by tlv_expect_sequence. * tests/cms/samplekeys/t6752-ov-user-ff.p12: New sample key. * tests/cms/samplekeys/Description-p12: Add its description -- This patch improves the BER parser by simplifying it. Now tlv_next pops off and thus closes all containers regardless on whether they are length bounded or ndef. tlv_set_pending is now always used to undo the effect of a tlv_next in a loop condition which was terminated by a nesting level change. Instead of using the length as seen in the decrypted container we now remove the padding and let the BER parser do its work. This might have a negative effect on pkcs#12 objects which are not correctly padded but we don't have any example of such broken objects. GnuPG-bug-id: 6752 --- sm/minip12.c | 501 ++++++++++++++-------- tests/cms/samplekeys/Description-p12 | 6 + tests/cms/samplekeys/t6752-ov-user-ff.p12 | Bin 0 -> 2323 bytes 3 files changed, 325 insertions(+), 182 deletions(-) create mode 100644 tests/cms/samplekeys/t6752-ov-user-ff.p12 diff --git a/sm/minip12.c b/sm/minip12.c index 3705f3770..98eb4e3b5 100644 --- a/sm/minip12.c +++ b/sm/minip12.c @@ -174,11 +174,14 @@ struct bufferlist_s /* An object to control the ASN.1 parsing. */ struct tlv_ctx_s { + /* The orginal buffer with the entire pkcs#12 object and its length. */ + const unsigned char *origbuffer; + size_t origbufsize; + /* The current buffer we are working on and its length. */ const unsigned char *buffer; size_t bufsize; - size_t offset; /* The current offset into this buffer. */ int in_ndef; /* Flag indicating that we are in a NDEF. */ int pending; /* The last tlv_next has not yet been processed. */ @@ -186,15 +189,14 @@ struct tlv_ctx_s gpg_error_t lasterr; /* Last error from tlv function. */ const char *lastfunc;/* Name of last called function. */ - struct bufferlist_s *bufferlist; /* To keep track of amlloced buffers. */ + struct bufferlist_s *bufferlist; /* To keep track of malloced buffers. */ - unsigned int pop_count;/* Number of pops by tlv_next. */ unsigned int stacklen; /* Used size of the stack. */ struct { const unsigned char *buffer; /* Saved value of BUFFER. */ size_t bufsize; /* Saved value of BUFSIZE. */ - size_t offset; /* Saved value of OFFSET. */ - int in_ndef; /* Saved IN_NDEF flag. */ + size_t length; /* Length of the container (ti.length). */ + int in_ndef; /* Saved IN_NDEF flag (ti.ndef). */ } stack[TLV_MAX_DEPTH]; }; @@ -240,8 +242,9 @@ p12_set_verbosity (int verbose, int debug) } +#define dump_tag_info(a,b) _dump_tag_info ((a),__LINE__,(b)) static void -dump_tag_info (const char *text, struct tlv_ctx_s *tlv) +_dump_tag_info (const char *text, int lno, struct tlv_ctx_s *tlv) { struct tag_info *ti; @@ -249,13 +252,31 @@ dump_tag_info (const char *text, struct tlv_ctx_s *tlv) return; ti = &tlv->ti; - log_debug ("p12_parse(%s): ti.class=%-2d tag=%-2lu len=%-4zu nhdr=%zu %s %s" - " (%u:%zu.%zu)\n", - text, + + log_debug ("p12_parse:%s:%d: @%04zu class=%d tag=%lu len=%zu nhdr=%zu %s%s\n", + text, lno, + (size_t)(tlv->buffer - tlv->origbuffer) - ti->nhdr, ti->class, ti->tag, ti->length, ti->nhdr, - ti->is_constructed?"cons":" ", - ti->ndef?"ndef":" ", - tlv->stacklen, tlv->bufsize, tlv->offset); + ti->is_constructed?" cons":"", + ti->ndef?" ndef":""); +} + + +#define dump_tlv_ctx(a,b,c) _dump_tlv_ctx ((a),(b),__LINE__,(c)) +static void +_dump_tlv_ctx (const char *text, const char *text2, + int lno, struct tlv_ctx_s *tlv) +{ + if (opt_verbose < 2) + return; + + log_debug ("p12_parse:%s%s%s:%d: @%04zu lvl=%u %s\n", + text, + text2? "/":"", text2? text2:"", + lno, + (size_t)(tlv->buffer - tlv->origbuffer), + tlv->stacklen, + tlv->in_ndef? " in-ndef":""); } @@ -413,6 +434,8 @@ tlv_new (const unsigned char *buffer, size_t bufsize) tlv = xtrycalloc (1, sizeof *tlv); if (tlv) { + tlv->origbuffer = buffer; + tlv->origbufsize = bufsize; tlv->buffer = buffer; tlv->bufsize = bufsize; } @@ -454,17 +477,45 @@ tlv_release (struct tlv_ctx_s *tlv) } -/* Helper for tlv_next and tlv_peek. */ +/* Helper for the tlv_peek functions. */ static gpg_error_t -_tlv_peek (struct tlv_ctx_s *tlv, size_t *r_n) +_tlv_peek (struct tlv_ctx_s *tlv, struct tag_info *ti) { const unsigned char *p; + size_t n; - if (tlv->offset > tlv->bufsize) + /* Note that we want to peek ahead of any current container but of + * course not beyond our entire buffer. */ + p = tlv->buffer; + if ((p - tlv->origbuffer) > tlv->origbufsize) return gpg_error (GPG_ERR_BUG); - p = tlv->buffer + tlv->offset; - *r_n = tlv->bufsize - tlv->offset; - return parse_tag (&p, r_n, &tlv->ti); + n = tlv->origbufsize - (p - tlv->origbuffer); + return parse_tag (&p, &n, ti); +} + + +/* Look for the next tag and return true if it matches CLASS and TAG. + * Otherwise return false. No state is changed. */ +static int +tlv_peek (struct tlv_ctx_s *tlv, int class, int tag) +{ + struct tag_info ti; + + return (!_tlv_peek (tlv, &ti) + && ti.class == class && ti.tag == tag); +} + + +/* Look for the next tag and return true if it is the Null tag. + * Otherwise return false. No state is changed. */ +static int +tlv_peek_null (struct tlv_ctx_s *tlv) +{ + struct tag_info ti; + + return (!_tlv_peek (tlv, &ti) + && ti.class == CLASS_UNIVERSAL && ti.tag == TAG_NULL + && !ti.is_constructed && !ti.length); } @@ -472,23 +523,30 @@ _tlv_peek (struct tlv_ctx_s *tlv, size_t *r_n) static gpg_error_t _tlv_push (struct tlv_ctx_s *tlv) { + /* Right now our pointer is at the value of the current container. + * We push that info onto the stack. */ if (tlv->stacklen >= TLV_MAX_DEPTH) return (tlv->lasterr = gpg_error (GPG_ERR_TOO_MANY)); tlv->stack[tlv->stacklen].buffer = tlv->buffer; tlv->stack[tlv->stacklen].bufsize = tlv->bufsize; - tlv->stack[tlv->stacklen].offset = tlv->offset; tlv->stack[tlv->stacklen].in_ndef = tlv->in_ndef; + tlv->stack[tlv->stacklen].length = tlv->ti.length; tlv->stacklen++; - tlv->buffer += tlv->offset; - if (tlv->ti.ndef) + + tlv->in_ndef = tlv->ti.ndef; + + /* We set the size of the buffer to the TLV length if it is known or + * else to the size of the remaining entire buffer. */ + if (tlv->in_ndef) { - log_assert (tlv->bufsize >= tlv->offset); - tlv->bufsize -= tlv->offset; + if ((tlv->buffer - tlv->origbuffer) > tlv->origbufsize) + return (tlv->lasterr = gpg_error (GPG_ERR_BUG)); + tlv->bufsize = tlv->origbufsize - (tlv->buffer - tlv->origbuffer); } else tlv->bufsize = tlv->ti.length; - tlv->offset = 0; - tlv->in_ndef = tlv->ti.ndef; + + dump_tlv_ctx (__func__, NULL, tlv); return 0; } @@ -497,74 +555,102 @@ _tlv_push (struct tlv_ctx_s *tlv) static gpg_error_t _tlv_pop (struct tlv_ctx_s *tlv) { - size_t saveoff; + size_t lastlen; + /* We reached the end of a container, either due to the size limit + * or due to an end tag. Now we pop the last container so that we + * are positioned at the value of the last container. */ if (!tlv->stacklen) return gpg_error (GPG_ERR_EOF); - saveoff = tlv->offset; - tlv->stacklen--; - tlv->buffer = tlv->stack[tlv->stacklen].buffer; - tlv->bufsize = tlv->stack[tlv->stacklen].bufsize; - tlv->offset = tlv->stack[tlv->stacklen].offset; tlv->in_ndef = tlv->stack[tlv->stacklen].in_ndef; + if (tlv->in_ndef) + { + /* We keep buffer but adjust bufsize to the end of the origbuffer. */ + if ((tlv->buffer - tlv->origbuffer) > tlv->origbufsize) + return (tlv->lasterr = gpg_error (GPG_ERR_BUG)); + tlv->bufsize = tlv->origbufsize - (tlv->buffer - tlv->origbuffer); + } + else + { + lastlen = tlv->stack[tlv->stacklen].length; + tlv->buffer = tlv->stack[tlv->stacklen].buffer; + tlv->bufsize = tlv->stack[tlv->stacklen].bufsize; + if (lastlen > tlv->bufsize) + { + log_debug ("%s: container length larger than buffer (%zu/%zu)\n", + __func__, lastlen, tlv->bufsize); + return gpg_error (GPG_ERR_INV_BER); + } + tlv->buffer += lastlen; + tlv->bufsize -= lastlen; + } - /* Move offset of the container to the end of the container. */ - tlv->offset += saveoff; - if (tlv->offset > tlv->bufsize) - return gpg_error (GPG_ERR_INV_BER); - - tlv->pop_count++; + dump_tlv_ctx (__func__, NULL, tlv); return 0; } -/* Parse the next tag and value. Also detect the end of a container; - * tlv_popped() can be used to detect this. */ +/* Parse the next tag and value. Also detect the end of a + * container. */ +#define tlv_next(a) _tlv_next ((a), __LINE__) static gpg_error_t -tlv_next (struct tlv_ctx_s *tlv) +_tlv_next (struct tlv_ctx_s *tlv, int lno) { gpg_error_t err; - size_t n; - tlv->pop_count = 0; tlv->lasterr = 0; tlv->lastfunc = __func__; + if (tlv->pending) { tlv->pending = 0; + if (opt_verbose > 1) + log_debug ("%s: tlv_next skipped\n", __func__); return 0; } - if (!tlv->in_ndef && tlv->offset == tlv->bufsize) + if (opt_verbose > 1) + log_debug ("%s: tlv_next called\n", __func__); + /* If we are at the end of an ndef container pop the stack. */ + if (!tlv->in_ndef && !tlv->bufsize) { - /* We are at the end of a container. Pop the stack. */ do err = _tlv_pop (tlv); - while (!err && !tlv->in_ndef && tlv->offset == tlv->bufsize); + while (!err && !tlv->in_ndef && !tlv->bufsize); if (err) return (tlv->lasterr = err); + if (opt_verbose > 1) + log_debug ("%s: container(s) closed due to size\n", __func__); } - err = _tlv_peek (tlv, &n); + again: + /* Get the next tag. */ + err = parse_tag (&tlv->buffer, &tlv->bufsize, &tlv->ti); if (err) - return err; + { + if (opt_verbose > 1) + log_debug ("%s: reading tag returned err=%d\n", __func__, err); + return err; + } + + /* If there is an end tag in an ndef container pop the stack. Also + * pop other containers which are fully consumed. */ if (tlv->in_ndef && (tlv->ti.class == CLASS_UNIVERSAL && !tlv->ti.tag && !tlv->ti.is_constructed)) { - /* End tag while in ndef container. Skip the tag, and pop. */ - tlv->offset += n - (tlv->bufsize - tlv->offset); - err = _tlv_pop (tlv); - /* FIXME: We need to peek whether there is another end tag and - * pop again. We can't modify the TLV object, though. */ + do + err = _tlv_pop (tlv); + while (!err && !tlv->in_ndef && !tlv->bufsize); if (err) return (tlv->lasterr = err); + if (opt_verbose > 1) + log_debug ("%s: container(s) closed due to end tag\n", __func__); + goto again; } - /* Set offset to the value of the TLV. */ - tlv->offset += tlv->bufsize - tlv->offset - n; - dump_tag_info ("tlv_next", tlv); + _dump_tag_info (__func__, lno, tlv); return 0; } @@ -577,15 +663,6 @@ tlv_level (struct tlv_ctx_s *tlv) } -/* If called right after tlv_next the number of container levels - * popped are returned. */ -static unsigned int -tlv_popped (struct tlv_ctx_s *tlv) -{ - return tlv->pop_count; -} - - /* Set a flag to indicate that the last tlv_next has not yet been * consumed. */ static void @@ -595,12 +672,15 @@ tlv_set_pending (struct tlv_ctx_s *tlv) } -/* Skip over the value of the current tag. */ +/* Skip over the value of the current tag. Does not yet work for ndef + * containers. */ static void tlv_skip (struct tlv_ctx_s *tlv) { tlv->lastfunc = __func__; - tlv->offset += tlv->ti.length; + log_assert (tlv->bufsize >= tlv->ti.length); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; } @@ -616,19 +696,6 @@ tlv_expect_sequence (struct tlv_ctx_s *tlv) return _tlv_push (tlv); } -/* Variant of tlv_expect_sequence to be used for the outer sequence - * of an object which might have padding after the ASN.1 data. */ -static gpg_error_t -tlv_expect_top_sequence (struct tlv_ctx_s *tlv) -{ - tlv->lastfunc = __func__; - if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SEQUENCE - && tlv->ti.is_constructed)) - return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - tlv->bufsize = tlv->ti.nhdr + tlv->ti.length; - return _tlv_push (tlv); -} - /* Expect that the current tag is a context tag and setup the context * for processing. The tag of the context is returned at R_TAG. */ @@ -666,20 +733,28 @@ tlv_expect_object (struct tlv_ctx_s *tlv, int class, int tag, { gpg_error_t err; const unsigned char *p; + size_t n; + int needpush = 0; tlv->lastfunc = __func__; if (!(tlv->ti.class == class && tlv->ti.tag == tag)) return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - p = tlv->buffer + tlv->offset; - if (!tlv->ti.length) + p = tlv->buffer; + n = tlv->ti.length; + if (!n && tlv->ti.ndef) + { + n = tlv->bufsize; + needpush = 1; + } + else if (!tlv->ti.length) return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); if (class == CLASS_CONTEXT && tag == 0 && tlv->ti.is_constructed - && need_octet_string_cramming (p, tlv->ti.length)) + && need_octet_string_cramming (p, n)) { char *newbuffer; - newbuffer = cram_octet_string (p, tlv->ti.length, r_datalen); + newbuffer = cram_octet_string (p, n, r_datalen); if (!newbuffer) return (tlv->lasterr = gpg_error (GPG_ERR_BAD_BER)); err = tlv_register_buffer (tlv, newbuffer); @@ -693,10 +768,15 @@ tlv_expect_object (struct tlv_ctx_s *tlv, int class, int tag, else { *r_data = p; - *r_datalen = tlv->ti.length; + *r_datalen = n; } + if (needpush) + return _tlv_push (tlv); - tlv->offset += tlv->ti.length; + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; return 0; } @@ -718,7 +798,7 @@ tlv_expect_octet_string (struct tlv_ctx_s *tlv, int encapsulates, if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OCTET_STRING && (!tlv->ti.is_constructed || encapsulates))) return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - p = tlv->buffer + tlv->offset; + p = tlv->buffer; if (!(n=tlv->ti.length) && !tlv->ti.ndef) return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); @@ -748,19 +828,10 @@ tlv_expect_octet_string (struct tlv_ctx_s *tlv, int encapsulates, if (encapsulates) return _tlv_push (tlv); - tlv->offset += tlv->ti.length; - return 0; -} - - -/* Expect a NULL tag. */ -static gpg_error_t -tlv_expect_null (struct tlv_ctx_s *tlv) -{ - tlv->lastfunc = __func__; - if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_NULL - && !tlv->ti.is_constructed && !tlv->ti.length)) - return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; return 0; } @@ -778,7 +849,7 @@ tlv_expect_integer (struct tlv_ctx_s *tlv, int *r_value) if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER && !tlv->ti.is_constructed)) return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - p = tlv->buffer + tlv->offset; + p = tlv->buffer; if (!(n=tlv->ti.length)) return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); @@ -794,7 +865,10 @@ tlv_expect_integer (struct tlv_ctx_s *tlv, int *r_value) return (tlv->lasterr = gpg_error (GPG_ERR_EOVERFLOW)); } *r_value = value; - tlv->offset += tlv->ti.length; + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; return 0; } @@ -816,11 +890,14 @@ tlv_expect_mpinteger (struct tlv_ctx_s *tlv, int ignore_zero, if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER && !tlv->ti.is_constructed)) return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - p = tlv->buffer + tlv->offset; + p = tlv->buffer; if (!(n=tlv->ti.length)) return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); - tlv->offset += tlv->ti.length; + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; if (ignore_zero && n == 1 && !*p) return gpg_error (GPG_ERR_FALSE); @@ -842,13 +919,16 @@ tlv_expect_object_id (struct tlv_ctx_s *tlv, if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OBJECT_ID && !tlv->ti.is_constructed)) return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - p = tlv->buffer + tlv->offset; + p = tlv->buffer; if (!(n=tlv->ti.length)) return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); *r_oid = p; *r_oidlen = tlv->ti.length; - tlv->offset += tlv->ti.length; + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; return 0; } @@ -1171,9 +1251,9 @@ crypt_block (unsigned char *buffer, size_t length, char *salt, size_t saltlen, and CIPHER_ALGO is the algorithm id to use. CHECK_FNC is a function called with the plaintext and used to check whether the decryption succeeded; i.e. that a correct passphrase has been - given. That function shall return true if the decryption has likely - succeeded. */ -static void + given. The function returns the length of the unpadded plaintext + or 0 on error. */ +static size_t decrypt_block (const void *ciphertext, unsigned char *plaintext, size_t length, char *salt, size_t saltlen, int iter, const void *iv, size_t ivlen, @@ -1202,6 +1282,7 @@ decrypt_block (const void *ciphertext, unsigned char *plaintext, size_t length, int charsetidx = 0; char *convertedpw = NULL; /* Malloced and converted password or NULL. */ size_t convertedpwsize = 0; /* Allocated length. */ + size_t plainlen = 0; for (charsetidx=0; charsets[charsetidx]; charsetidx++) { @@ -1251,9 +1332,31 @@ decrypt_block (const void *ciphertext, unsigned char *plaintext, size_t length, crypt_block (plaintext, length, salt, saltlen, iter, iv, ivlen, convertedpw? convertedpw:pw, cipher_algo, digest_algo, 0); if (check_fnc (plaintext, length)) - break; /* Decryption succeeded. */ + { + /* Strip the pkcs#7 padding. */ + if (length) + { + int n, i; + + n = plaintext[length-1]; + if (n >= length || n > 16) + log_info ("decryption failed; invalid padding size\n"); + else + { + for (i=1; i < n; i++) + if (plaintext[length-i-1] != n) + break; + if (i < n) + log_info ("decryption failed; invalid padding octet\n"); + else + plainlen = length - n; + } + } + break; /* Decryption probably succeeded. */ + } } gcry_free (convertedpw); + return plainlen; } @@ -1308,7 +1411,7 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) int keyelem_count; int renewed_tlv = 0; int loopcount; - unsigned int startlevel; + unsigned int startlevel, startlevel2; int digest_algo = GCRY_MD_SHA1; where = "bag.encryptedData"; @@ -1455,10 +1558,12 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) if (opt_verbose > 1) log_debug ("kdf digest algo = %d\n", digest_algo); - if (tlv_next (tlv)) - goto bailout; - if (tlv_expect_null (tlv)) - tlv_set_pending (tlv); /* NULL tag missing - ignore this. */ + if (tlv_peek_null (tlv)) + { + /* Read the optional Null tag. */ + if (tlv_next (tlv)) + goto bailout; + } } else digest_algo = GCRY_MD_SHA1; @@ -1548,12 +1653,17 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) log_error ("error allocating decryption buffer\n"); goto bailout; } - decrypt_block (data, plain, datalen, salt, saltlen, iter, + datalen = decrypt_block (data, plain, datalen, salt, saltlen, iter, iv, is_pbes2?16:0, ctx->password, is_pbes2 ? (is_aes256?GCRY_CIPHER_AES256:GCRY_CIPHER_AES128) : is_3des ? GCRY_CIPHER_3DES : GCRY_CIPHER_RFC2268_40, digest_algo, bag_decrypted_data_p); + if (!datalen) + { + err = gpg_error (GPG_ERR_DECRYPT_FAILED); + goto bailout; + } /* We do not need the TLV anymore and allocated a new one. */ where = "bag.encryptedData.decrypted-text"; @@ -1570,7 +1680,7 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) ctx->badpass = 1; goto bailout; } - if (tlv_expect_top_sequence (tlv)) + if (tlv_expect_sequence (tlv)) { ctx->badpass = 1; goto bailout; @@ -1692,7 +1802,8 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) where = "reading.keybag.key-parameters"; keyelem_count = 0; - while (!(err = tlv_next (tlv)) && !tlv_popped (tlv)) + startlevel2 = tlv_level (tlv); + while (!(err = tlv_next (tlv)) && tlv_level (tlv) == startlevel2) { if (keyelem_count >= 9) { @@ -1714,7 +1825,9 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) log_debug ("RSA key parameter %d found\n", keyelem_count); keyelem_count++; } - if (err && gpg_err_code (err) != GPG_ERR_EOF) + if (!err) + tlv_set_pending (tlv); + else if (err && gpg_err_code (err) != GPG_ERR_EOF) goto bailout; err = 0; } @@ -1763,24 +1876,21 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) /* Skip the optional SET with the pkcs12 cert attributes. */ where = "bag.attribute_set"; - err = tlv_next (tlv); - if (gpg_err_code (err) == GPG_ERR_EOF) - break; - if (err) - goto bailout; - err = tlv_expect_set (tlv); - if (!err) - { /* This is the optional set of attributes. Skip it. */ + if (tlv_peek (tlv, CLASS_UNIVERSAL, TAG_SET)) + { + if (tlv_next (tlv)) + goto bailout; + err = tlv_expect_set (tlv); + if (err) + goto bailout; tlv_skip (tlv); if (opt_verbose) - log_info ("skipping bag.attribute_set\n"); + log_info ("skipping %s\n", where); } - else if (gpg_err_code (err) == GPG_ERR_INV_OBJ) - tlv_set_pending (tlv); /* The next tlv_next will be skipped. */ - else - goto bailout; } - if (err && gpg_err_code (err) != GPG_ERR_EOF) + if (!err) + tlv_set_pending (tlv); + else if (err && gpg_err_code (err) != GPG_ERR_EOF) { if (!loopcount) /* The first while(tlv_next) failed. */ ctx->badpass = 1; @@ -1804,10 +1914,9 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) bailout: if (!err) err = gpg_error (GPG_ERR_GENERAL); - log_error ("%s(%s): offset %u.%zu (%s): %s - %s\n", + log_error ("%s(%s): lvl=%u (%s): %s - %s\n", __func__, where, tlv? tlv->stacklen : 0, - tlv? tlv->offset : 0, tlv? tlv->lastfunc : "", tlv ? gpg_strerror (tlv->lasterr) : "init failed", gpg_strerror (err)); @@ -1978,10 +2087,12 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) if (opt_verbose > 1) log_debug ("kdf digest algo = %d\n", digest_algo); - if (tlv_next (tlv)) - goto bailout; - if (tlv_expect_null (tlv)) - tlv_set_pending (tlv); /* NULL tag missing - ignore this. */ + if (tlv_peek_null (tlv)) + { + /* Read the optional Null tag. */ + if (tlv_next (tlv)) + goto bailout; + } } else digest_algo = GCRY_MD_SHA1; @@ -2067,13 +2178,17 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) log_error ("error allocating decryption buffer\n"); goto bailout; } - decrypt_block (data, plain, datalen, salt, saltlen, iter, + datalen = decrypt_block (data, plain, datalen, salt, saltlen, iter, iv, is_pbes2? 16:0, ctx->password, is_pbes2 ? (is_aes256?GCRY_CIPHER_AES256:GCRY_CIPHER_AES128) : GCRY_CIPHER_3DES, digest_algo, bag_data_p); - + if (!datalen) + { + err = gpg_error (GPG_ERR_DECRYPT_FAILED); + goto bailout; + } /* We do not need the TLV anymore and allocated a new one. */ where = "shrouded_key_bag.decrypted-text"; @@ -2093,7 +2208,7 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) ctx->badpass = 1; goto bailout; } - if (tlv_expect_top_sequence (tlv)) + if (tlv_expect_sequence (tlv)) { ctx->badpass = 1; goto bailout; @@ -2130,10 +2245,13 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) { if (opt_verbose > 1) log_debug ("RSA parameters\n"); - if (tlv_next (tlv)) - goto bailout; - if (tlv_expect_null (tlv)) - tlv_set_pending (tlv); /* NULL tag missing - ignore this. */ + + if (tlv_peek_null (tlv)) + { + /* Read the optional Null tag. */ + if (tlv_next (tlv)) + goto bailout; + } } else if (oidlen == DIM(oid_pcPublicKey) && !memcmp (oid, oid_pcPublicKey, oidlen)) @@ -2218,8 +2336,9 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) { int keyelem_count = 0; int firstparam = 1; + unsigned int startlevel = tlv_level (tlv); - while (!(err = tlv_next (tlv)) && !tlv_popped (tlv)) + while (!(err = tlv_next (tlv)) && tlv_level (tlv) == startlevel) { if (keyelem_count >= 9) { @@ -2245,7 +2364,9 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) } firstparam = 0; } - if (err && gpg_err_code (err) != GPG_ERR_EOF) + if (!err) + tlv_set_pending (tlv); + else if (err && gpg_err_code (err) != GPG_ERR_EOF) goto bailout; err = 0; } @@ -2257,22 +2378,18 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) tlv = saved_tlv; where = "shrouded_key_bag.attribute_set"; - err = tlv_next (tlv); - if (gpg_err_code (err) == GPG_ERR_EOF) - goto leave; - if (err) - goto bailout; - err = tlv_expect_set (tlv); - if (!err) - { /* This is the optional set of attributes. Skip it. */ + /* Check for an optional set of attributes. */ + if (tlv_peek (tlv, CLASS_UNIVERSAL, TAG_SET)) + { + if (tlv_next (tlv)) + goto bailout; + err = tlv_expect_set (tlv); + if (err) + goto bailout; tlv_skip (tlv); if (opt_verbose) log_info ("skipping %s\n", where); } - else if (gpg_err_code (err) == GPG_ERR_INV_OBJ) - tlv_set_pending (tlv); /* The next tlv_next will be skipped. */ - else /* Other error. */ - goto bailout; leave: @@ -2288,10 +2405,9 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) bailout: if (!err) err = gpg_error (GPG_ERR_GENERAL); - log_error ("%s(%s): offset %u.%zu (%s): %s - %s\n", + log_error ("%s(%s): lvl=%d (%s): %s - %s\n", __func__, where, tlv? tlv->stacklen : 0, - tlv? tlv->offset : 0, tlv? tlv->lastfunc : "", tlv ? gpg_strerror (tlv->lasterr) : "init failed", gpg_strerror (err)); @@ -2367,28 +2483,27 @@ parse_cert_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) * SEQUENCE -- we actually ignore this. */ where = "certbag.attribute_set"; - if (tlv_next (tlv)) - goto bailout; - err = tlv_expect_set (tlv); - if (!err) - { /* This is the optional set of attributes. Skip it. */ + /* Check for an optional set of attributes. */ + if (tlv_peek (tlv, CLASS_UNIVERSAL, TAG_SET)) + { + if (tlv_next (tlv)) + goto bailout; + err = tlv_expect_set (tlv); + if (err) + goto bailout; tlv_skip (tlv); if (opt_verbose) - log_info ("skipping certbag.attribute_set\n"); + log_info ("skipping %s\n", where); } - else if (gpg_err_code (err) == GPG_ERR_INV_OBJ) - tlv_set_pending (tlv); /* The next tlv_next will be skipped. */ - else - goto bailout; + leave: return err; bailout: - log_error ("%s(%s): offset %u.%zu (%s): %s - %s\n", + log_error ("%s(%s): lvl=%u (%s): %s - %s\n", __func__, where, tlv? tlv->stacklen : 0, - tlv? tlv->offset : 0, tlv? tlv->lastfunc : "", tlv ? gpg_strerror (tlv->lasterr) : "init failed", gpg_strerror (err)); @@ -2426,6 +2541,14 @@ parse_bag_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) if (tlv_expect_octet_string (tlv, 1, NULL, NULL)) goto bailout; + if (tlv_peek (tlv, CLASS_UNIVERSAL, TAG_OCTET_STRING)) + { + if (tlv_next (tlv)) + goto bailout; + err = tlv_expect_octet_string (tlv, 1, NULL, NULL); + if (err) + goto bailout; + } /* Expect: * SEQUENCE @@ -2437,6 +2560,7 @@ parse_bag_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) goto bailout; startlevel = tlv_level (tlv); + dump_tlv_ctx ("data.outerseqs", "beginloop", tlv); while (!(err = tlv_next (tlv)) && tlv_level (tlv) == startlevel) { /* Expect: @@ -2475,11 +2599,12 @@ parse_bag_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) log_info ("unknown inner data type - skipped\n"); } } - if (err && gpg_err_code (err) != GPG_ERR_EOF) + dump_tlv_ctx ("data.outerseqs", "endloop", tlv); + if (!err) + tlv_set_pending (tlv); + else if (err && gpg_err_code (err) != GPG_ERR_EOF) goto bailout; err = 0; - if (tlv_popped (tlv)) - tlv_set_pending (tlv); leave: return err; @@ -2487,10 +2612,9 @@ parse_bag_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) bailout: if (!err) err = gpg_error (GPG_ERR_GENERAL); - log_error ("%s(%s): offset %u.%zu (%s): %s - %s\n", + log_error ("%s(%s): lvl=%d (%s): %s - %s\n", __func__, where, tlv? tlv->stacklen : 0, - tlv? tlv->offset : 0, tlv? tlv->lastfunc : "", tlv ? gpg_strerror (tlv->lasterr) : "init failed", gpg_strerror (err)); @@ -2567,6 +2691,14 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, if (tlv_expect_octet_string (tlv, 1, NULL, NULL)) goto bailout; + if (tlv_peek (tlv, CLASS_UNIVERSAL, TAG_OCTET_STRING)) + { + if (tlv_next (tlv)) + goto bailout; + err = tlv_expect_octet_string (tlv, 1, NULL, NULL); + if (err) + goto bailout; + } where = "bags"; if (tlv_next (tlv)) @@ -2575,9 +2707,11 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, goto bailout; startlevel = tlv_level (tlv); + dump_tlv_ctx ("bags", "beginloop", tlv); while (!(err = tlv_next (tlv)) && tlv_level (tlv) == startlevel) { where = "bag-sequence"; + dump_tlv_ctx (where, NULL, tlv); if (tlv_expect_sequence (tlv)) goto bailout; @@ -2614,7 +2748,10 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, log_info ("unknown outer bag type - skipped\n"); } } - if (err && gpg_err_code (err) != GPG_ERR_EOF) + dump_tlv_ctx ("bags", "endloop", tlv); + if (!err) + tlv_set_pending (tlv); + else if (err && gpg_err_code (err) != GPG_ERR_EOF) goto bailout; err = 0; @@ -2628,12 +2765,12 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, bailout: *r_badpass = ctx.badpass; - log_error ("%s(%s): offset %u.%zu (%s): %s - %s\n", + log_error ("%s(%s): @%04zu lvl=%u %s: %s - %s\n", __func__, where, + tlv? (size_t)(tlv->buffer - tlv->origbuffer):0, tlv? tlv->stacklen : 0, - tlv? tlv->offset : 0, tlv? tlv->lastfunc : "", - tlv ? gpg_strerror (tlv->lasterr) : "init failed", + tlv? gpg_strerror (tlv->lasterr) : "init failed", gpg_strerror (err)); if (ctx.privatekey) { diff --git a/tests/cms/samplekeys/Description-p12 b/tests/cms/samplekeys/Description-p12 index a73998fac..01276087f 100644 --- a/tests/cms/samplekeys/Description-p12 +++ b/tests/cms/samplekeys/Description-p12 @@ -45,4 +45,10 @@ Pass: abc Cert: 5cea0c5bf09ccd92535267c662fc098f6c81c27e Key: 3cb2fba95d1976df69eb7aa8c65ac5354e15af32 +Name: t6752-ov-user-ff.p12 +Desc: Mozilla generated with a surplus octet string container +Pass: start +Cert: 4753a910e0c8b4caa8663ca0e4273a884eb5397d +Key: 93be89edd11214ab74280d988a665b6beef876c5 + # eof # diff --git a/tests/cms/samplekeys/t6752-ov-user-ff.p12 b/tests/cms/samplekeys/t6752-ov-user-ff.p12 new file mode 100644 index 0000000000000000000000000000000000000000..153ffb00037a62d7ac72ff96c8a67225a34e2d54 GIT binary patch literal 2323 zcmZvcdpy&98^?b(#^y}KFdA~|Foh}ZoT8A391@|08_RK?CC!|bHe@tp~_jg z&HbJS(uDnF=g?P_FKujMkX-0Q2+TU*BVL^@|E0TR!+ee(v08)|?yX%`3>&XgLo!i$ z9WL*gFN&_JP}Wt^XWbYHWnHXasX(K`NM@Yu+HtNIfg@C)^o<$Am78Wti@&Wm6$tX4 z^0{hKaoPQ5vlNhNW-T1C9n51 z9L#bv&c9>`ljTVI9#+LT|52lb!FC05yHH2ROhRv0=vG!cx%p{OJ3By-TKcv5HJRZA zna#&Whcd`Ij4zqKS=owL#d9Z$hQt?HL7o7)_QrDav6Wo@g3_q82?OYyEv2U%pCZ(IeTUK!W zkp67C8cwAYULY@#3XMIHmj{>Bo}LH=4}09Edq0t{fr#1WU6JrWR~FyGIp^4Fll2v1 zhoORO&=(x*yoZ0=q|jM*L(=QQ4q1j^xTLE2ey1u;Jnn8ERj#pRq9(cZyPzRIPt|k` zzQ&Vbfh<2T_{<8ts2Fz?kIdCD3`N=eXX>Fl3{4P5fIV;# zhy)^lXaEO<0D(W=Kp+Ot0KEUsLV%#{N*EBZ-JtfNp_;G&52}0hc3nT>mtO@-OJ&=FWv$q1Q{3*nbIc`^VV%ULj<_A6G zZ9GMQGWQIoz@arYcwv@jz0%Fd_H7YeYBbjggWoE$+FgWZa-sxEuz8EZcILgDRgC2^ zS{G*?i)xO!AtdJXPNkin=1n)(wxnE92JK>|u8C9gK5XjB;LIWN zk@rNP(;sCNX%%l%6tq>A(S*p?EmF0Q5bY2VP^XZpY zC`x_C{I*7)=9P0de;4bshGLK@A{Cb`4&%>*ET&2W7;HqpCTrp|Wpk)LUo+G|+ZC;I zqtswjg!db45tur_D>W%9@VDB>eD$k~H8UgOv8L*4M)z?u0h zd`14r&=rd|`|Ui@?z%PN%$sGEnxP>Dn;gaw|mhz+^kHumt|L+;_PUTYKBzJp^G+5$r@cgNIw zNqbYb^d?96*5DjTlVo?QCt~d|!p!NxQSPxL;sP9{p^gKqD_CQ_o8%~EQUl9j;-2{- zB8wTLaF_O?PiLR)Rk-CHV7_ZMklOpa`c#uTfq;|HY-tlWy}$4A2lGjbh0I%7>%^H3 zO!zOGr}a7aCUBi*M`{x7(i+&(MS~IGq2Bc@*od4F3q_Of_?~#6$KKPpx&NNtZY8y+ zPnK(Pave*FsYkj(r_7)-vG zJYTF2?-Tea!1-XnZAo9va#g>op~I;TJnjo+o}uRc2BQS)j~ zO8utZ@9;pxb7-Nd@SIgnCro$HimQaNfVp-)AgsOTJFxx9P#3a^HZKa$dG}vMlf}*GRarr z^V7DS)c$eOKAf_n?-u)Q2!74reN%EmcJmamLW|@XrQstb%J`PY$I1BP>P7eIuzzd zud5+m6o5o|b~|x!n}HX++sqDs_zSh&{PFk~wKsyenq>tJIobAT&;aKw7>2K^A3$X0 zVh`itnGe|M(1OrAM>H6n#qLgv6C>mVf<4TLvfC?IaZ%QEk^?_OBUeA?IcAgm`+T@b zpSNxT4;3HvsO1luX_ewqg2_8(>{PP9FD|V~eO}0kv)!*KPg>He97^j!F2xRPeZPPw zRnSs2o?4glESFo|9+cm#2)~z^T$%daTdszEZp(_BITeOm?F&{ID>eJAdbCa`KV_U= zt%rv>#Hvdav&f?j76wgvkar|AFmWb=bYqU~uPcbAUN3%tQ!dunnlWy}=%nndh?#m= zwO3o$0d+KEL^|Zm+jovk0UdXLa6>ht8j?7&qA~-sx;@Tu8?JA7-jNYs9KWJ-b*aVN zMBevFIe*E^fe_nisjBbez&Ig*)K`5r;?hj0PD6rCm9bVq(P=2 zelEGbD2l~ftqORqE@|a-XXa+piM@EzL-1$`L0Bvol6T&zl$i0a?fUbLI*i(df Date: Tue, 24 Oct 2023 13:25:10 +0200 Subject: [PATCH 49/55] common: Provide API to parse BER/TLV encodings. * sm/minip12.c: Factor parsing code out to ... * common/tlv-parser.c: new. Extend function names and provide a few extra functions. * common/Makefile.am (common_sources): Add new file. * sm/minip12.c: Adjust to use the new parser API. --- common/Makefile.am | 2 +- common/tlv-parser.c | 788 ++++++++++++++++++++++++++++++++++++++++ common/tlv.h | 67 +++- sm/minip12.c | 860 ++++---------------------------------------- 4 files changed, 922 insertions(+), 795 deletions(-) create mode 100644 common/tlv-parser.c diff --git a/common/Makefile.am b/common/Makefile.am index 7c78708da..91718f99e 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -58,7 +58,7 @@ common_sources = \ openpgpdefs.h \ gc-opt-flags.h \ sexp-parse.h \ - tlv.c tlv.h tlv-builder.c \ + tlv.c tlv.h tlv-builder.c tlv-parser.c \ init.c init.h \ sexputil.c \ sysutils.c sysutils.h \ diff --git a/common/tlv-parser.c b/common/tlv-parser.c new file mode 100644 index 000000000..c9b33d4b6 --- /dev/null +++ b/common/tlv-parser.c @@ -0,0 +1,788 @@ +/* tlv-parser.c - Parse BER encoded objects + * Copyright (C) 2023 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 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 . + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include + +#include +#include +#include +#include + +#include "util.h" +#include "tlv.h" + + +#define TLV_MAX_DEPTH 25 + + +struct bufferlist_s +{ + struct bufferlist_s *next; + char *buffer; +}; + + +/* An object to control the ASN.1 parsing. */ +struct tlv_parser_s +{ + /* The orginal buffer with the entire pkcs#12 object and its length. */ + const unsigned char *origbuffer; + size_t origbufsize; + + /* The current buffer we are working on and its length. */ + const unsigned char *buffer; + size_t bufsize; + + int in_ndef; /* Flag indicating that we are in a NDEF. */ + int pending; /* The last tlv_next has not yet been processed. */ + + struct tag_info ti; /* The current tag. */ + gpg_error_t lasterr; /* Last error from tlv function. */ + const char *lastfunc;/* Name of last called function. */ + int verbosity; /* Arg from tlv_parser_new. */ + + struct bufferlist_s *bufferlist; /* To keep track of malloced buffers. */ + + unsigned int stacklen; /* Used size of the stack. */ + struct { + const unsigned char *buffer; /* Saved value of BUFFER. */ + size_t bufsize; /* Saved value of BUFSIZE. */ + size_t length; /* Length of the container (ti.length). */ + int in_ndef; /* Saved IN_NDEF flag (ti.ndef). */ + } stack[TLV_MAX_DEPTH]; +}; + + +static unsigned char *cram_octet_string (const unsigned char *input, + size_t length, size_t *r_newlength); +static int need_octet_string_cramming (const unsigned char *input, + size_t length); + + + +void +_tlv_parser_dump_tag (const char *text, int lno, tlv_parser_t tlv) +{ + struct tag_info *ti; + + if (!tlv || tlv->verbosity < 2) + return; + + ti = &tlv->ti; + + log_debug ("p12_parse:%s:%d: @%04zu class=%d tag=%lu len=%zu nhdr=%zu %s%s\n", + text, lno, + (size_t)(tlv->buffer - tlv->origbuffer) - ti->nhdr, + ti->class, ti->tag, ti->length, ti->nhdr, + ti->is_constructed?" cons":"", + ti->ndef?" ndef":""); +} + + +void +_tlv_parser_dump_state (const char *text, const char *text2, + int lno, tlv_parser_t tlv) +{ + if (!tlv || tlv->verbosity < 2) + return; + + log_debug ("p12_parse:%s%s%s:%d: @%04zu lvl=%u %s\n", + text, + text2? "/":"", text2? text2:"", + lno, + (size_t)(tlv->buffer - tlv->origbuffer), + tlv->stacklen, + tlv->in_ndef? " in-ndef":""); +} + + + +/* Parse the buffer at the address BUFFER which is of SIZE and return + * the tag and the length part from the TLV triplet. Update BUFFER + * and SIZE on success. Checks that the encoded length does not + * exhaust the length of the provided buffer. */ +static int +parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti) +{ + gpg_error_t err; + int tag; + + err = parse_ber_header (buffer, size, + &ti->class, &tag, + &ti->is_constructed, &ti->ndef, + &ti->length, &ti->nhdr); + if (err) + return err; + if (tag < 0) + return gpg_error (GPG_ERR_EOVERFLOW); + ti->tag = tag; + + if (ti->length > *size) + return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); /* data larger than buffer. */ + + return 0; +} + +/* Public version of parse_tag. */ +gpg_error_t +tlv_parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti) +{ + return parse_tag (buffer, size, ti); +} + + +/* Create a new TLV object. */ +tlv_parser_t +tlv_parser_new (const unsigned char *buffer, size_t bufsize, int verbosity) +{ + tlv_parser_t tlv; + tlv = xtrycalloc (1, sizeof *tlv); + if (tlv) + { + tlv->origbuffer = buffer; + tlv->origbufsize = bufsize; + tlv->buffer = buffer; + tlv->bufsize = bufsize; + tlv->verbosity = verbosity; + } + return tlv; +} + + +/* This function can be used to store a malloced buffer into the TLV + * object. Ownership of BUFFER is thus transferred to TLV. This + * buffer will then only be released by tlv_release. */ +static gpg_error_t +register_buffer (tlv_parser_t tlv, char *buffer) +{ + struct bufferlist_s *item; + + item = xtrycalloc (1, sizeof *item); + if (!item) + return gpg_error_from_syserror (); + item->buffer = buffer; + item->next = tlv->bufferlist; + tlv->bufferlist = item; + return 0; +} + + +void +tlv_parser_release (tlv_parser_t tlv) +{ + if (!tlv) + return; + while (tlv->bufferlist) + { + struct bufferlist_s *save = tlv->bufferlist->next; + xfree (tlv->bufferlist->buffer); + xfree (tlv->bufferlist); + tlv->bufferlist = save; + } + xfree (tlv); +} + + +/* Helper for the tlv_peek functions. */ +static gpg_error_t +_tlv_peek (tlv_parser_t tlv, struct tag_info *ti) +{ + const unsigned char *p; + size_t n; + + /* Note that we want to peek ahead of any current container but of + * course not beyond our entire buffer. */ + p = tlv->buffer; + if ((p - tlv->origbuffer) > tlv->origbufsize) + return gpg_error (GPG_ERR_BUG); + n = tlv->origbufsize - (p - tlv->origbuffer); + return parse_tag (&p, &n, ti); +} + + +/* Look for the next tag and return true if it matches CLASS and TAG. + * Otherwise return false. No state is changed. */ +int +_tlv_parser_peek (tlv_parser_t tlv, int class, int tag) +{ + struct tag_info ti; + + return (!_tlv_peek (tlv, &ti) + && ti.class == class && ti.tag == tag); +} + + +/* Look for the next tag and return true if it is the Null tag. + * Otherwise return false. No state is changed. */ +int +_tlv_parser_peek_null (tlv_parser_t tlv) +{ + struct tag_info ti; + + return (!_tlv_peek (tlv, &ti) + && ti.class == CLASS_UNIVERSAL && ti.tag == TAG_NULL + && !ti.is_constructed && !ti.length); +} + + +/* Helper for tlv_expect_sequence and tlv_expect_context_tag. */ +static gpg_error_t +_tlv_push (tlv_parser_t tlv) +{ + /* Right now our pointer is at the value of the current container. + * We push that info onto the stack. */ + if (tlv->stacklen >= TLV_MAX_DEPTH) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_MANY)); + tlv->stack[tlv->stacklen].buffer = tlv->buffer; + tlv->stack[tlv->stacklen].bufsize = tlv->bufsize; + tlv->stack[tlv->stacklen].in_ndef = tlv->in_ndef; + tlv->stack[tlv->stacklen].length = tlv->ti.length; + tlv->stacklen++; + + tlv->in_ndef = tlv->ti.ndef; + + /* We set the size of the buffer to the TLV length if it is known or + * else to the size of the remaining entire buffer. */ + if (tlv->in_ndef) + { + if ((tlv->buffer - tlv->origbuffer) > tlv->origbufsize) + return (tlv->lasterr = gpg_error (GPG_ERR_BUG)); + tlv->bufsize = tlv->origbufsize - (tlv->buffer - tlv->origbuffer); + } + else + tlv->bufsize = tlv->ti.length; + + _tlv_parser_dump_state (__func__, NULL, 0, tlv); + return 0; +} + + +/* Helper for tlv_next. */ +static gpg_error_t +_tlv_pop (tlv_parser_t tlv) +{ + size_t lastlen; + + /* We reached the end of a container, either due to the size limit + * or due to an end tag. Now we pop the last container so that we + * are positioned at the value of the last container. */ + if (!tlv->stacklen) + return gpg_error (GPG_ERR_EOF); + + tlv->stacklen--; + tlv->in_ndef = tlv->stack[tlv->stacklen].in_ndef; + if (tlv->in_ndef) + { + /* We keep buffer but adjust bufsize to the end of the origbuffer. */ + if ((tlv->buffer - tlv->origbuffer) > tlv->origbufsize) + return (tlv->lasterr = gpg_error (GPG_ERR_BUG)); + tlv->bufsize = tlv->origbufsize - (tlv->buffer - tlv->origbuffer); + } + else + { + lastlen = tlv->stack[tlv->stacklen].length; + tlv->buffer = tlv->stack[tlv->stacklen].buffer; + tlv->bufsize = tlv->stack[tlv->stacklen].bufsize; + if (lastlen > tlv->bufsize) + { + log_debug ("%s: container length larger than buffer (%zu/%zu)\n", + __func__, lastlen, tlv->bufsize); + return gpg_error (GPG_ERR_INV_BER); + } + tlv->buffer += lastlen; + tlv->bufsize -= lastlen; + } + + _tlv_parser_dump_state (__func__, NULL, 0, tlv); + return 0; +} + + +/* Parse the next tag and value. Also detect the end of a + * container. The caller should use the tlv_next macro. */ +gpg_error_t +_tlv_parser_next (tlv_parser_t tlv, int lno) +{ + gpg_error_t err; + + tlv->lasterr = 0; + tlv->lastfunc = __func__; + + if (tlv->pending) + { + tlv->pending = 0; + if (tlv->verbosity > 1) + log_debug ("%s: skipped\n", __func__); + return 0; + } + + if (tlv->verbosity > 1) + log_debug ("%s: called\n", __func__); + /* If we are at the end of an ndef container pop the stack. */ + if (!tlv->in_ndef && !tlv->bufsize) + { + do + err = _tlv_pop (tlv); + while (!err && !tlv->in_ndef && !tlv->bufsize); + if (err) + return (tlv->lasterr = err); + if (tlv->verbosity > 1) + log_debug ("%s: container(s) closed due to size\n", __func__); + } + + again: + /* Get the next tag. */ + err = parse_tag (&tlv->buffer, &tlv->bufsize, &tlv->ti); + if (err) + { + if (tlv->verbosity > 1) + log_debug ("%s: reading tag returned err=%d\n", __func__, err); + return err; + } + + /* If there is an end tag in an ndef container pop the stack. Also + * pop other containers which are fully consumed. */ + if (tlv->in_ndef && (tlv->ti.class == CLASS_UNIVERSAL + && !tlv->ti.tag && !tlv->ti.is_constructed)) + { + do + err = _tlv_pop (tlv); + while (!err && !tlv->in_ndef && !tlv->bufsize); + if (err) + return (tlv->lasterr = err); + if (tlv->verbosity > 1) + log_debug ("%s: container(s) closed due to end tag\n", __func__); + goto again; + } + + _tlv_parser_dump_tag (__func__, lno, tlv); + return 0; +} + + +/* Return the current neting level of the TLV object. */ +unsigned int +tlv_parser_level (tlv_parser_t tlv) +{ + return tlv? tlv->stacklen : 0; +} + +/* Returns the current offset of the parser. */ +size_t +tlv_parser_offset (tlv_parser_t tlv) +{ + return tlv? (size_t)(tlv->buffer - tlv->origbuffer) : 0; +} + + +/* Return a string with the last function used. If TLV is NULL an + * empty string is returned. */ +const char * +tlv_parser_lastfunc (tlv_parser_t tlv) +{ + return tlv? tlv->lastfunc:""; +} + + +const char * +tlv_parser_lasterrstr (tlv_parser_t tlv) +{ + return tlv? gpg_strerror (tlv->lasterr) : "tlv parser not yet initialized"; +} + + +/* Set a flag to indicate that the last tlv_next has not yet been + * consumed. */ +void +tlv_parser_set_pending (tlv_parser_t tlv) +{ + tlv->pending = 1; +} + + +/* Return the length of the last read tag. If with_header is 1 the + * lengtb of the header is added to the returned length. */ +size_t +tlv_parser_tag_length (tlv_parser_t tlv, int with_header) +{ + if (with_header) + return tlv->ti.length + tlv->ti.nhdr; + else + return tlv->ti.length; +} + + +/* Skip over the value of the current tag. Does not yet work for ndef + * containers. */ +void +tlv_parser_skip (tlv_parser_t tlv) +{ + tlv->lastfunc = __func__; + log_assert (tlv->bufsize >= tlv->ti.length); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; +} + + +/* Expect that the current tag is a sequence and setup the context for + * processing. */ +gpg_error_t +tlv_expect_sequence (tlv_parser_t tlv) +{ + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SEQUENCE + && tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + return _tlv_push (tlv); +} + + +/* Expect that the current tag is a context tag and setup the context + * for processing. The tag of the context is returned at R_TAG. */ +gpg_error_t +tlv_expect_context_tag (tlv_parser_t tlv, int *r_tag) +{ + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_CONTEXT && tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + *r_tag = tlv->ti.tag; + return _tlv_push (tlv); +} + + +/* Expect that the current tag is a SET and setup the context for + * processing. */ +gpg_error_t +tlv_expect_set (tlv_parser_t tlv) +{ + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SET + && tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + return _tlv_push (tlv); +} + + +/* Expect an object of CLASS with TAG and store its value at + * (R_DATA,R_DATALEN). Then skip over its value to the next tag. + * Note that the stored value is not allocated but points into + * TLV. */ +gpg_error_t +tlv_expect_object (tlv_parser_t tlv, int class, int tag, + unsigned char const **r_data, size_t *r_datalen) +{ + gpg_error_t err; + const unsigned char *p; + size_t n; + int needpush = 0; + + tlv->lastfunc = __func__; + if (!(tlv->ti.class == class && tlv->ti.tag == tag)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer; + n = tlv->ti.length; + if (!n && tlv->ti.ndef) + { + n = tlv->bufsize; + needpush = 1; + } + else if (!tlv->ti.length) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + if (class == CLASS_CONTEXT && tag == 0 && tlv->ti.is_constructed + && need_octet_string_cramming (p, n)) + { + char *newbuffer; + + newbuffer = cram_octet_string (p, n, r_datalen); + if (!newbuffer) + return (tlv->lasterr = gpg_error (GPG_ERR_BAD_BER)); + err = register_buffer (tlv, newbuffer); + if (err) + { + xfree (newbuffer); + return (tlv->lasterr = err); + } + *r_data = newbuffer; + } + else + { + *r_data = p; + *r_datalen = n; + } + if (needpush) + return _tlv_push (tlv); + + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; + return 0; +} + + +/* Expect that the current tag is an object string and store its value + * at (R_DATA,R_DATALEN). Then skip over its value to the next tag. + * Note that the stored value are not allocated but point into TLV. + * If ENCAPSULATES is set the octet string is used as a new + * container. R_DATA and R_DATALEN are optional. */ +gpg_error_t +tlv_expect_octet_string (tlv_parser_t tlv, int encapsulates, + unsigned char const **r_data, size_t *r_datalen) +{ + gpg_error_t err; + const unsigned char *p; + size_t n; + + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OCTET_STRING + && (!tlv->ti.is_constructed || encapsulates))) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer; + if (!(n=tlv->ti.length) && !tlv->ti.ndef) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + if (encapsulates && tlv->ti.is_constructed + && need_octet_string_cramming (p, n)) + { + char *newbuffer; + + newbuffer = cram_octet_string (p, n, r_datalen); + if (!newbuffer) + return (tlv->lasterr = gpg_error (GPG_ERR_BAD_BER)); + err = register_buffer (tlv, newbuffer); + if (err) + { + xfree (newbuffer); + return (tlv->lasterr = err); + } + *r_data = newbuffer; + } + else + { + if (r_data) + *r_data = p; + if (r_datalen) + *r_datalen = tlv->ti.length; + } + if (encapsulates) + return _tlv_push (tlv); + + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; + return 0; +} + + +/* Expect that the current tag is an integer and return its value at + * R_VALUE. Then skip over its value to the next tag. */ +gpg_error_t +tlv_expect_integer (tlv_parser_t tlv, int *r_value) +{ + const unsigned char *p; + size_t n; + int value; + + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER + && !tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer; + if (!(n=tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + /* We currently support only positive values. */ + if ((*p & 0x80)) + return (tlv->lasterr = gpg_error (GPG_ERR_ERANGE)); + + for (value = 0; n; n--) + { + value <<= 8; + value |= (*p++) & 0xff; + if (value < 0) + return (tlv->lasterr = gpg_error (GPG_ERR_EOVERFLOW)); + } + *r_value = value; + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; + return 0; +} + + +/* Variant of tlv_expect_integer which returns an MPI. If IGNORE_ZERO + * is set a value of 0 is ignored and R_VALUE not changed and the + * function returns GPG_ERR_FALSE. No check for negative encoded + * integers is done because the old code here worked the same and we + * can't foreclose invalid encoded PKCS#12 stuff - after all it is + * PKCS#12 see https://www.cs.auckland.ac.nz/~pgut001/pubs/pfx.html */ +#ifdef GCRYPT_VERSION +gpg_error_t +tlv_expect_mpinteger (tlv_parser_t tlv, int ignore_zero, + gcry_mpi_t *r_value) +{ + const unsigned char *p; + size_t n; + + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER + && !tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer; + if (!(n=tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; + if (ignore_zero && n == 1 && !*p) + return gpg_error (GPG_ERR_FALSE); + + return gcry_mpi_scan (r_value, GCRYMPI_FMT_USG, p, n, NULL); +} +#endif /*GCRYPT_VERSION*/ + + +/* Expect that the current tag is an object id and store its value at + * (R_OID,R_OIDLEN). Then skip over its value to the next tag. Note + * that the stored value is not allocated but points into TLV. */ +gpg_error_t +tlv_expect_object_id (tlv_parser_t tlv, + unsigned char const **r_oid, size_t *r_oidlen) +{ + const unsigned char *p; + size_t n; + + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OBJECT_ID + && !tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer; + if (!(n=tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + *r_oid = p; + *r_oidlen = tlv->ti.length; + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; + return 0; +} + + +/* Given an ASN.1 chunk of a structure like: + * + * 24 NDEF: OCTET STRING -- This is not passed to us + * 04 1: OCTET STRING -- INPUT point s to here + * : 30 + * 04 1: OCTET STRING + * : 80 + * [...] + * 04 2: OCTET STRING + * : 00 00 + * : } -- This denotes a Null tag and are the last + * -- two bytes in INPUT. + * + * The example is from Mozilla Firefox 1.0.4 which actually exports + * certs as single byte chunks of octet strings. + * + * Create a new buffer with the content of that octet string. INPUT + * is the original buffer with a LENGTH. Returns + * NULL on error or a new malloced buffer with its actual used length + * stored at R_NEWLENGTH. */ +static unsigned char * +cram_octet_string (const unsigned char *input, size_t length, + size_t *r_newlength) +{ + const unsigned char *s = input; + size_t n = length; + unsigned char *output, *d; + struct tag_info ti; + + /* Allocate output buf. We know that it won't be longer than the + input buffer. */ + d = output = xtrymalloc (length); + if (!output) + goto bailout; + + while (n) + { + if (parse_tag (&s, &n, &ti)) + goto bailout; + if (ti.class == CLASS_UNIVERSAL && ti.tag == TAG_OCTET_STRING + && !ti.ndef && !ti.is_constructed) + { + memcpy (d, s, ti.length); + s += ti.length; + d += ti.length; + n -= ti.length; + } + else if (ti.class == CLASS_UNIVERSAL && !ti.tag && !ti.is_constructed) + break; /* Ready */ + else + goto bailout; + } + + + *r_newlength = d - output; + return output; + + bailout: + xfree (output); + return NULL; +} + + +/* Return true if (INPUT,LENGTH) is a structure which should be passed + * to cram_octet_string. This is basically the same loop as in + * cram_octet_string but without any actual copying. */ +static int +need_octet_string_cramming (const unsigned char *input, size_t length) +{ + const unsigned char *s = input; + size_t n = length; + struct tag_info ti; + + if (!length) + return 0; + + while (n) + { + if (parse_tag (&s, &n, &ti)) + return 0; + if (ti.class == CLASS_UNIVERSAL && ti.tag == TAG_OCTET_STRING + && !ti.ndef && !ti.is_constructed) + { + s += ti.length; + n -= ti.length; + } + else if (ti.class == CLASS_UNIVERSAL && !ti.tag && !ti.is_constructed) + break; /* Ready */ + else + return 0; + } + + return 1; +} diff --git a/common/tlv.h b/common/tlv.h index e371ca57e..afaa649d9 100644 --- a/common/tlv.h +++ b/common/tlv.h @@ -71,10 +71,22 @@ enum tlv_tag_type { TAG_BMP_STRING = 30 }; +struct tag_info +{ + int class; + int is_constructed; + unsigned long tag; + size_t length; /* length part of the TLV */ + size_t nhdr; + int ndef; /* It is an indefinite length */ +}; struct tlv_builder_s; typedef struct tlv_builder_s *tlv_builder_t; +struct tlv_parser_s; +typedef struct tlv_parser_s *tlv_parser_t; + /*-- tlv.c --*/ /* Locate a TLV encoded data object in BUFFER of LENGTH and return a @@ -94,7 +106,7 @@ const unsigned char *find_tlv_unchecked (const unsigned char *buffer, /* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag and the length part from the TLV triplet. Update BUFFER and SIZE - on success. */ + on success. See also tlv_parse_tag. */ gpg_error_t parse_ber_header (unsigned char const **buffer, size_t *size, int *r_class, int *r_tag, int *r_constructed, @@ -137,6 +149,59 @@ void put_tlv_to_membuf (membuf_t *membuf, int class, int tag, size_t get_tlv_length (int class, int tag, int constructed, size_t length); +/*-- tlv-parser.c --*/ + +tlv_parser_t tlv_parser_new (const unsigned char *buffer, size_t bufsize, + int verbosity); +void tlv_parser_release (tlv_parser_t tlv); + +void _tlv_parser_dump_tag (const char *text, int lno, tlv_parser_t tlv); +void _tlv_parser_dump_state (const char *text, const char *text2, + int lno, tlv_parser_t tlv); + +int _tlv_parser_peek (tlv_parser_t tlv, int class, int tag); +int _tlv_parser_peek_null (tlv_parser_t tlv); +gpg_error_t _tlv_parser_next (tlv_parser_t tlv, int lno); + +unsigned int tlv_parser_level (tlv_parser_t tlv); +size_t tlv_parser_offset (tlv_parser_t tlv); +const char *tlv_parser_lastfunc (tlv_parser_t tlv); +const char *tlv_parser_lasterrstr (tlv_parser_t tlv); +void tlv_parser_set_pending (tlv_parser_t tlv); +size_t tlv_parser_tag_length (tlv_parser_t tlv, int with_header); + +void tlv_parser_skip (tlv_parser_t tlv); + +gpg_error_t tlv_expect_sequence (tlv_parser_t tlv); +gpg_error_t tlv_expect_context_tag (tlv_parser_t tlv, int *r_tag); +gpg_error_t tlv_expect_set (tlv_parser_t tlv); +gpg_error_t tlv_expect_object (tlv_parser_t tlv, int class, int tag, + unsigned char const **r_data, + size_t *r_datalen); +gpg_error_t tlv_expect_octet_string (tlv_parser_t tlv, int encapsulates, + unsigned char const **r_data, + size_t *r_datalen); +gpg_error_t tlv_expect_integer (tlv_parser_t tlv, int *r_value); +#ifdef GCRYPT_VERSION +gpg_error_t tlv_expect_mpinteger (tlv_parser_t tlv, int ignore_zero, + gcry_mpi_t *r_value); +#endif +gpg_error_t tlv_expect_object_id (tlv_parser_t tlv, + unsigned char const **r_oid, + size_t *r_oidlen); + +/* Easier to use wrapper around parse_ber_header. */ +gpg_error_t tlv_parse_tag (unsigned char const **buffer, + size_t *size, struct tag_info *ti); + +/* Convenience macro and macros to include the line number. */ +#define tlv_parser_dump_tag(a,b) _tlv_parser_dump_tag ((a),__LINE__,(b)) +#define tlv_parser_dump_state(a,b,c) \ + _tlv_parser_dump_state ((a),(b),__LINE__,(c)) +#define tlv_peek(a,b,c) _tlv_parser_peek ((a),(b),(c)) +#define tlv_peek_null(a) _tlv_parser_peek_null ((a)) +#define tlv_next(a) _tlv_parser_next ((a), __LINE__) + #endif /* SCD_TLV_H */ diff --git a/sm/minip12.c b/sm/minip12.c index 98eb4e3b5..ae81d821b 100644 --- a/sm/minip12.c +++ b/sm/minip12.c @@ -150,57 +150,6 @@ struct buffer_s }; -struct tag_info -{ - int class; - int is_constructed; - unsigned long tag; - size_t length; /* length part of the TLV */ - size_t nhdr; - int ndef; /* It is an indefinite length */ -}; - - -#define TLV_MAX_DEPTH 20 - - -struct bufferlist_s -{ - struct bufferlist_s *next; - char *buffer; -}; - - -/* An object to control the ASN.1 parsing. */ -struct tlv_ctx_s -{ - /* The orginal buffer with the entire pkcs#12 object and its length. */ - const unsigned char *origbuffer; - size_t origbufsize; - - /* The current buffer we are working on and its length. */ - const unsigned char *buffer; - size_t bufsize; - - int in_ndef; /* Flag indicating that we are in a NDEF. */ - int pending; /* The last tlv_next has not yet been processed. */ - - struct tag_info ti; /* The current tag. */ - gpg_error_t lasterr; /* Last error from tlv function. */ - const char *lastfunc;/* Name of last called function. */ - - struct bufferlist_s *bufferlist; /* To keep track of malloced buffers. */ - - unsigned int stacklen; /* Used size of the stack. */ - struct { - const unsigned char *buffer; /* Saved value of BUFFER. */ - size_t bufsize; /* Saved value of BUFSIZE. */ - size_t length; /* Length of the container (ti.length). */ - int in_ndef; /* Saved IN_NDEF flag (ti.ndef). */ - } stack[TLV_MAX_DEPTH]; -}; - - /* Parser communication object. */ struct p12_parse_ctx_s { @@ -225,11 +174,6 @@ struct p12_parse_ctx_s static int opt_verbose; -static unsigned char *cram_octet_string (const unsigned char *input, - size_t length, size_t *r_newlength); -static int need_octet_string_cramming (const unsigned char *input, - size_t length); - @@ -242,44 +186,6 @@ p12_set_verbosity (int verbose, int debug) } -#define dump_tag_info(a,b) _dump_tag_info ((a),__LINE__,(b)) -static void -_dump_tag_info (const char *text, int lno, struct tlv_ctx_s *tlv) -{ - struct tag_info *ti; - - if (opt_verbose < 2) - return; - - ti = &tlv->ti; - - log_debug ("p12_parse:%s:%d: @%04zu class=%d tag=%lu len=%zu nhdr=%zu %s%s\n", - text, lno, - (size_t)(tlv->buffer - tlv->origbuffer) - ti->nhdr, - ti->class, ti->tag, ti->length, ti->nhdr, - ti->is_constructed?" cons":"", - ti->ndef?" ndef":""); -} - - -#define dump_tlv_ctx(a,b,c) _dump_tlv_ctx ((a),(b),__LINE__,(c)) -static void -_dump_tlv_ctx (const char *text, const char *text2, - int lno, struct tlv_ctx_s *tlv) -{ - if (opt_verbose < 2) - return; - - log_debug ("p12_parse:%s%s%s:%d: @%04zu lvl=%u %s\n", - text, - text2? "/":"", text2? text2:"", - lno, - (size_t)(tlv->buffer - tlv->origbuffer), - tlv->stacklen, - tlv->in_ndef? " in-ndef":""); -} - - static int digest_algo_from_oid (unsigned char const *oid, size_t oidlen) { @@ -398,638 +304,6 @@ builder_add_mpi (tlv_builder_t tb, int class, int tag, gcry_mpi_t mpi, } - -/* Parse the buffer at the address BUFFER which is of SIZE and return - * the tag and the length part from the TLV triplet. Update BUFFER - * and SIZE on success. Checks that the encoded length does not - * exhaust the length of the provided buffer. */ -static int -parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti) -{ - gpg_error_t err; - int tag; - - err = parse_ber_header (buffer, size, - &ti->class, &tag, - &ti->is_constructed, &ti->ndef, - &ti->length, &ti->nhdr); - if (err) - return err; - if (tag < 0) - return gpg_error (GPG_ERR_EOVERFLOW); - ti->tag = tag; - - if (ti->length > *size) - return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); /* data larger than buffer. */ - - return 0; -} - - -/* Create a new TLV object. */ -static struct tlv_ctx_s * -tlv_new (const unsigned char *buffer, size_t bufsize) -{ - struct tlv_ctx_s *tlv; - tlv = xtrycalloc (1, sizeof *tlv); - if (tlv) - { - tlv->origbuffer = buffer; - tlv->origbufsize = bufsize; - tlv->buffer = buffer; - tlv->bufsize = bufsize; - } - return tlv; -} - - -/* This function can be used to store a malloced buffer into the TLV - * object. Ownership of BUFFER is thus transferred to TLV. This - * buffer will then only be released by tlv_release. */ -static gpg_error_t -tlv_register_buffer (struct tlv_ctx_s *tlv, char *buffer) -{ - struct bufferlist_s *item; - - item = xtrycalloc (1, sizeof *item); - if (!item) - return gpg_error_from_syserror (); - item->buffer = buffer; - item->next = tlv->bufferlist; - tlv->bufferlist = item; - return 0; -} - - -static void -tlv_release (struct tlv_ctx_s *tlv) -{ - if (!tlv) - return; - while (tlv->bufferlist) - { - struct bufferlist_s *save = tlv->bufferlist->next; - xfree (tlv->bufferlist->buffer); - xfree (tlv->bufferlist); - tlv->bufferlist = save; - } - xfree (tlv); -} - - -/* Helper for the tlv_peek functions. */ -static gpg_error_t -_tlv_peek (struct tlv_ctx_s *tlv, struct tag_info *ti) -{ - const unsigned char *p; - size_t n; - - /* Note that we want to peek ahead of any current container but of - * course not beyond our entire buffer. */ - p = tlv->buffer; - if ((p - tlv->origbuffer) > tlv->origbufsize) - return gpg_error (GPG_ERR_BUG); - n = tlv->origbufsize - (p - tlv->origbuffer); - return parse_tag (&p, &n, ti); -} - - -/* Look for the next tag and return true if it matches CLASS and TAG. - * Otherwise return false. No state is changed. */ -static int -tlv_peek (struct tlv_ctx_s *tlv, int class, int tag) -{ - struct tag_info ti; - - return (!_tlv_peek (tlv, &ti) - && ti.class == class && ti.tag == tag); -} - - -/* Look for the next tag and return true if it is the Null tag. - * Otherwise return false. No state is changed. */ -static int -tlv_peek_null (struct tlv_ctx_s *tlv) -{ - struct tag_info ti; - - return (!_tlv_peek (tlv, &ti) - && ti.class == CLASS_UNIVERSAL && ti.tag == TAG_NULL - && !ti.is_constructed && !ti.length); -} - - -/* Helper for tlv_expect_sequence and tlv_expect_context_tag. */ -static gpg_error_t -_tlv_push (struct tlv_ctx_s *tlv) -{ - /* Right now our pointer is at the value of the current container. - * We push that info onto the stack. */ - if (tlv->stacklen >= TLV_MAX_DEPTH) - return (tlv->lasterr = gpg_error (GPG_ERR_TOO_MANY)); - tlv->stack[tlv->stacklen].buffer = tlv->buffer; - tlv->stack[tlv->stacklen].bufsize = tlv->bufsize; - tlv->stack[tlv->stacklen].in_ndef = tlv->in_ndef; - tlv->stack[tlv->stacklen].length = tlv->ti.length; - tlv->stacklen++; - - tlv->in_ndef = tlv->ti.ndef; - - /* We set the size of the buffer to the TLV length if it is known or - * else to the size of the remaining entire buffer. */ - if (tlv->in_ndef) - { - if ((tlv->buffer - tlv->origbuffer) > tlv->origbufsize) - return (tlv->lasterr = gpg_error (GPG_ERR_BUG)); - tlv->bufsize = tlv->origbufsize - (tlv->buffer - tlv->origbuffer); - } - else - tlv->bufsize = tlv->ti.length; - - dump_tlv_ctx (__func__, NULL, tlv); - return 0; -} - - -/* Helper for tlv_next. */ -static gpg_error_t -_tlv_pop (struct tlv_ctx_s *tlv) -{ - size_t lastlen; - - /* We reached the end of a container, either due to the size limit - * or due to an end tag. Now we pop the last container so that we - * are positioned at the value of the last container. */ - if (!tlv->stacklen) - return gpg_error (GPG_ERR_EOF); - - tlv->stacklen--; - tlv->in_ndef = tlv->stack[tlv->stacklen].in_ndef; - if (tlv->in_ndef) - { - /* We keep buffer but adjust bufsize to the end of the origbuffer. */ - if ((tlv->buffer - tlv->origbuffer) > tlv->origbufsize) - return (tlv->lasterr = gpg_error (GPG_ERR_BUG)); - tlv->bufsize = tlv->origbufsize - (tlv->buffer - tlv->origbuffer); - } - else - { - lastlen = tlv->stack[tlv->stacklen].length; - tlv->buffer = tlv->stack[tlv->stacklen].buffer; - tlv->bufsize = tlv->stack[tlv->stacklen].bufsize; - if (lastlen > tlv->bufsize) - { - log_debug ("%s: container length larger than buffer (%zu/%zu)\n", - __func__, lastlen, tlv->bufsize); - return gpg_error (GPG_ERR_INV_BER); - } - tlv->buffer += lastlen; - tlv->bufsize -= lastlen; - } - - dump_tlv_ctx (__func__, NULL, tlv); - return 0; -} - - -/* Parse the next tag and value. Also detect the end of a - * container. */ -#define tlv_next(a) _tlv_next ((a), __LINE__) -static gpg_error_t -_tlv_next (struct tlv_ctx_s *tlv, int lno) -{ - gpg_error_t err; - - tlv->lasterr = 0; - tlv->lastfunc = __func__; - - if (tlv->pending) - { - tlv->pending = 0; - if (opt_verbose > 1) - log_debug ("%s: tlv_next skipped\n", __func__); - return 0; - } - - if (opt_verbose > 1) - log_debug ("%s: tlv_next called\n", __func__); - /* If we are at the end of an ndef container pop the stack. */ - if (!tlv->in_ndef && !tlv->bufsize) - { - do - err = _tlv_pop (tlv); - while (!err && !tlv->in_ndef && !tlv->bufsize); - if (err) - return (tlv->lasterr = err); - if (opt_verbose > 1) - log_debug ("%s: container(s) closed due to size\n", __func__); - } - - again: - /* Get the next tag. */ - err = parse_tag (&tlv->buffer, &tlv->bufsize, &tlv->ti); - if (err) - { - if (opt_verbose > 1) - log_debug ("%s: reading tag returned err=%d\n", __func__, err); - return err; - } - - /* If there is an end tag in an ndef container pop the stack. Also - * pop other containers which are fully consumed. */ - if (tlv->in_ndef && (tlv->ti.class == CLASS_UNIVERSAL - && !tlv->ti.tag && !tlv->ti.is_constructed)) - { - do - err = _tlv_pop (tlv); - while (!err && !tlv->in_ndef && !tlv->bufsize); - if (err) - return (tlv->lasterr = err); - if (opt_verbose > 1) - log_debug ("%s: container(s) closed due to end tag\n", __func__); - goto again; - } - - _dump_tag_info (__func__, lno, tlv); - return 0; -} - - -/* Return the current neting level of the TLV object. */ -static unsigned int -tlv_level (struct tlv_ctx_s *tlv) -{ - return tlv->stacklen; -} - - -/* Set a flag to indicate that the last tlv_next has not yet been - * consumed. */ -static void -tlv_set_pending (struct tlv_ctx_s *tlv) -{ - tlv->pending = 1; -} - - -/* Skip over the value of the current tag. Does not yet work for ndef - * containers. */ -static void -tlv_skip (struct tlv_ctx_s *tlv) -{ - tlv->lastfunc = __func__; - log_assert (tlv->bufsize >= tlv->ti.length); - tlv->buffer += tlv->ti.length; - tlv->bufsize -= tlv->ti.length; -} - - -/* Expect that the current tag is a sequence and setup the context for - * processing. */ -static gpg_error_t -tlv_expect_sequence (struct tlv_ctx_s *tlv) -{ - tlv->lastfunc = __func__; - if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SEQUENCE - && tlv->ti.is_constructed)) - return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - return _tlv_push (tlv); -} - - -/* Expect that the current tag is a context tag and setup the context - * for processing. The tag of the context is returned at R_TAG. */ -static gpg_error_t -tlv_expect_context_tag (struct tlv_ctx_s *tlv, int *r_tag) -{ - tlv->lastfunc = __func__; - if (!(tlv->ti.class == CLASS_CONTEXT && tlv->ti.is_constructed)) - return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - *r_tag = tlv->ti.tag; - return _tlv_push (tlv); -} - - -/* Expect that the current tag is a SET and setup the context for - * processing. */ -static gpg_error_t -tlv_expect_set (struct tlv_ctx_s *tlv) -{ - tlv->lastfunc = __func__; - if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SET - && tlv->ti.is_constructed)) - return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - return _tlv_push (tlv); -} - - -/* Expect an object of CLASS with TAG and store its value at - * (R_DATA,R_DATALEN). Then skip over its value to the next tag. - * Note that the stored value is not allocated but points into - * TLV. */ -static gpg_error_t -tlv_expect_object (struct tlv_ctx_s *tlv, int class, int tag, - unsigned char const **r_data, size_t *r_datalen) -{ - gpg_error_t err; - const unsigned char *p; - size_t n; - int needpush = 0; - - tlv->lastfunc = __func__; - if (!(tlv->ti.class == class && tlv->ti.tag == tag)) - return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - p = tlv->buffer; - n = tlv->ti.length; - if (!n && tlv->ti.ndef) - { - n = tlv->bufsize; - needpush = 1; - } - else if (!tlv->ti.length) - return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); - - if (class == CLASS_CONTEXT && tag == 0 && tlv->ti.is_constructed - && need_octet_string_cramming (p, n)) - { - char *newbuffer; - - newbuffer = cram_octet_string (p, n, r_datalen); - if (!newbuffer) - return (tlv->lasterr = gpg_error (GPG_ERR_BAD_BER)); - err = tlv_register_buffer (tlv, newbuffer); - if (err) - { - xfree (newbuffer); - return (tlv->lasterr = err); - } - *r_data = newbuffer; - } - else - { - *r_data = p; - *r_datalen = n; - } - if (needpush) - return _tlv_push (tlv); - - if (!(tlv->bufsize >= tlv->ti.length)) - return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); - tlv->buffer += tlv->ti.length; - tlv->bufsize -= tlv->ti.length; - return 0; -} - - -/* Expect that the current tag is an object string and store its value - * at (R_DATA,R_DATALEN). Then skip over its value to the next tag. - * Note that the stored value are not allocated but point into TLV. - * If ENCAPSULATES is set the octet string is used as a new - * container. R_DATA and R_DATALEN are optional. */ -static gpg_error_t -tlv_expect_octet_string (struct tlv_ctx_s *tlv, int encapsulates, - unsigned char const **r_data, size_t *r_datalen) -{ - gpg_error_t err; - const unsigned char *p; - size_t n; - - tlv->lastfunc = __func__; - if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OCTET_STRING - && (!tlv->ti.is_constructed || encapsulates))) - return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - p = tlv->buffer; - if (!(n=tlv->ti.length) && !tlv->ti.ndef) - return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); - - if (encapsulates && tlv->ti.is_constructed - && need_octet_string_cramming (p, n)) - { - char *newbuffer; - - newbuffer = cram_octet_string (p, n, r_datalen); - if (!newbuffer) - return (tlv->lasterr = gpg_error (GPG_ERR_BAD_BER)); - err = tlv_register_buffer (tlv, newbuffer); - if (err) - { - xfree (newbuffer); - return (tlv->lasterr = err); - } - *r_data = newbuffer; - } - else - { - if (r_data) - *r_data = p; - if (r_datalen) - *r_datalen = tlv->ti.length; - } - if (encapsulates) - return _tlv_push (tlv); - - if (!(tlv->bufsize >= tlv->ti.length)) - return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); - tlv->buffer += tlv->ti.length; - tlv->bufsize -= tlv->ti.length; - return 0; -} - - -/* Expect that the current tag is an integer and return its value at - * R_VALUE. Then skip over its value to the next tag. */ -static gpg_error_t -tlv_expect_integer (struct tlv_ctx_s *tlv, int *r_value) -{ - const unsigned char *p; - size_t n; - int value; - - tlv->lastfunc = __func__; - if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER - && !tlv->ti.is_constructed)) - return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - p = tlv->buffer; - if (!(n=tlv->ti.length)) - return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); - - /* We currently support only positive values. */ - if ((*p & 0x80)) - return (tlv->lasterr = gpg_error (GPG_ERR_ERANGE)); - - for (value = 0; n; n--) - { - value <<= 8; - value |= (*p++) & 0xff; - if (value < 0) - return (tlv->lasterr = gpg_error (GPG_ERR_EOVERFLOW)); - } - *r_value = value; - if (!(tlv->bufsize >= tlv->ti.length)) - return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); - tlv->buffer += tlv->ti.length; - tlv->bufsize -= tlv->ti.length; - return 0; -} - - -/* Variant of tlv_expect_integer which returns an MPI. If IGNORE_ZERO - * is set a value of 0 is ignored and R_VALUE not changed and the - * function returns GPG_ERR_FALSE. No check for negative encoded - * integers is doe because the old code here worked the same and we - * can't foreclose invalid encoded PKCS#12 stuff - after all it is - * PKCS#12 see https://www.cs.auckland.ac.nz/~pgut001/pubs/pfx.html */ -static gpg_error_t -tlv_expect_mpinteger (struct tlv_ctx_s *tlv, int ignore_zero, - gcry_mpi_t *r_value) -{ - const unsigned char *p; - size_t n; - - tlv->lastfunc = __func__; - if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER - && !tlv->ti.is_constructed)) - return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - p = tlv->buffer; - if (!(n=tlv->ti.length)) - return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); - - if (!(tlv->bufsize >= tlv->ti.length)) - return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); - tlv->buffer += tlv->ti.length; - tlv->bufsize -= tlv->ti.length; - if (ignore_zero && n == 1 && !*p) - return gpg_error (GPG_ERR_FALSE); - - return gcry_mpi_scan (r_value, GCRYMPI_FMT_USG, p, n, NULL); -} - - -/* Expect that the current tag is an object id and store its value at - * (R_OID,R_OIDLEN). Then skip over its value to the next tag. Note - * that the stored value is not allocated but points into TLV. */ -static gpg_error_t -tlv_expect_object_id (struct tlv_ctx_s *tlv, - unsigned char const **r_oid, size_t *r_oidlen) -{ - const unsigned char *p; - size_t n; - - tlv->lastfunc = __func__; - if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OBJECT_ID - && !tlv->ti.is_constructed)) - return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); - p = tlv->buffer; - if (!(n=tlv->ti.length)) - return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); - - *r_oid = p; - *r_oidlen = tlv->ti.length; - if (!(tlv->bufsize >= tlv->ti.length)) - return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); - tlv->buffer += tlv->ti.length; - tlv->bufsize -= tlv->ti.length; - return 0; -} - - - -/* Given an ASN.1 chunk of a structure like: - * - * 24 NDEF: OCTET STRING -- This is not passed to us - * 04 1: OCTET STRING -- INPUT point s to here - * : 30 - * 04 1: OCTET STRING - * : 80 - * [...] - * 04 2: OCTET STRING - * : 00 00 - * : } -- This denotes a Null tag and are the last - * -- two bytes in INPUT. - * - * The example is from Mozilla Firefox 1.0.4 which actually exports - * certs as single byte chunks of octet strings. - * - * Create a new buffer with the content of that octet string. INPUT - * is the original buffer with a LENGTH. Returns - * NULL on error or a new malloced buffer with its actual used length - * stored at R_NEWLENGTH. */ -static unsigned char * -cram_octet_string (const unsigned char *input, size_t length, - size_t *r_newlength) -{ - const unsigned char *s = input; - size_t n = length; - unsigned char *output, *d; - struct tag_info ti; - - /* Allocate output buf. We know that it won't be longer than the - input buffer. */ - d = output = gcry_malloc (length); - if (!output) - goto bailout; - - while (n) - { - if (parse_tag (&s, &n, &ti)) - goto bailout; - if (ti.class == CLASS_UNIVERSAL && ti.tag == TAG_OCTET_STRING - && !ti.ndef && !ti.is_constructed) - { - memcpy (d, s, ti.length); - s += ti.length; - d += ti.length; - n -= ti.length; - } - else if (ti.class == CLASS_UNIVERSAL && !ti.tag && !ti.is_constructed) - break; /* Ready */ - else - goto bailout; - } - - - *r_newlength = d - output; - return output; - - bailout: - gcry_free (output); - return NULL; -} - - -/* Return true if (INPUT,LENGTH) is a structure which should be passed - * to cram_octet_string. This is basically the same loop as in - * cram_octet_string but without any actual copying. */ -static int -need_octet_string_cramming (const unsigned char *input, size_t length) -{ - const unsigned char *s = input; - size_t n = length; - struct tag_info ti; - - if (!length) - return 0; - - while (n) - { - if (parse_tag (&s, &n, &ti)) - return 0; - if (ti.class == CLASS_UNIVERSAL && ti.tag == TAG_OCTET_STRING - && !ti.ndef && !ti.is_constructed) - { - s += ti.length; - n -= ti.length; - } - else if (ti.class == CLASS_UNIVERSAL && !ti.tag && !ti.is_constructed) - break; /* Ready */ - else - return 0; - } - - return 1; -} - - static int string_to_key (int id, char *salt, size_t saltlen, int iter, const char *pw, int req_keylen, unsigned char *keybuf) @@ -1379,11 +653,11 @@ bag_decrypted_data_p (const void *plaintext, size_t length) } #endif /*ENABLE_DER_STRUCT_DUMPING*/ - if (parse_tag (&p, &n, &ti)) + if (tlv_parse_tag (&p, &n, &ti)) return 0; if (ti.class || ti.tag != TAG_SEQUENCE) return 0; - if (parse_tag (&p, &n, &ti)) + if (tlv_parse_tag (&p, &n, &ti)) return 0; return 1; @@ -1391,7 +665,7 @@ bag_decrypted_data_p (const void *plaintext, size_t length) static int -parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) +parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, tlv_parser_t tlv) { gpg_error_t err = 0; const char *where; @@ -1510,13 +784,13 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) goto bailout; if (tlv_expect_sequence (tlv)) goto bailout; - parmlen = tlv->ti.length; + parmlen = tlv_parser_tag_length (tlv, 0); if (tlv_next (tlv)) goto bailout; if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) goto bailout; - parmlen -= tlv->ti.length + tlv->ti.nhdr; + parmlen -= tlv_parser_tag_length (tlv, 1); if (datalen < 8 || datalen > sizeof salt) { log_info ("bad length of salt (%zu)\n", datalen); @@ -1530,7 +804,7 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) goto bailout; if ((err = tlv_expect_integer (tlv, &intval))) goto bailout; - parmlen -= tlv->ti.length + tlv->ti.nhdr; + parmlen -= tlv_parser_tag_length (tlv, 1); if (!intval) /* Not a valid iteration count. */ { err = gpg_error (GPG_ERR_INV_VALUE); @@ -1667,7 +941,7 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) /* We do not need the TLV anymore and allocated a new one. */ where = "bag.encryptedData.decrypted-text"; - tlv = tlv_new (plain, datalen); + tlv = tlv_parser_new (plain, datalen, opt_verbose); if (!tlv) { err = gpg_error_from_syserror (); @@ -1688,8 +962,8 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) /* Loop over all certificates inside the bag. */ loopcount = 0; - startlevel = tlv_level (tlv); - while (!(err = tlv_next (tlv)) && tlv_level (tlv) == startlevel) + startlevel = tlv_parser_level (tlv); + while (!(err = tlv_next (tlv)) && tlv_parser_level (tlv) == startlevel) { int iscrlbag = 0; int iskeybag = 0; @@ -1802,8 +1076,8 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) where = "reading.keybag.key-parameters"; keyelem_count = 0; - startlevel2 = tlv_level (tlv); - while (!(err = tlv_next (tlv)) && tlv_level (tlv) == startlevel2) + startlevel2 = tlv_parser_level (tlv); + while (!(err = tlv_next (tlv)) && tlv_parser_level (tlv) == startlevel2) { if (keyelem_count >= 9) { @@ -1826,7 +1100,7 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) keyelem_count++; } if (!err) - tlv_set_pending (tlv); + tlv_parser_set_pending (tlv); else if (err && gpg_err_code (err) != GPG_ERR_EOF) goto bailout; err = 0; @@ -1883,13 +1157,13 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) err = tlv_expect_set (tlv); if (err) goto bailout; - tlv_skip (tlv); + tlv_parser_skip (tlv); if (opt_verbose) log_info ("skipping %s\n", where); } } if (!err) - tlv_set_pending (tlv); + tlv_parser_set_pending (tlv); else if (err && gpg_err_code (err) != GPG_ERR_EOF) { if (!loopcount) /* The first while(tlv_next) failed. */ @@ -1900,7 +1174,7 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) leave: if (renewed_tlv) - tlv_release (tlv); + tlv_parser_release (tlv); gcry_free (plain); if (ctx->badpass) { @@ -1916,9 +1190,9 @@ parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) err = gpg_error (GPG_ERR_GENERAL); log_error ("%s(%s): lvl=%u (%s): %s - %s\n", __func__, where, - tlv? tlv->stacklen : 0, - tlv? tlv->lastfunc : "", - tlv ? gpg_strerror (tlv->lasterr) : "init failed", + tlv_parser_level (tlv), + tlv_parser_lastfunc (tlv), + tlv_parser_lasterrstr (tlv), gpg_strerror (err)); goto leave; } @@ -1943,9 +1217,9 @@ bag_data_p (const void *plaintext, size_t length) } #endif /*ENABLE_DER_STRUCT_DUMPING*/ - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + if (tlv_parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) return 0; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER + if (tlv_parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER || ti.length != 1 || *p) return 0; @@ -1954,7 +1228,7 @@ bag_data_p (const void *plaintext, size_t length) static gpg_error_t -parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) +parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, tlv_parser_t tlv) { gpg_error_t err = 0; const char *where; @@ -1967,7 +1241,7 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) size_t saltlen; char iv[16]; unsigned int iter; - struct tlv_ctx_s *saved_tlv = NULL; + tlv_parser_t saved_tlv = NULL; int renewed_tlv = 0; /* True if the TLV must be released. */ unsigned char *plain = NULL; int is_pbes2 = 0; @@ -2039,13 +1313,13 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) goto bailout; if (tlv_expect_sequence (tlv)) goto bailout; - parmlen = tlv->ti.length; + parmlen = tlv_parser_tag_length (tlv, 0); if (tlv_next (tlv)) goto bailout; if (tlv_expect_octet_string (tlv, 0, &data, &datalen)) goto bailout; - parmlen -= tlv->ti.length + tlv->ti.nhdr; + parmlen -= tlv_parser_tag_length (tlv, 1); if (datalen < 8 || datalen > sizeof salt) { log_info ("bad length of salt (%zu) for AES\n", datalen); @@ -2059,7 +1333,7 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) goto bailout; if ((err = tlv_expect_integer (tlv, &intval))) goto bailout; - parmlen -= tlv->ti.length + tlv->ti.nhdr; + parmlen -= tlv_parser_tag_length (tlv, 1); if (!intval) /* Not a valid iteration count. */ { err = gpg_error (GPG_ERR_INV_VALUE); @@ -2193,7 +1467,7 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) /* We do not need the TLV anymore and allocated a new one. */ where = "shrouded_key_bag.decrypted-text"; saved_tlv = tlv; - tlv = tlv_new (plain, datalen); + tlv = tlv_parser_new (plain, datalen, opt_verbose); if (!tlv) { err = gpg_error_from_syserror (); @@ -2336,9 +1610,9 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) { int keyelem_count = 0; int firstparam = 1; - unsigned int startlevel = tlv_level (tlv); + unsigned int startlevel = tlv_parser_level (tlv); - while (!(err = tlv_next (tlv)) && tlv_level (tlv) == startlevel) + while (!(err = tlv_next (tlv)) && tlv_parser_level (tlv) == startlevel) { if (keyelem_count >= 9) { @@ -2365,7 +1639,7 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) firstparam = 0; } if (!err) - tlv_set_pending (tlv); + tlv_parser_set_pending (tlv); else if (err && gpg_err_code (err) != GPG_ERR_EOF) goto bailout; err = 0; @@ -2373,7 +1647,7 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) if (opt_verbose > 1) log_debug ("restoring parser context\n"); - tlv_release (tlv); + tlv_parser_release (tlv); renewed_tlv = 0; tlv = saved_tlv; @@ -2386,7 +1660,7 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) err = tlv_expect_set (tlv); if (err) goto bailout; - tlv_skip (tlv); + tlv_parser_skip (tlv); if (opt_verbose) log_info ("skipping %s\n", where); } @@ -2396,7 +1670,7 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) gcry_free (plain); if (renewed_tlv) { - tlv_release (tlv); + tlv_parser_release (tlv); if (opt_verbose > 1) log_debug ("parser context released\n"); } @@ -2405,18 +1679,18 @@ parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) bailout: if (!err) err = gpg_error (GPG_ERR_GENERAL); - log_error ("%s(%s): lvl=%d (%s): %s - %s\n", + log_error ("%s(%s): lvl=%u (%s): %s - %s\n", __func__, where, - tlv? tlv->stacklen : 0, - tlv? tlv->lastfunc : "", - tlv ? gpg_strerror (tlv->lasterr) : "init failed", + tlv_parser_level (tlv), + tlv_parser_lastfunc (tlv), + tlv_parser_lasterrstr (tlv), gpg_strerror (err)); goto leave; } static gpg_error_t -parse_cert_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) +parse_cert_bag (struct p12_parse_ctx_s *ctx, tlv_parser_t tlv) { gpg_error_t err = 0; const char *where; @@ -2491,7 +1765,7 @@ parse_cert_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) err = tlv_expect_set (tlv); if (err) goto bailout; - tlv_skip (tlv); + tlv_parser_skip (tlv); if (opt_verbose) log_info ("skipping %s\n", where); } @@ -2503,9 +1777,9 @@ parse_cert_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) bailout: log_error ("%s(%s): lvl=%u (%s): %s - %s\n", __func__, where, - tlv? tlv->stacklen : 0, - tlv? tlv->lastfunc : "", - tlv ? gpg_strerror (tlv->lasterr) : "init failed", + tlv_parser_level (tlv), + tlv_parser_lastfunc (tlv), + tlv_parser_lasterrstr (tlv), gpg_strerror (err)); if (!err) err = gpg_error (GPG_ERR_GENERAL); @@ -2514,7 +1788,7 @@ parse_cert_bag (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) static gpg_error_t -parse_bag_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) +parse_bag_data (struct p12_parse_ctx_s *ctx, tlv_parser_t tlv) { gpg_error_t err = 0; const char *where; @@ -2559,9 +1833,9 @@ parse_bag_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) if (tlv_expect_sequence (tlv)) goto bailout; - startlevel = tlv_level (tlv); - dump_tlv_ctx ("data.outerseqs", "beginloop", tlv); - while (!(err = tlv_next (tlv)) && tlv_level (tlv) == startlevel) + startlevel = tlv_parser_level (tlv); + tlv_parser_dump_state ("data.outerseqs", "beginloop", tlv); + while (!(err = tlv_next (tlv)) && tlv_parser_level (tlv) == startlevel) { /* Expect: * SEQUENCE @@ -2595,13 +1869,13 @@ parse_bag_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) } else { - tlv_skip (tlv); + tlv_parser_skip (tlv); log_info ("unknown inner data type - skipped\n"); } } - dump_tlv_ctx ("data.outerseqs", "endloop", tlv); + tlv_parser_dump_state ("data.outerseqs", "endloop", tlv); if (!err) - tlv_set_pending (tlv); + tlv_parser_set_pending (tlv); else if (err && gpg_err_code (err) != GPG_ERR_EOF) goto bailout; err = 0; @@ -2612,11 +1886,11 @@ parse_bag_data (struct p12_parse_ctx_s *ctx, struct tlv_ctx_s *tlv) bailout: if (!err) err = gpg_error (GPG_ERR_GENERAL); - log_error ("%s(%s): lvl=%d (%s): %s - %s\n", + log_error ("%s(%s): lvl=%u (%s): %s - %s\n", __func__, where, - tlv? tlv->stacklen : 0, - tlv? tlv->lastfunc : "", - tlv ? gpg_strerror (tlv->lasterr) : "init failed", + tlv_parser_level (tlv), + tlv_parser_lastfunc (tlv), + tlv_parser_lasterrstr (tlv), gpg_strerror (err)); goto leave; } @@ -2636,7 +1910,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, { gpg_error_t err; const char *where = ""; - struct tlv_ctx_s *tlv; + tlv_parser_t tlv; struct p12_parse_ctx_s ctx = { NULL }; const unsigned char *oid; size_t oidlen; @@ -2649,7 +1923,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, ctx.certcbarg = certcbarg; ctx.password = pw; - tlv = tlv_new (buffer, length); + tlv = tlv_parser_new (buffer, length, opt_verbose); if (!tlv) { err = gpg_error_from_syserror (); @@ -2706,12 +1980,12 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, if (tlv_expect_sequence (tlv)) goto bailout; - startlevel = tlv_level (tlv); - dump_tlv_ctx ("bags", "beginloop", tlv); - while (!(err = tlv_next (tlv)) && tlv_level (tlv) == startlevel) + startlevel = tlv_parser_level (tlv); + tlv_parser_dump_state ("bags", "beginloop", tlv); + while (!(err = tlv_next (tlv)) && tlv_parser_level (tlv) == startlevel) { where = "bag-sequence"; - dump_tlv_ctx (where, NULL, tlv); + tlv_parser_dump_state (where, NULL, tlv); if (tlv_expect_sequence (tlv)) goto bailout; @@ -2744,18 +2018,18 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, } else { - tlv_skip (tlv); + tlv_parser_skip (tlv); log_info ("unknown outer bag type - skipped\n"); } } - dump_tlv_ctx ("bags", "endloop", tlv); + tlv_parser_dump_state ("bags", "endloop", tlv); if (!err) - tlv_set_pending (tlv); + tlv_parser_set_pending (tlv); else if (err && gpg_err_code (err) != GPG_ERR_EOF) goto bailout; err = 0; - tlv_release (tlv); + tlv_parser_release (tlv); if (r_curve) *r_curve = ctx.curve; else @@ -2767,10 +2041,10 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, *r_badpass = ctx.badpass; log_error ("%s(%s): @%04zu lvl=%u %s: %s - %s\n", __func__, where, - tlv? (size_t)(tlv->buffer - tlv->origbuffer):0, - tlv? tlv->stacklen : 0, - tlv? tlv->lastfunc : "", - tlv? gpg_strerror (tlv->lasterr) : "init failed", + tlv_parser_offset (tlv), + tlv_parser_level (tlv), + tlv_parser_lastfunc (tlv), + tlv_parser_lasterrstr (tlv), gpg_strerror (err)); if (ctx.privatekey) { @@ -2781,7 +2055,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, gcry_free (ctx.privatekey); ctx.privatekey = NULL; } - tlv_release (tlv); + tlv_parser_release (tlv); gcry_free (ctx.curve); if (r_curve) *r_curve = NULL; From 97708e2ac72253fa1ddbcde63b23095ac2d1604f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 24 Oct 2023 14:22:05 +0200 Subject: [PATCH 50/55] sm: Flag Brainpool curves as compliant. * sm/keylist.c (print_compliance_flags): Add arg curve. (list_cert_colon): Pass curve to the compliance check. -- GnuPG-bug-id: 6253 --- sm/keylist.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sm/keylist.c b/sm/keylist.c index d6eccfc45..e84eb31d3 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -430,7 +430,7 @@ email_kludge (const char *name) * number. NBITS is the length of the key in bits. */ static void print_compliance_flags (ksba_cert_t cert, int algo, unsigned int nbits, - estream_t fp) + const char *curvename, estream_t fp) { int indent = 0; int hashalgo; @@ -438,7 +438,7 @@ print_compliance_flags (ksba_cert_t cert, int algo, unsigned int nbits, /* Note that we do not need to test for PK_ALGO_FLAG_RSAPSS because * that is not a property of the key but one of the created * signature. */ - if (gnupg_pk_is_compliant (CO_DE_VS, algo, 0, NULL, nbits, NULL)) + if (gnupg_pk_is_compliant (CO_DE_VS, algo, 0, NULL, nbits, curvename)) { hashalgo = gcry_md_map_name (ksba_cert_get_digest_algo (cert)); if (gnupg_digest_is_compliant (CO_DE_VS, hashalgo)) @@ -629,7 +629,7 @@ list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity, if (curve) es_fputs (curve, fp); es_putc (':', fp); /* End of field 17. */ - print_compliance_flags (cert, algo, nbits, fp); + print_compliance_flags (cert, algo, nbits, curve, fp); es_putc (':', fp); /* End of field 18. */ es_putc ('\n', fp); From 2c3c049fd8a001dc9937e5ac3884831b6e25d2da Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 24 Oct 2023 14:51:16 +0200 Subject: [PATCH 51/55] sm: Flag Brainpool curves as compliant for all other operations. * sm/fingerprint.c (gpgsm_get_key_algo_info2): Rename to (gpgsm_get_key_algo_info): this. Remove the old wrapper. Adjust all callers. * sm/decrypt.c (gpgsm_decrypt): Pass the curve to the compliance checker. * sm/encrypt.c (gpgsm_encrypt): Ditto. * sm/sign.c (gpgsm_sign): Ditto. * sm/verify.c (gpgsm_verify): Ditto. -- GnuPG-bug-id: 6253 --- sm/decrypt.c | 9 ++++++--- sm/encrypt.c | 10 +++++++--- sm/export.c | 2 +- sm/fingerprint.c | 11 ++--------- sm/gpgsm.h | 5 ++--- sm/keylist.c | 2 +- sm/sign.c | 9 ++++++--- sm/verify.c | 4 ++-- 8 files changed, 27 insertions(+), 25 deletions(-) diff --git a/sm/decrypt.c b/sm/decrypt.c index 62983fe9c..787e2f5e6 100644 --- a/sm/decrypt.c +++ b/sm/decrypt.c @@ -1065,6 +1065,7 @@ gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp) int recp; estream_t in_fp = NULL; struct decrypt_filter_parm_s dfparm; + char *curve = NULL; memset (&dfparm, 0, sizeof dfparm); @@ -1309,14 +1310,15 @@ gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp) pkfpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); pkalgostr = gpgsm_pubkey_algo_string (cert, NULL); - pk_algo = gpgsm_get_key_algo_info (cert, &nbits); + xfree (curve); + pk_algo = gpgsm_get_key_algo_info (cert, &nbits, &curve); if (!opt.quiet) log_info (_("encrypted to %s key %s\n"), pkalgostr, pkfpr); /* Check compliance. */ if (!gnupg_pk_is_allowed (opt.compliance, PK_USE_DECRYPTION, - pk_algo, 0, NULL, nbits, NULL)) + pk_algo, 0, NULL, nbits, curve)) { char kidstr[10+1]; @@ -1334,7 +1336,7 @@ gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp) dfparm.is_de_vs = (dfparm.is_de_vs && gnupg_pk_is_compliant (CO_DE_VS, pk_algo, 0, - NULL, nbits, NULL)); + NULL, nbits, curve)); oops: if (rc) @@ -1512,6 +1514,7 @@ gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp) log_error ("message decryption failed: %s <%s>\n", gpg_strerror (rc), gpg_strsource (rc)); } + xfree (curve); ksba_cms_release (cms); gnupg_ksba_destroy_reader (b64reader); gnupg_ksba_destroy_writer (b64writer); diff --git a/sm/encrypt.c b/sm/encrypt.c index 6e78a0620..024f48673 100644 --- a/sm/encrypt.c +++ b/sm/encrypt.c @@ -758,11 +758,12 @@ gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, estream_t out_fp) unsigned char *encval; unsigned int nbits; int pk_algo; + char *curve = NULL; /* Check compliance. */ - pk_algo = gpgsm_get_key_algo_info (cl->cert, &nbits); + pk_algo = gpgsm_get_key_algo_info (cl->cert, &nbits, &curve); if (!gnupg_pk_is_compliant (opt.compliance, pk_algo, 0, - NULL, nbits, NULL)) + NULL, nbits, curve)) { char kidstr[10+1]; @@ -777,9 +778,12 @@ gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, estream_t out_fp) /* Fixme: When adding ECC we need to provide the curvename and * the key to gnupg_pk_is_compliant. */ if (compliant - && !gnupg_pk_is_compliant (CO_DE_VS, pk_algo, 0, NULL, nbits, NULL)) + && !gnupg_pk_is_compliant (CO_DE_VS, pk_algo, 0, NULL, nbits, curve)) compliant = 0; + xfree (curve); + curve = NULL; + rc = encrypt_dek (dek, cl->cert, pk_algo, &encval); if (rc) { diff --git a/sm/export.c b/sm/export.c index 54893b54d..a6ba40f5d 100644 --- a/sm/export.c +++ b/sm/export.c @@ -430,7 +430,7 @@ gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream, int rawmode) if (rawmode == 0) ctrl->pem_name = "PKCS12"; - else if (gpgsm_get_key_algo_info (cert, NULL) == GCRY_PK_ECC) + else if (gpgsm_get_key_algo_info (cert, NULL, NULL) == GCRY_PK_ECC) ctrl->pem_name = "EC PRIVATE KEY"; else if (rawmode == 1) ctrl->pem_name = "PRIVATE KEY"; diff --git a/sm/fingerprint.c b/sm/fingerprint.c index 5f3f6f51f..375a8647e 100644 --- a/sm/fingerprint.c +++ b/sm/fingerprint.c @@ -222,7 +222,7 @@ gpgsm_get_keygrip_hexstring (ksba_cert_t cert) * algorithm is used the name or OID of the curve is stored there; the * caller needs to free this value. */ int -gpgsm_get_key_algo_info2 (ksba_cert_t cert, unsigned int *nbits, char **r_curve) +gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits, char **r_curve) { gcry_sexp_t s_pkey; int rc; @@ -299,18 +299,11 @@ gpgsm_get_key_algo_info2 (ksba_cert_t cert, unsigned int *nbits, char **r_curve) } -int -gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits) -{ - return gpgsm_get_key_algo_info2 (cert, nbits, NULL); -} - - /* Return true if CERT is an ECC key. */ int gpgsm_is_ecc_key (ksba_cert_t cert) { - return GCRY_PK_ECC == gpgsm_get_key_algo_info2 (cert, NULL, NULL); + return GCRY_PK_ECC == gpgsm_get_key_algo_info (cert, NULL, NULL); } diff --git a/sm/gpgsm.h b/sm/gpgsm.h index a22327edc..684251fda 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -339,9 +339,8 @@ unsigned long gpgsm_get_short_fingerprint (ksba_cert_t cert, unsigned long *r_high); unsigned char *gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array); char *gpgsm_get_keygrip_hexstring (ksba_cert_t cert); -int gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits); -int gpgsm_get_key_algo_info2 (ksba_cert_t cert, unsigned int *nbits, - char **r_curve); +int gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits, + char **r_curve); int gpgsm_is_ecc_key (ksba_cert_t cert); char *gpgsm_pubkey_algo_string (ksba_cert_t cert, int *r_algoid); gcry_mpi_t gpgsm_get_rsa_modulus (ksba_cert_t cert); diff --git a/sm/keylist.c b/sm/keylist.c index e84eb31d3..ed1b74729 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -562,7 +562,7 @@ list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity, if (*truststring) es_fputs (truststring, fp); - algo = gpgsm_get_key_algo_info2 (cert, &nbits, &curve); + algo = gpgsm_get_key_algo_info (cert, &nbits, &curve); es_fprintf (fp, ":%u:%d:%s:", nbits, algo, fpr+24); ksba_cert_get_validity (cert, 0, t); diff --git a/sm/sign.c b/sm/sign.c index 235dac8cb..c3781506d 100644 --- a/sm/sign.c +++ b/sm/sign.c @@ -640,6 +640,7 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, certlist_t cl; int release_signerlist = 0; int binary_detached = detached && !ctrl->create_pem && !ctrl->create_base64; + char *curve = NULL; audit_set_type (ctrl->audit, AUDIT_TYPE_SIGN); @@ -778,7 +779,8 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, unsigned int nbits; int pk_algo; - pk_algo = gpgsm_get_key_algo_info (cl->cert, &nbits); + xfree (curve); + pk_algo = gpgsm_get_key_algo_info (cl->cert, &nbits, &curve); cl->pk_algo = pk_algo; if (opt.forced_digest_algo) @@ -838,8 +840,8 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, goto leave; } - if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_SIGNING, pk_algo, 0, - NULL, nbits, NULL)) + if (!gnupg_pk_is_allowed (opt.compliance, PK_USE_SIGNING, pk_algo, 0, + NULL, nbits, curve)) { char kidstr[10+1]; @@ -1205,6 +1207,7 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, gpg_strerror (rc), gpg_strsource (rc) ); if (release_signerlist) gpgsm_release_certlist (signerlist); + xfree (curve); ksba_cms_release (cms); gnupg_ksba_destroy_writer (b64writer); keydb_release (kh); diff --git a/sm/verify.c b/sm/verify.c index c7f4492ce..1f5c1d378 100644 --- a/sm/verify.c +++ b/sm/verify.c @@ -468,7 +468,7 @@ gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, estream_t out_fp) pkfpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); pkalgostr = gpgsm_pubkey_algo_string (cert, NULL); - pkalgo = gpgsm_get_key_algo_info2 (cert, &nbits, &pkcurve); + pkalgo = gpgsm_get_key_algo_info (cert, &nbits, &pkcurve); /* Remap the ECC algo to the algo we use. Note that EdDSA has * already been mapped. */ if (pkalgo == GCRY_PK_ECC) @@ -504,7 +504,7 @@ gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, estream_t out_fp) /* Check compliance. */ if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_VERIFICATION, - pkalgo, pkalgoflags, NULL, nbits, NULL)) + pkalgo, pkalgoflags, NULL, nbits, pkcurve)) { char kidstr[10+1]; From 164c687cb6a1cafe6c1c47456a1837046a3f00f1 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 26 Oct 2023 09:59:40 +0200 Subject: [PATCH 52/55] common: New functions timegm_u64, isotime2epoch_u64. * common/mischelp.c (timegm): Move to ... * common/gettime.c (timegm): here. On Windows use timegm_u32. (timegm_u32): New. (isotime2epoch): Factor code out to ... (isotime_make_tm): new helper. (isotime2epoch_u64): New. (_win32_timegm): Remove duplicated code. (parse_timestamp): Use of timegm. (scan_isodatestr): Fallback to isotime2epoch_u64. -- This mainly helps on 32 bit Windows. For Unix we assume everyone is using 64 bit or shall wait until the libc hackers finally provide a time64_t. GnuPG-bug-id: 6736 --- common/gettime.c | 239 ++++++++++++++++++++++++++++++++-------------- common/gettime.h | 8 +- common/mischelp.c | 77 --------------- common/mischelp.h | 6 -- 4 files changed, 176 insertions(+), 154 deletions(-) diff --git a/common/gettime.c b/common/gettime.c index c3b0c6c6c..3f1ce0c5a 100644 --- a/common/gettime.c +++ b/common/gettime.c @@ -37,6 +37,10 @@ #ifdef HAVE_LANGINFO_H #include #endif +#ifdef HAVE_W32_SYSTEM +# define WIN32_LEAN_AND_MEAN +# include +#endif /*!HAVE_W32_SYSTEM*/ #include /* We use uint64_t. */ #include "util.h" @@ -62,6 +66,111 @@ static enum { NORMAL = 0, FROZEN, FUTURE, PAST } timemode; #define JD_DIFF 1721060L + +/* + timegm() is a GNU function that might not be available everywhere. + It's basically the inverse of gmtime() - you give it a struct tm, + and get back a time_t. It differs from mktime() in that it handles + the case where the struct tm is UTC and the local environment isn't. + + Note, that this replacement implementation might not be thread-safe! + + Some BSDs don't handle the putenv("foo") case properly, so we use + unsetenv if the platform has it to remove environment variables. +*/ +#ifndef HAVE_TIMEGM +time_t +timegm (struct tm *tm) +{ +#ifdef HAVE_W32_SYSTEM + uint64_t val = timegm_u64 (tm); + if (val == (uint64_t)(-1)) + return (time_t)(-1); + return (time_t)val; +#else /* (Non thread safe implementation!) */ + + time_t answer; + char *zone; + + zone=getenv("TZ"); + putenv("TZ=UTC"); + tzset(); + answer=mktime(tm); + if(zone) + { + static char *old_zone; + + if (!old_zone) + { + old_zone = malloc(3+strlen(zone)+1); + if (old_zone) + { + strcpy(old_zone,"TZ="); + strcat(old_zone,zone); + } + } + if (old_zone) + putenv (old_zone); + } + else + gnupg_unsetenv("TZ"); + + tzset(); + return answer; +#endif +} +#endif /*!HAVE_TIMEGM*/ + + +/* Version of the GNU timegm which returns an unsigned 64 bit integer + * instead of the usually signed time_t. On error (uint64_t)(-1) is + * returned. This function is mostly here becuase on 32 bit Windows + * we have an internal API to get the system time even after + * 2023-01-19. For 32 bit Unix we need to suffer from the too short + * time_t and no system function to construct the time from a tm. */ +uint64_t +timegm_u64 (struct tm *tm) +{ +#ifdef HAVE_W32_SYSTEM + /* This one is thread safe. */ + SYSTEMTIME st; + FILETIME ft; + unsigned long long cnsecs; + + st.wYear = tm->tm_year + 1900; + st.wMonth = tm->tm_mon + 1; + st.wDay = tm->tm_mday; + st.wHour = tm->tm_hour; + st.wMinute = tm->tm_min; + st.wSecond = tm->tm_sec; + st.wMilliseconds = 0; /* Not available. */ + st.wDayOfWeek = 0; /* Ignored. */ + + /* System time is UTC thus the conversion is pretty easy. */ + if (!SystemTimeToFileTime (&st, &ft)) + { + gpg_err_set_errno (EINVAL); + return (uint64_t)(-1); + } + + cnsecs = (((unsigned long long)ft.dwHighDateTime << 32) + | ft.dwLowDateTime); + cnsecs -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ + return (uint64_t)(cnsecs / 10000000ULL); + +#else /*Unix*/ + + time_t t = timegm (tm); + if (t == (time_t)(-1)) + return (uint64_t)(-1); + if ((int64_t)t < 0) + return (uint64_t)(-1); + return (uint64_t)t; + +#endif /*Unix*/ +} + + /* Wrapper for the time(3). We use this here so we can fake the time for tests */ time_t @@ -231,7 +340,21 @@ scan_isodatestr( const char *string ) tmbuf.tm_isdst = -1; stamp = mktime( &tmbuf ); if( stamp == (time_t)-1 ) - return 0; + { + /* mktime did not work. Construct an ISO timestring for noon + * of the given day instead. We keep the use of mktime for 64 + * bit system to limit the risk of regressions. */ + gnupg_isotime_t isobuf; + uint64_t tmp64; + + snprintf (isobuf, 16, "%04d%02d%02dT120000", year, month, day); + tmp64 = isotime2epoch_u64 (isobuf); + if (tmp64 == (uint64_t)(-1)) + return 0; /* Error. */ + if (tmp64 >= (u32)(-1)) + return 0; /* Error. */ + return (u32)tmp64; + } return stamp; } @@ -386,18 +509,14 @@ string2isotime (gnupg_isotime_t atime, const char *string) } -/* Scan an ISO timestamp and return an Epoch based timestamp. The - only supported format is "yyyymmddThhmmss[Z]" delimited by white - space, nul, a colon or a comma. Returns (time_t)(-1) for an - invalid string. */ -time_t -isotime2epoch (const char *string) +/* Helper for isotime2epoch. Returns 0 on success. */ +static int +isotime_make_tm (const char *string, struct tm *tmbuf) { int year, month, day, hour, minu, sec; - struct tm tmbuf; if (!isotime_p (string)) - return (time_t)(-1); + return -1; year = atoi_4 (string); month = atoi_2 (string + 4); @@ -409,20 +528,48 @@ isotime2epoch (const char *string) /* Basic checks. */ if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minu > 59 || sec > 61 ) + return -1; + + memset (tmbuf, 0, sizeof *tmbuf); + tmbuf->tm_sec = sec; + tmbuf->tm_min = minu; + tmbuf->tm_hour = hour; + tmbuf->tm_mday = day; + tmbuf->tm_mon = month-1; + tmbuf->tm_year = year - 1900; + tmbuf->tm_isdst = -1; + return 0; +} + + +/* Scan an ISO timestamp and return an Epoch based timestamp. The + only supported format is "yyyymmddThhmmss[Z]" delimited by white + space, nul, a colon or a comma. Returns (time_t)(-1) for an + invalid string. */ +time_t +isotime2epoch (const char *string) +{ + struct tm tmbuf; + + if (isotime_make_tm (string, &tmbuf)) return (time_t)(-1); - memset (&tmbuf, 0, sizeof tmbuf); - tmbuf.tm_sec = sec; - tmbuf.tm_min = minu; - tmbuf.tm_hour = hour; - tmbuf.tm_mday = day; - tmbuf.tm_mon = month-1; - tmbuf.tm_year = year - 1900; - tmbuf.tm_isdst = -1; return timegm (&tmbuf); } +uint64_t +isotime2epoch_u64 (const char *string) +{ + struct tm tmbuf; + + if (isotime_make_tm (string, &tmbuf)) + return (uint64_t)(-1); + + return timegm_u64 (&tmbuf); +} + + /* Convert an Epoch time to an iso time stamp. */ void epoch2isotime (gnupg_isotime_t timebuf, time_t atime) @@ -476,41 +623,6 @@ isodate_human_to_tm (const char *string, struct tm *t) } -/* This function is a copy of gpgme/src/conversion.c:_gpgme_timegm. - If you change it, then update the other one too. */ -#ifdef HAVE_W32_SYSTEM -static time_t -_win32_timegm (struct tm *tm) -{ - /* This one is thread safe. */ - SYSTEMTIME st; - FILETIME ft; - unsigned long long cnsecs; - - st.wYear = tm->tm_year + 1900; - st.wMonth = tm->tm_mon + 1; - st.wDay = tm->tm_mday; - st.wHour = tm->tm_hour; - st.wMinute = tm->tm_min; - st.wSecond = tm->tm_sec; - st.wMilliseconds = 0; /* Not available. */ - st.wDayOfWeek = 0; /* Ignored. */ - - /* System time is UTC thus the conversion is pretty easy. */ - if (!SystemTimeToFileTime (&st, &ft)) - { - gpg_err_set_errno (EINVAL); - return (time_t)(-1); - } - - cnsecs = (((unsigned long long)ft.dwHighDateTime << 32) - | ft.dwLowDateTime); - cnsecs -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ - return (time_t)(cnsecs / 10000000ULL); -} -#endif - - /* Parse the string TIMESTAMP into a time_t. The string may either be seconds since Epoch or in the ISO 8601 format like "20390815T143012". Returns 0 for an empty string or seconds since @@ -519,7 +631,11 @@ _win32_timegm (struct tm *tm) This function is a copy of gpgme/src/conversion.c:_gpgme_parse_timestamp. If you change it, - then update the other one too. */ + then update the other one too. + + FIXME: Replace users of this function by one of the more modern + functions or change the return type to u64. +*/ time_t parse_timestamp (const char *timestamp, char **endp) { @@ -555,24 +671,7 @@ parse_timestamp (const char *timestamp, char **endp) buf.tm_min = atoi_2 (timestamp+11); buf.tm_sec = atoi_2 (timestamp+13); -#ifdef HAVE_W32_SYSTEM - return _win32_timegm (&buf); -#else -#ifdef HAVE_TIMEGM return timegm (&buf); -#else - { - time_t tim; - - putenv ("TZ=UTC"); - tim = mktime (&buf); -#ifdef __GNUC__ -#warning fixme: we must somehow reset TZ here. It is not threadsafe anyway. -#endif - return tim; - } -#endif /* !HAVE_TIMEGM */ -#endif /* !HAVE_W32_SYSTEM */ } else return (time_t)strtoul (timestamp, endp, 10); diff --git a/common/gettime.h b/common/gettime.h index 18f65ab1a..e216ddd36 100644 --- a/common/gettime.h +++ b/common/gettime.h @@ -32,7 +32,7 @@ #include /* We need time_t. */ #include /* We need gpg_error_t. */ - +#include /* We use uint64_t. */ /* A type to hold the ISO time. Note that this is the same as the KSBA type ksba_isotime_t. */ @@ -43,6 +43,11 @@ typedef char gnupg_isotime_t[16]; #define GNUPG_ISOTIME_NONE \ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +#ifndef HAVE_TIMEGM +time_t timegm (struct tm *tm); +#endif /*!HAVE_TIMEGM*/ +uint64_t timegm_u64 (struct tm *tm); + time_t gnupg_get_time (void); struct tm *gnupg_gmtime (const time_t *timep, struct tm *result); void gnupg_get_isotime (gnupg_isotime_t timebuf); @@ -57,6 +62,7 @@ int isotime_p (const char *string); int isotime_human_p (const char *string, int date_only); size_t string2isotime (gnupg_isotime_t atime, const char *string); time_t isotime2epoch (const char *string); +uint64_t isotime2epoch_u64 (const char *string); void epoch2isotime (gnupg_isotime_t timebuf, time_t atime); int isodate_human_to_tm (const char *string, struct tm *t); time_t parse_timestamp (const char *timestamp, char **endp); diff --git a/common/mischelp.c b/common/mischelp.c index ee8500297..ef70c9d83 100644 --- a/common/mischelp.c +++ b/common/mischelp.c @@ -126,80 +126,3 @@ same_file_p (const char *name1, const char *name2) } return yes; } - - -/* - timegm() is a GNU function that might not be available everywhere. - It's basically the inverse of gmtime() - you give it a struct tm, - and get back a time_t. It differs from mktime() in that it handles - the case where the struct tm is UTC and the local environment isn't. - - Note, that this replacement implementation might not be thread-safe! - - Some BSDs don't handle the putenv("foo") case properly, so we use - unsetenv if the platform has it to remove environment variables. -*/ -#ifndef HAVE_TIMEGM -time_t -timegm (struct tm *tm) -{ -#ifdef HAVE_W32_SYSTEM - /* This one is thread safe. */ - SYSTEMTIME st; - FILETIME ft; - unsigned long long cnsecs; - - st.wYear = tm->tm_year + 1900; - st.wMonth = tm->tm_mon + 1; - st.wDay = tm->tm_mday; - st.wHour = tm->tm_hour; - st.wMinute = tm->tm_min; - st.wSecond = tm->tm_sec; - st.wMilliseconds = 0; /* Not available. */ - st.wDayOfWeek = 0; /* Ignored. */ - - /* System time is UTC thus the conversion is pretty easy. */ - if (!SystemTimeToFileTime (&st, &ft)) - { - gpg_err_set_errno (EINVAL); - return (time_t)(-1); - } - - cnsecs = (((unsigned long long)ft.dwHighDateTime << 32) - | ft.dwLowDateTime); - cnsecs -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ - return (time_t)(cnsecs / 10000000ULL); - -#else /* (Non thread safe implementation!) */ - - time_t answer; - char *zone; - - zone=getenv("TZ"); - putenv("TZ=UTC"); - tzset(); - answer=mktime(tm); - if(zone) - { - static char *old_zone; - - if (!old_zone) - { - old_zone = malloc(3+strlen(zone)+1); - if (old_zone) - { - strcpy(old_zone,"TZ="); - strcat(old_zone,zone); - } - } - if (old_zone) - putenv (old_zone); - } - else - gnupg_unsetenv("TZ"); - - tzset(); - return answer; -#endif -} -#endif /*!HAVE_TIMEGM*/ diff --git a/common/mischelp.h b/common/mischelp.h index bdee5a443..7359ec29e 100644 --- a/common/mischelp.h +++ b/common/mischelp.h @@ -38,12 +38,6 @@ int same_file_p (const char *name1, const char *name2); -#ifndef HAVE_TIMEGM -#include -time_t timegm (struct tm *tm); -#endif /*!HAVE_TIMEGM*/ - - #define DIM(v) (sizeof(v)/sizeof((v)[0])) #define DIMof(type,member) DIM(((type *)0)->member) From a4fe307b5535ed350fff63941aaa0b19ee2e683a Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 26 Oct 2023 12:01:44 +0200 Subject: [PATCH 53/55] gpg: Allow expiration time after 2038-01-19 on 32 bit Windows. * g10/keygen.c (parse_expire_string_with_ct): Use isotime2epoch_u64. (parse_creation_string): Ditto. -- GnuPG-bug-id: 6736 --- g10/keygen.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/g10/keygen.c b/g10/keygen.c index 06fc39aa1..87940722d 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -2748,6 +2748,7 @@ parse_expire_string_with_ct (const char *string, u32 creation_time) u32 seconds; u32 abs_date = 0; time_t tt; + uint64_t tmp64; u32 curtime; if (creation_time == (u32)-1) @@ -2763,11 +2764,16 @@ parse_expire_string_with_ct (const char *string, u32 creation_time) else if ((abs_date = scan_isodatestr(string)) && (abs_date+86400/2) > curtime) seconds = (abs_date+86400/2) - curtime; - else if ((tt = isotime2epoch (string)) != (time_t)(-1)) - seconds = (u32)tt - curtime; + else if ((tt = isotime2epoch_u64 (string)) != (uint64_t)(-1)) + { + tmp64 = tt - curtime; + if (tmp64 >= (u32)(-1)) + seconds = (u32)(-1) - 1; /* cap value. */ + else + seconds = (u32)tmp64; + } else if ((mult = check_valid_days (string))) { - uint64_t tmp64; tmp64 = scan_secondsstr (string) * 86400L * mult; if (tmp64 >= (u32)(-1)) seconds = (u32)(-1) - 1; /* cap value. */ @@ -2800,8 +2806,13 @@ parse_creation_string (const char *string) seconds = scan_secondsstr (string+8); else if ( !(seconds = scan_isodatestr (string))) { - time_t tmp = isotime2epoch (string); - seconds = (tmp == (time_t)(-1))? 0 : tmp; + uint64_t tmp = isotime2epoch_u64 (string); + if (tmp == (uint64_t)(-1)) + seconds = 0; + else if (tmp > (u32)(-1)) + seconds = 0; + else + seconds = tmp; } return seconds; } From 95b9a31f81e4a56518269d2476b54a1f10fe8b3e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 27 Oct 2023 14:20:47 +0200 Subject: [PATCH 54/55] gpg: Fix minor memory leak during certain smartcard operations. * g10/keygen.c (card_store_key_with_backup): Fix memory leak on error. --- g10/keygen.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/g10/keygen.c b/g10/keygen.c index 87940722d..2f8528278 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -5386,17 +5386,26 @@ card_store_key_with_backup (ctrl_t ctrl, PKT_public_key *sub_psk, { ecdh_param_str = ecdh_param_str_from_pk (sk); if (!ecdh_param_str) - return gpg_error_from_syserror (); + { + free_public_key (sk); + return gpg_error_from_syserror (); + } } err = hexkeygrip_from_pk (sk, &hexgrip); if (err) - goto leave; + { + xfree (ecdh_param_str); + free_public_key (sk); + goto leave; + } memset(&info, 0, sizeof (info)); rc = agent_scd_getattr ("SERIALNO", &info); if (rc) { + xfree (ecdh_param_str); + free_public_key (sk); err = (gpg_error_t)rc; goto leave; } From 678c81902750a5a40573d708c5e14dad5225121e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 27 Oct 2023 14:00:59 +0200 Subject: [PATCH 55/55] w32: Use utf8 for the asctimestamp function. * common/gettime.c (asctimestamp) [W32]: Use ".UTF8" for the locale. -- This has been suggested by the reporter of GnuPG-bug-id: 6741 --- NEWS | 2 ++ common/gettime.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index f4ecb1d64..7f6162cbb 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ Noteworthy changes in version 2.4.4 (unreleased) ------------------------------------------------ + * Fix garbled time output in non-English Windows. [T6741] + Release-info: https://dev.gnupg.org/T6578 diff --git a/common/gettime.c b/common/gettime.c index 3f1ce0c5a..136c47ca7 100644 --- a/common/gettime.c +++ b/common/gettime.c @@ -850,7 +850,7 @@ asctimestamp (u32 stamp) * 2018 has a lot of additional support but that will for sure * break other things. We should move to ISO strings to get * rid of such problems. */ - setlocale (LC_TIME, ""); + setlocale (LC_TIME, ".UTF8"); done = 1; /* log_debug ("LC_ALL now '%s'\n", setlocale (LC_ALL, NULL)); */ /* log_debug ("LC_TIME now '%s'\n", setlocale (LC_TIME, NULL)); */