From f35e7dbf9e71f847f3c7da40542bd6c37f43711e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 24 Jan 2023 09:29:04 +0100 Subject: [PATCH 01/60] common: Slight redefinition of nvc_get_boolean. * common/name-value.c (nvc_get_boolean): Rewrite. -- The function may now return a positive or negative number instead of just 1 for true. All callers were already prepared for this. GnuPG-bug-id: 6212 --- agent/gpg-agent.c | 3 ++- common/name-value.c | 14 +++++++++----- common/name-value.h | 3 ++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 412eb43e1..381999cea 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -147,6 +147,7 @@ enum cmd_and_opt_values oS2KCalibration, oAutoExpandSecmem, oListenBacklog, + oInactivityTimeout, oWriteEnvFile, @@ -185,7 +186,7 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_noconffile (oNoOptions, "no-options", "@"), - + ARGPARSE_s_i (oInactivityTimeout, "inactivity-timeout", "@"), ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")), diff --git a/common/name-value.c b/common/name-value.c index d1d0a3f6f..67429e47f 100644 --- a/common/name-value.c +++ b/common/name-value.c @@ -608,13 +608,14 @@ nvc_get_string (nvc_t nvc, const char *name) } -/* Return true if NAME exists and its value is true; that is either - * "yes", "true", or a decimal value unequal to 0. */ +/* Return true (ie. a non-zero value) if NAME exists and its value is + * true; that is either "yes", "true", or a decimal value unequal to 0. */ int nvc_get_boolean (nvc_t nvc, const char *name) { nve_t item; const char *s; + int n; if (!nvc) return 0; @@ -622,9 +623,12 @@ nvc_get_boolean (nvc_t nvc, const char *name) if (!item) return 0; s = nve_value (item); - if (s && (atoi (s) - || !ascii_strcasecmp (s, "yes") - || !ascii_strcasecmp (s, "true"))) + if (!s) + return 0; + n = atoi (s); + if (n) + return n; + if (!ascii_strcasecmp (s, "yes") || !ascii_strcasecmp (s, "true")) return 1; return 0; } diff --git a/common/name-value.h b/common/name-value.h index cf854e04d..504b5d0f0 100644 --- a/common/name-value.h +++ b/common/name-value.h @@ -75,7 +75,8 @@ nve_t nve_next_value (nve_t entry, const char *name); /* Return the string for the first entry in NVC with NAME or NULL. */ const char *nvc_get_string (nvc_t nvc, const char *name); -/* Return a boolean value for the first entry in NVC with NAME. */ +/* Return a boolean value (zero or non-zero) for the first entry in + * NVC with NAME. */ int nvc_get_boolean (nvc_t nvc, const char *name); From 3de5e00d04a58cbb76e04ab1187c459257d1726b Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 26 Jan 2023 15:24:24 +0900 Subject: [PATCH 02/60] po: Update Japanese Translation. -- Signed-off-by: NIIBE Yutaka --- po/ja.po | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/po/ja.po b/po/ja.po index 92902efe5..fb62f7737 100644 --- a/po/ja.po +++ b/po/ja.po @@ -9,9 +9,9 @@ # msgid "" msgstr "" -"Project-Id-Version: gnupg 2.3.8\n" +"Project-Id-Version: gnupg 2.4.0\n" "Report-Msgid-Bugs-To: translations@gnupg.org\n" -"PO-Revision-Date: 2022-09-01 14:45+0900\n" +"PO-Revision-Date: 2023-01-26 15:24+0900\n" "Last-Translator: NIIBE Yutaka \n" "Language-Team: none\n" "Language: ja\n" @@ -378,18 +378,16 @@ msgid "ignore requests to change the X display" msgstr "Xディスプレイの変更要求を無視する" msgid "enable ssh support" -msgstr "sshサポートを有功にする" +msgstr "sshサポートを有効にする" msgid "|ALGO|use ALGO to show ssh fingerprints" msgstr "|ALGO|ssh署名の表示にALGOを使う" msgid "enable putty support" -msgstr "puttyサポートを有功にする" +msgstr "puttyサポートを有効にする" -#, fuzzy -#| msgid "enable putty support" msgid "enable Win32-OpenSSH support" -msgstr "puttyサポートを有功にする" +msgstr "Win32-OpenSSH サポートを有効する" msgid "Options controlling the security" msgstr "セキュリティを制御するオプション" @@ -1768,10 +1766,9 @@ msgstr "*警告*: 鍵%sは、%sモードでは、暗号化に適しません\n" msgid "error creating passphrase: %s\n" msgstr "パスフレーズの作成エラー: %s\n" -#, fuzzy, c-format -#| msgid "can't use a symmetric ESK packet due to the S2K mode\n" +#, c-format msgid "can't use a SKESK packet due to the S2K mode\n" -msgstr "S2Kモードのため、共通鍵ESKパケットを使えません\n" +msgstr "S2Kモードのため、SKESKパケットを使えません\n" #, c-format msgid "using cipher %s.%s\n" @@ -1840,18 +1837,14 @@ msgstr "エクスポートの際、利用できない部分を除去する" msgid "remove as much as possible from key during export" msgstr "エクスポートの際、できるだけ除去する" -#, fuzzy -#| msgid "generate a revocation certificate" msgid "export only revocation certificates" -msgstr "失効証明書を生成" +msgstr "失効証明書だけをエクスポートする" msgid "use the GnuPG key backup format" msgstr "GnuPGの鍵のバックアップフォーマットを使います" -#, fuzzy -#| msgid "exporting secret keys not allowed\n" msgid "export secret keys using the GnuPG format" -msgstr "秘密鍵のエクスポートは認められません\n" +msgstr "GnuPG フォーマットで秘密鍵をエクスポートをする" msgid " - skipped" msgstr " - スキップされました" @@ -2283,10 +2276,8 @@ msgstr "鍵の一覧に鍵リングの名前を表示する" msgid "show expiration dates during signature listings" msgstr "署名の一覧時に有効期限の日付を表示する" -#, fuzzy -#| msgid "list preferences (expert)" msgid "show preferences" -msgstr "優先指定の一覧 (エキスパート)" +msgstr "優先指定の表示" #, c-format msgid "unknown TOFU policy '%s'\n" From 1ab21c82c342618051319c93125945c62142ec03 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 26 Jan 2023 11:39:19 +0100 Subject: [PATCH 03/60] gpgtar: Allow decryption from stdin. * tools/gpgtar.c (main): Revamp switch and fix usage test for aDecrypt and aList. -- GnuPG-bug-id: 6355 --- doc/tools.texi | 8 ++++++-- tools/gpgtar.c | 31 ++++++++++++++----------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/doc/tools.texi b/doc/tools.texi index e22a2285f..5fa21c66a 100644 --- a/doc/tools.texi +++ b/doc/tools.texi @@ -1907,6 +1907,8 @@ Put given files and directories into a vanilla ``ustar'' archive. @item --extract @opindex extract Extract all files from a vanilla ``ustar'' archive. +If no file name is given (or it is "-") the archive is taken from +stdin. @item --encrypt @itemx -e @@ -1918,7 +1920,8 @@ be decrypted via a secret key or a passphrase. @item --decrypt @itemx -d @opindex decrypt -Extract all files from an encrypted archive. +Extract all files from an encrypted archive. If no file name is given +(or it is "-") the archive is taken from stdin. @item --sign @itemx -s @@ -1929,7 +1932,8 @@ encrypted archive. @item --list-archive @itemx -t @opindex list-archive -List the contents of the specified archive. +List the contents of the specified archive. If no file name is given +(or it is "-") the archive is taken from stdin. @item --symmetric @itemx -c diff --git a/tools/gpgtar.c b/tools/gpgtar.c index 8461666b9..b412f3ffb 100644 --- a/tools/gpgtar.c +++ b/tools/gpgtar.c @@ -499,17 +499,27 @@ main (int argc, char **argv) switch (cmd) { + case aDecrypt: case aList: if (argc > 1) gpgrt_usage (1); - fname = argc ? *argv : NULL; + fname = (argc && strcmp (*argv, "-"))? *argv : NULL; if (opt.filename) log_info ("note: ignoring option --set-filename\n"); if (files_from) log_info ("note: ignoring option --files-from\n"); - err = gpgtar_list (fname, !skip_crypto); - if (err && log_get_errorcount (0) == 0) - log_error ("listing archive failed: %s\n", gpg_strerror (err)); + if (cmd == aDecrypt) + { + err = gpgtar_extract (fname, !skip_crypto); + if (err && !log_get_errorcount (0)) + log_error ("extracting archive failed: %s\n", gpg_strerror (err)); + } + else + { + err = gpgtar_list (fname, !skip_crypto); + if (err && !log_get_errorcount (0)) + log_error ("listing archive failed: %s\n", gpg_strerror (err)); + } break; case aEncrypt: @@ -530,19 +540,6 @@ main (int argc, char **argv) log_error ("creating archive failed: %s\n", gpg_strerror (err)); break; - case aDecrypt: - if (argc != 1) - gpgrt_usage (1); - if (opt.outfile) - log_info ("note: ignoring option --output\n"); - if (files_from) - log_info ("note: ignoring option --files-from\n"); - fname = argc ? *argv : NULL; - err = gpgtar_extract (fname, !skip_crypto); - if (err && log_get_errorcount (0) == 0) - log_error ("extracting archive failed: %s\n", gpg_strerror (err)); - break; - default: log_error (_("invalid command (there is no implicit command)\n")); break; From d5fe8ba7214b995eddfa16740b33f0866e4d5768 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 26 Jan 2023 11:54:44 +0100 Subject: [PATCH 04/60] gpgtar: Fix parent directory creation bug * tools/gpgtar-extract.c (extract_directory): Ignore EEXIST on parent directory creation. --- tools/gpgtar-extract.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/gpgtar-extract.c b/tools/gpgtar-extract.c index bc2e672de..e690dde7a 100644 --- a/tools/gpgtar-extract.c +++ b/tools/gpgtar-extract.c @@ -198,6 +198,8 @@ extract_directory (const char *dirname, tar_header_t hdr, strlist_t exthdr) { *p = 0; rc = gnupg_mkdir (fname, "-rwx------"); + if (gpg_err_code (rc) == GPG_ERR_EEXIST) + rc = 0; *p = '/'; if (rc) break; From f84264e8acf742793c73ce78491cab61fac37051 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 30 Jan 2023 15:23:38 +0100 Subject: [PATCH 05/60] gpgtar: Emit progress status lines in create mode. * tools/gpgtar.h (opt): Add field status_stream. * tools/gpgtar.c (main): Set status_stream. * tools/gpgtar-create.c (global_header_count): Rename to global_total_files. (global_written_files): New. (global_total_data, global_written_data): New. (struct scanctrl_s): Add field file_count. (write_progress): New. (write_file): Add arg skipped_open. Don't bail out immediatly on open error. Write progress lines. (gpgtar_create): Write progress lines. Print info aout skipped files. -- GnuPG-bug-id: 6363 --- doc/DETAILS | 8 ++- tools/gpgtar-create.c | 120 +++++++++++++++++++++++++++++++++++++----- tools/gpgtar.c | 26 +++++++++ tools/gpgtar.h | 1 + 4 files changed, 142 insertions(+), 13 deletions(-) diff --git a/doc/DETAILS b/doc/DETAILS index a3fe802a2..e01b74ac1 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -1151,7 +1151,13 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: - learncard :: Send by the agent and gpgsm while learing the data of a smartcard. - card_busy :: A smartcard is still working - - scd_locked :: Waiting for other clients to unlock the scdaemon + - scd_locked :: Waiting for other clients to unlock the + scdaemon + - gpgtar :: Here has a special meaning: 's' + indicates total size and 'c' file count. A + of zero indicates that gpgtar is in the + scanning phase. A positive is used in + the writing phase. When refers to a file path, it may be truncated. diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c index f52295b5c..26d37a332 100644 --- a/tools/gpgtar-create.c +++ b/tools/gpgtar-create.c @@ -1,5 +1,5 @@ /* gpgtar-create.c - Create a TAR archive - * Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH + * Copyright (C) 2016-2017, 2019-2023 g10 Code GmbH * Copyright (C) 2010, 2012, 2013 Werner Koch * Copyright (C) 2010 Free Software Foundation, Inc. * @@ -49,10 +49,19 @@ #define lstat(a,b) gnupg_stat ((a), (b)) #endif +/* Number of files to be write. */ +static unsigned long global_total_files; + +/* Count the number of written file and thus headers. Extended + * headers are not counted. */ +static unsigned long global_written_files; + +/* Total data expected to be written. */ +static unsigned long long global_total_data; + +/* Number of data bytes written so far. */ +static unsigned long long global_written_data; -/* Count the number of written headers. Extended headers are not - * counted. */ -static unsigned long global_header_count; /* Object to control the file scanning. */ @@ -62,10 +71,62 @@ struct scanctrl_s { tar_header_t flist; tar_header_t *flist_tail; + unsigned long file_count; int nestlevel; }; +/* See ../g10/progress.c:write_status_progress for some background. */ +static void +write_progress (int countmode, unsigned long long current, + unsigned long long total_arg) +{ + char units[] = "BKMGTPEZY?"; + int unitidx = 0; + uint64_t total = total_arg; + + if (!opt.status_stream) + return; /* Not enabled. */ + + if (countmode) + { + if (total && current > total) + current = total; + } + else if (total) /* Size mode: This may use units. */ + { + if (current > total) + current = total; + + while (total > 1024*1024) + { + total /= 1024; + current /= 1024; + unitidx++; + } + } + else /* Size mode */ + { + while (current > 1024*1024) + { + current /= 1024; + unitidx++; + } + } + + if (unitidx > sizeof units - 1) + unitidx = sizeof units - 1; + + if (countmode) + es_fprintf (opt.status_stream, "[GNUPG:] PROGRESS gpgtar c %zu %zu\n", + (size_t)current, (size_t)total); + else + es_fprintf (opt.status_stream, "[GNUPG:] PROGRESS gpgtar s %zu %zu %c%s\n", + (size_t)current, (size_t)total, + units[unitidx], + unitidx? "iB" : ""); +} + /* On Windows convert name to UTF8 and return it; caller must release * the result. On Unix or if ALREADY_UTF8 is set, this function is a @@ -300,6 +361,12 @@ add_entry (const char *dname, const char *entryname, scanctrl_t scanctrl) gpgtar_print_header (hdr, NULL, log_get_stream ()); *scanctrl->flist_tail = hdr; scanctrl->flist_tail = &hdr->next; + scanctrl->file_count++; + /* Print a progress line during scnanning in increments of 5000 + * and not of 100 as we doing during write: Scanning is of + * course much faster. */ + if (!(scanctrl->file_count % 5000)) + write_progress (1, scanctrl->file_count, 0); } return 0; @@ -709,7 +776,7 @@ build_header (void *record, tar_header_t hdr, strlist_t *r_exthdr) * will do. To ease testing we also put in the PID. The * count is bumped after the header has been written. */ snprintf (raw->name, sizeof raw->name-1, "_@paxheader.%u.%lu", - (unsigned int)getpid(), global_header_count + 1); + (unsigned int)getpid(), global_written_files + 1); } } @@ -881,7 +948,7 @@ write_extended_header (estream_t stream, const void *record, strlist_t exthdr) static gpg_error_t -write_file (estream_t stream, tar_header_t hdr) +write_file (estream_t stream, tar_header_t hdr, unsigned int *skipped_open) { gpg_error_t err; char record[RECORDSIZE]; @@ -895,7 +962,7 @@ write_file (estream_t stream, tar_header_t hdr) { if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED) { - log_info ("skipping unsupported file '%s'\n", hdr->name); + log_info ("silently skipping unsupported file '%s'\n", hdr->name); err = 0; } return err; @@ -907,9 +974,12 @@ write_file (estream_t stream, tar_header_t hdr) if (!infp) { err = gpg_error_from_syserror (); - log_error ("can't open '%s': %s - skipped\n", + log_info ("can't open '%s': %s - skipped\n", hdr->name, gpg_strerror (err)); - return err; + ++*skipped_open; + if (!*skipped_open) /* Protect against overflow. */ + --*skipped_open; + return 0; } } else @@ -920,7 +990,9 @@ write_file (estream_t stream, tar_header_t hdr) err = write_record (stream, record); if (err) goto leave; - global_header_count++; + global_written_files++; + if (!(global_written_files % 100)) + write_progress (1, global_written_files, global_total_files); if (hdr->typeflag == TF_REGULAR) { @@ -946,6 +1018,9 @@ write_file (estream_t stream, tar_header_t hdr) err = write_record (stream, record); if (err) goto leave; + global_written_data += nbytes; + if (!((global_written_data/nbytes) % (2048*100))) + write_progress (0, global_written_data, global_total_data); } nread = es_fread (record, 1, 1, infp); if (nread) @@ -995,6 +1070,7 @@ gpgtar_create (char **inpattern, const char *files_from, int null_names, estream_t outstream = NULL; int eof_seen = 0; pid_t pid = (pid_t)(-1); + unsigned int skipped_open = 0; memset (scanctrl, 0, sizeof *scanctrl); scanctrl->flist_tail = &scanctrl->flist; @@ -1137,6 +1213,17 @@ gpgtar_create (char **inpattern, const char *files_from, int null_names, if (files_from_stream && files_from_stream != es_stdin) es_fclose (files_from_stream); + global_total_files = global_total_data = 0; + global_written_files = global_written_data = 0; + for (hdr = scanctrl->flist; hdr; hdr = hdr->next) + { + global_total_files++; + global_total_data += hdr->size; + } + write_progress (1, 0, global_total_files); + write_progress (0, 0, global_total_data); + + if (encrypt || sign) { strlist_t arg; @@ -1228,17 +1315,20 @@ gpgtar_create (char **inpattern, const char *files_from, int null_names, es_set_binary (outstream); } - + skipped_open = 0; for (hdr = scanctrl->flist; hdr; hdr = hdr->next) { - err = write_file (outstream, hdr); + err = write_file (outstream, hdr, &skipped_open); if (err) goto leave; } + err = write_eof_mark (outstream); if (err) goto leave; + write_progress (1, global_written_files, global_total_files); + write_progress (0, global_written_data, global_total_data); if (pid != (pid_t)(-1)) { @@ -1259,6 +1349,12 @@ gpgtar_create (char **inpattern, const char *files_from, int null_names, } } + if (skipped_open) + { + log_info ("number of skipped files: %u\n", skipped_open); + log_error ("exiting with failure status due to previous errors\n"); + } + leave: if (!err) { diff --git a/tools/gpgtar.c b/tools/gpgtar.c index b412f3ffb..5d16b70a7 100644 --- a/tools/gpgtar.c +++ b/tools/gpgtar.c @@ -491,6 +491,32 @@ main (int argc, char **argv) log_info (_("NOTE: '%s' is not considered an option\n"), argv[i]); } + /* Set status stream for our own use of --status-fd. The original + * status fd is passed verbatim to gpg. */ + if (opt.status_fd) + { + int fd = translate_sys2libc_fd_int (opt.status_fd, 1); + + if (!gnupg_fd_valid (fd)) + log_fatal ("status-fd is invalid: %s\n", strerror (errno)); + + if (fd == 1) + opt.status_stream = es_stdout; + else if (fd == 2) + opt.status_stream = es_stderr; + else + { + opt.status_stream = es_fdopen (fd, "w"); + if (opt.status_stream) + es_setvbuf (opt.status_stream, NULL, _IOLBF, 0); + } + if (!opt.status_stream) + { + log_fatal ("can't open fd %d for status output: %s\n", + fd, strerror (errno)); + } + } + if (! opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); diff --git a/tools/gpgtar.h b/tools/gpgtar.h index 0854064fb..303db0045 100644 --- a/tools/gpgtar.h +++ b/tools/gpgtar.h @@ -45,6 +45,7 @@ struct int answer_yes; int answer_no; int status_fd; + estream_t status_stream; int require_compliance; int with_log; } opt; From 851ac88bdeb0a760b73bc5532983752c9eefa80f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 30 Jan 2023 15:56:11 +0100 Subject: [PATCH 06/60] gpgtar: Fix new --status-fd handling. -- Fixes-commit: f84264e8acf742793c73ce78491cab61fac37051 --- tools/gpgtar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/gpgtar.c b/tools/gpgtar.c index 5d16b70a7..cfd760499 100644 --- a/tools/gpgtar.c +++ b/tools/gpgtar.c @@ -493,7 +493,7 @@ main (int argc, char **argv) /* Set status stream for our own use of --status-fd. The original * status fd is passed verbatim to gpg. */ - if (opt.status_fd) + if (opt.status_fd != -1) { int fd = translate_sys2libc_fd_int (opt.status_fd, 1); From d11d3cf85b948cb2aedff230fc19fe5925d1ecd1 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 30 Jan 2023 15:59:15 +0100 Subject: [PATCH 07/60] gpg: For readibility use macro instead of integers in key-clean. * g10/key-clean.c (NF_USABLE, NF_CONSIDER): New. (NF_PROCESSED, NF_REVOC, NF_NOKEY): New. --- g10/key-clean.c | 76 +++++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/g10/key-clean.c b/g10/key-clean.c index 9320428c8..c8a6efe50 100644 --- a/g10/key-clean.c +++ b/g10/key-clean.c @@ -35,14 +35,19 @@ #include "key-clean.h" +#define NF_USABLE 8 /* Usable signature and not a revocation. */ +#define NF_CONSIDER 9 /* Internal use. */ +#define NF_PROCESSED 10 /* Internal use. */ +#define NF_REVOC 11 /* Usable revocation. */ +#define NF_NOKEY 12 /* Key not available. */ + /* * Mark the signature of the given UID which are used to certify it. * To do this, we first remove all signatures which are not valid and * from the remaining we look for the latest one. If this is not a * certification revocation signature we mark the signature by setting - * node flag bit 8. Revocations are marked with flag 11, and sigs - * from unavailable keys are marked with flag 12. Note that flag bits - * 9 and 10 are used for internal purposes. + * node flag bit NF_USABLE. Revocations are marked with NF_REVOC, and + * sigs from unavailable keys are marked with NF_NOKEY. */ void mark_usable_uid_certs (ctrl_t ctrl, kbnode_t keyblock, kbnode_t uidnode, @@ -57,7 +62,8 @@ mark_usable_uid_certs (ctrl_t ctrl, kbnode_t keyblock, kbnode_t uidnode, { int rc; - node->flag &= ~(1<<8 | 1<<9 | 1<<10 | 1<<11 | 1<<12); + node->flag &= ~(1<pkt->pkttype == PKT_USER_ID || node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) @@ -81,19 +87,20 @@ mark_usable_uid_certs (ctrl_t ctrl, kbnode_t keyblock, kbnode_t uidnode, /* we ignore anything that won't verify, but tag the no_pubkey case */ if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY) - node->flag |= 1<<12; + node->flag |= 1<flag |= 1<<9; + node->flag |= 1<next) - node->flag &= ~(1<<8 | 1<<9 | 1<<10 | 1<<11 | 1<<12); + node->flag &= ~(1<next; node; node = node->next) @@ -105,11 +112,11 @@ mark_usable_uid_certs (ctrl_t ctrl, kbnode_t keyblock, kbnode_t uidnode, if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) break; - if ( !(node->flag & (1<<9)) ) + if ( !(node->flag & (1<flag & (1<<10)) ) + if ( (node->flag & (1<flag |= (1<<10); /* mark this node as processed */ + node->flag |= (1<pkt->pkt.signature; signode = node; sigdate = sig->timestamp; @@ -121,14 +128,14 @@ mark_usable_uid_certs (ctrl_t ctrl, kbnode_t keyblock, kbnode_t uidnode, if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY || n->pkt->pkttype == PKT_SECRET_SUBKEY) break; - if ( !(n->flag & (1<<9)) ) + if ( !(n->flag & (1<flag & (1<<10)) ) + if ( (n->flag & (1<pkt->pkt.signature; if (kid[0] != sig->keyid[0] || kid[1] != sig->keyid[1]) continue; - n->flag |= (1<<10); /* mark this node as processed */ + n->flag |= (1< curtime ) { - signode->flag |= (1<<8); /* yeah, found a good cert */ + signode->flag |= (1<flag |= (1<<11); + signode->flag |= (1<pkt->pkt.signature->keyid[1] == keyid[1]) : 1; /* Keep usable uid sigs ... */ - if ((node->flag & (1<<8)) && keep) + if ((node->flag & (1<flag & (1<<11)) && keep) + if ((node->flag & (1<flag & (1<<12)) + if(node->flag & (1<pkt->pkt.signature->keyid), uidnode->pkt->pkt.user_id->name, - node->flag&(1<<12)? "key unavailable": - node->flag&(1<<9)? "signature superseded" - /* */ :"invalid signature" ); + node->flag&(1<flag&(1< Date: Tue, 31 Jan 2023 11:32:41 +0100 Subject: [PATCH 08/60] gpg: Make "--list-options show-sig-subpackets=n,m" work again. * g10/gpg.c (parse_list_options): Set value for show-sig-subpackets. -- Fixes-commit: 811cfa34cb3e7166f0cf1f94565504dee21cd9f5 and thus a regression in 2.4.0 --- NEWS | 3 +++ g10/gpg.c | 15 +++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index a2b63f851..d2bb1c65e 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ Noteworthy changes in version 2.4.1 (unreleased) ------------------------------------------------ + * gpg: Make "--list-options show-sig-subpackets" work again. + Fixes regression in 2.4.0. + Noteworthy changes in version 2.4.0 (2022-12-16) ------------------------------------------------ diff --git a/g10/gpg.c b/g10/gpg.c index dd0bf0167..b9a81510f 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -2047,6 +2047,8 @@ parse_list_options(char *str) char *subpackets=""; /* something that isn't NULL */ struct parse_options lopts[]= { + {"show-sig-subpackets",LIST_SHOW_SIG_SUBPACKETS,NULL, + NULL}, {"show-photos",LIST_SHOW_PHOTOS,NULL, N_("display photo IDs during key listings")}, {"show-usage",LIST_SHOW_USAGE,NULL, @@ -2077,20 +2079,25 @@ parse_list_options(char *str) N_("show preferences")}, {"show-pref-verbose", LIST_SHOW_PREF_VERBOSE, NULL, N_("show preferences")}, - {"show-sig-subpackets",LIST_SHOW_SIG_SUBPACKETS,NULL, - NULL}, {"show-only-fpr-mbox",LIST_SHOW_ONLY_FPR_MBOX, NULL, NULL}, {"sort-sigs", LIST_SORT_SIGS, NULL, NULL}, {NULL,0,NULL,NULL} }; + int i; /* C99 allows for non-constant initializers, but we'd like to compile everywhere, so fill in the show-sig-subpackets argument here. Note that if the parse_options array changes, we'll have - to change the subscript here. */ - lopts[13].value=&subpackets; + to change the subscript here. We use a loop here in case the + list above is reordered. */ + for (i=0; lopts[i].name; i++) + if (lopts[i].bit == LIST_SHOW_SIG_SUBPACKETS) + { + lopts[i].value = &subpackets; + break; + } if(parse_options(str,&opt.list_options,lopts,1)) { From f9bcec6f8afa5e23f14d1d1e13cec08b242dac3c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 31 Jan 2023 11:52:01 +0100 Subject: [PATCH 09/60] gpg: New pseudo option full-help for --list-options et al. * g10/misc.c (parse_options): Implement "full-help". -- --- g10/misc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/g10/misc.c b/g10/misc.c index dcf001877..05d232f8f 100644 --- a/g10/misc.c +++ b/g10/misc.c @@ -1563,9 +1563,10 @@ parse_options(char *str,unsigned int *options, { char *tok; - if (str && !strcmp (str, "help")) + if (str && (!strcmp (str, "help") || !strcmp (str, "full-help"))) { int i,maxlen=0; + int full = *str == 'f'; /* Figure out the longest option name so we can line these up neatly. */ @@ -1577,6 +1578,10 @@ parse_options(char *str,unsigned int *options, if(opts[i].help) es_printf("%s%*s%s\n",opts[i].name, maxlen+2-(int)strlen(opts[i].name),"",_(opts[i].help)); + if (full) + for (i=0; opts[i].name; i++) + if(!opts[i].help) + es_printf("%s\n",opts[i].name); g10_exit(0); } From 8b8a8b246c443d5631a88ec59b88edf00aa0ff51 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 1 Feb 2023 09:27:28 +0100 Subject: [PATCH 10/60] ssh: Allow to define the order in which keys are returned. * agent/findkey.c (public_key_from_file): Add arg r_sshorder. (agent_ssh_key_from_file): Ditto. * agent/command-ssh.c (struct key_collection_item_s): New. (struct key_collection_s): New. (search_control_file): Add art r_lnr. (add_to_key_array): New. (free_key_array): New. (compare_key_collection_items): New. (ssh_send_available_keys): Rewrite to return the keys in the user given order. -- GnuPG-bug-id: 6212 We now first return the keys from active cards, followed by keys listed in sshcontrol, finally from those with the "Use-for-ssh" key attribute. Keys from active cards are returned sorted by their S/N. Keys from sshcontrol are returned in the order they are given in that file. Use-for-ssh keys are ordered by the value assigned to that key attribute. The values for the latter are clamped at 99999. --- agent/agent.h | 2 +- agent/command-ssh.c | 237 ++++++++++++++++++++++++++++++++------------ agent/findkey.c | 15 ++- agent/keyformat.txt | 4 +- 4 files changed, 190 insertions(+), 68 deletions(-) diff --git a/agent/agent.h b/agent/agent.h index ee5c67568..303f92e50 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -479,7 +479,7 @@ gpg_error_t agent_public_key_from_file (ctrl_t ctrl, gcry_sexp_t *result); gpg_error_t agent_ssh_key_from_file (ctrl_t ctrl, const unsigned char *grip, - gcry_sexp_t *result); + gcry_sexp_t *result, int *r_order); int agent_pk_get_algo (gcry_sexp_t s_key); int agent_is_tpm2_key(gcry_sexp_t s_key); int agent_key_available (const unsigned char *grip); diff --git a/agent/command-ssh.c b/agent/command-ssh.c index f237f9355..7621e7c2f 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -231,6 +231,22 @@ struct ssh_control_file_s }; +/* Two objects definition to hold keys for later sorting. */ +struct key_collection_item_s +{ + gcry_sexp_t key; /* Public key. (owned by us) */ + char *cardsn; /* Serial number of a card or NULL. (owned by us) */ + int order; /* Computed ordinal */ +}; + +struct key_collection_s +{ + struct key_collection_item_s *items; + size_t allocated; + size_t nitems; +}; + + /* Prototypes. */ static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl, estream_t request, @@ -1030,10 +1046,11 @@ read_control_file_item (ssh_control_file_t cf) HEXGRIP is found; return success in this case and store true at DISABLED if the found key has been disabled. If R_TTL is not NULL a specified TTL for that key is stored there. If R_CONFIRM is not - NULL it is set to 1 if the key has the confirm flag set. */ + NULL it is set to 1 if the key has the confirm flag set. The line + number where the item was found is stored at R_LNR. */ static gpg_error_t search_control_file (ssh_control_file_t cf, const char *hexgrip, - int *r_disabled, int *r_ttl, int *r_confirm) + int *r_disabled, int *r_ttl, int *r_confirm, int *r_lnr) { gpg_error_t err; @@ -1045,6 +1062,8 @@ search_control_file (ssh_control_file_t cf, const char *hexgrip, *r_ttl = 0; if (r_confirm) *r_confirm = 0; + if (r_lnr) + *r_lnr = -1; rewind_control_file (cf); while (!(err=read_control_file_item (cf))) @@ -1062,6 +1081,8 @@ search_control_file (ssh_control_file_t cf, const char *hexgrip, *r_ttl = cf->item.ttl; if (r_confirm) *r_confirm = cf->item.confirm; + if (r_lnr) + *r_lnr = cf->lnr; } return err; } @@ -1090,7 +1111,7 @@ add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec, if (err) return err; - err = search_control_file (cf, hexgrip, &disabled, NULL, NULL); + err = search_control_file (cf, hexgrip, &disabled, NULL, NULL, NULL); if (err && gpg_err_code(err) == GPG_ERR_EOF) { struct tm *tp; @@ -1141,7 +1162,7 @@ ttl_from_sshcontrol (const char *hexgrip) if (open_control_file (&cf, 0)) return 0; /* Error: Use the global default TTL. */ - if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL) + if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL, NULL) || disabled) ttl = 0; /* Use the global default if not found or disabled. */ @@ -1164,7 +1185,7 @@ confirm_flag_from_sshcontrol (const char *hexgrip) if (open_control_file (&cf, 0)) return 1; /* Error: Better ask for confirmation. */ - if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm) + if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm, NULL) || disabled) confirm = 0; /* If not found or disabled, there is no reason to ask for confirmation. */ @@ -1250,7 +1271,8 @@ ssh_search_control_file (ssh_control_file_t cf, if (i != 40) err = gpg_error (GPG_ERR_INV_LENGTH); else - err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm); + err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm, + NULL); if (gpg_err_code (err) == GPG_ERR_EOF) err = gpg_error (GPG_ERR_NOT_FOUND); return err; @@ -2483,8 +2505,71 @@ get_ssh_keyinfo_on_cards (ctrl_t ctrl) return keyinfo_on_cards; } + +/* Append (KEY,CARDSN,LNR,ORDER) to ARRAY. The array must initially + * be passed as a cleared struct. ARRAY takes ownership of KEY and + * CARDSN. */ static gpg_error_t -ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *key_counter_p) +add_to_key_array (struct key_collection_s *array, gcry_sexp_t key, + char *cardsn, int order) +{ + if (array->nitems == array->allocated) + { + struct key_collection_item_s *newitems; + size_t newsize = ((array->allocated + 63)/64 + 1) * 64; + + newitems = xtryreallocarray (array->items, array->allocated, newsize+1, + sizeof *newitems); + if (!newitems) + return gpg_error_from_syserror (); + array->allocated = newsize; + array->items = newitems; + } + array->items[array->nitems].key = key; + array->items[array->nitems].cardsn = cardsn; + array->items[array->nitems].order = order; + array->nitems++; + return 0; +} + +/* Release the content of ARRAY. */ +static void +free_key_array (struct key_collection_s *array) +{ + if (array && array->items) + { + unsigned int n; + + for (n = 0; n < array->nitems; n++) + { + gcry_sexp_release (array->items[n].key); + xfree (array->items[n].cardsn); + } + xfree (array->items); + } +} + + +/* Helper for the qsort in ssh_send_available_keys. */ +static int +compare_key_collection_items (const void *arg_a, const void *arg_b) +{ + const struct key_collection_item_s *a + = (const struct key_collection_item_s *)arg_a; + const struct key_collection_item_s *b + = (const struct key_collection_item_s *)arg_b; + int res; + + res = a->order - b->order; + /* If we are comparing two cards we sort by serial number. */ + if (!res && a->order == 1) + res = strcmp (a->cardsn?a->cardsn:"", b->cardsn?b->cardsn:""); + return res; +} + + +static gpg_error_t +ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *r_key_counter) { gpg_error_t err; char *dirname; @@ -2495,6 +2580,8 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *key_counter_p) struct card_key_info_s *keyinfo_on_cards, *l; char *cardsn; gcry_sexp_t key_public = NULL; + int count; + struct key_collection_s keyarray = { NULL }; err = open_control_file (&cf, 0); if (err) @@ -2526,7 +2613,7 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *key_counter_p) while ( (dir_entry = gnupg_readdir (dir)) ) { struct card_key_info_s *l_prev = NULL; - int disabled, is_ssh; + int disabled, is_ssh, lnr, order; unsigned char grip[20]; cardsn = NULL; @@ -2548,15 +2635,32 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *key_counter_p) /* Check if it's listed in "ssh_control" file. */ disabled = is_ssh = 0; - err = search_control_file (cf, hexgrip, &disabled, NULL, NULL); + err = search_control_file (cf, hexgrip, &disabled, NULL, NULL, &lnr); if (!err) { if (!disabled) - is_ssh = 1; + { + is_ssh = 1; + } } else if (gpg_err_code (err) != GPG_ERR_EOF) break; + /* Clamp LNR value and set the ordinal. + * Current use of ordinals: + * 1..99999 - inserted cards (right now only 1) + * 100000..199999 - listed in sshcontrol + * 200000..299999 - order taken from Use-for-ssh + */ + if (is_ssh) + { + if (lnr < 1) + lnr = 0; + else if (lnr > 99999) + lnr = 99999; + order = lnr + 100000; + } + if (l) { err = card_key_available (ctrl, l, &key_public, &cardsn); @@ -2570,25 +2674,76 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *key_counter_p) xfree (l->usage); xfree (l); l = NULL; + /* If we want to allow that the user to change the sorting + * order of card keys (which are sorted by their s/n), we + * would need to get the use-for-ssh: value from the stub + * file and set an appropriate ordinal. */ + order = 1; } else if (is_ssh) err = agent_public_key_from_file (ctrl, grip, &key_public); - else - /* Examine the file if it's suitable for SSH. */ - err = agent_ssh_key_from_file (ctrl, grip, &key_public); + else /* Examine the file if it's suitable for SSH. */ + { + err = agent_ssh_key_from_file (ctrl, grip, &key_public, &order); + if (order < 0 || err) + order = 0; + else if (order > 99999) + order = 99999; + order += 200000; + } if (err) { - /* Clear ERR, skiping the key in question. */ + /* Clear ERR, skipping the key in question. */ err = 0; continue; } - err = ssh_send_key_public (key_blobs, key_public, cardsn); - xfree (cardsn); + err = add_to_key_array (&keyarray, key_public, cardsn, order); + if (err) + { + gcry_sexp_release (key_public); + xfree (cardsn); + goto leave; + } + } + + gnupg_closedir (dir); + ssh_close_control_file (cf); + + /* Lastly, handle remaining keys which don't have the stub files. */ + for (l = keyinfo_on_cards, count=0; l; l = l->next, count++) + { + cardsn = NULL; + if (card_key_available (ctrl, l, &key_public, &cardsn)) + continue; + + err = add_to_key_array (&keyarray, key_public, cardsn, 300000+count); + if (err) + { + gcry_sexp_release (key_public); + xfree (cardsn); + goto leave; + } + } + + /* Sort the array. */ + qsort (keyarray.items, keyarray.nitems, sizeof *keyarray.items, + compare_key_collection_items); + if (opt.debug) + for (count=0; count < keyarray.nitems; count++) + log_debug ("sshkeys[%d]: order=%d, pubkey=%p sn=%s\n", + count, keyarray.items[count].order, + keyarray.items[count].key, keyarray.items[count].cardsn); + + /* And print the keys. */ + for (count=0; count < keyarray.nitems; count++) + { + err = ssh_send_key_public (key_blobs, keyarray.items[count].key, + keyarray.items[count].cardsn); if (err) { if (opt.debug) - gcry_log_debugsxp ("pubkey", key_public); + gcry_log_debugsxp ("pubkey", keyarray.items[count].key); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_CURVE || gpg_err_code (err) == GPG_ERR_INV_CURVE) { @@ -2597,54 +2752,14 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *key_counter_p) * We ignore them. */ } else - { - gcry_sexp_release (key_public); - break; /* the readdir loop. */ - } + goto leave; } - else /* Success */ - (*key_counter_p)++; - - gcry_sexp_release (key_public); } + *r_key_counter = count; - gnupg_closedir (dir); - ssh_close_control_file (cf); - - /* Lastly, handle remaining keys which don't have the stub files. */ - for (l = keyinfo_on_cards; l; l = l->next) - { - cardsn = NULL; - if (card_key_available (ctrl, l, &key_public, &cardsn)) - continue; - - err = ssh_send_key_public (key_blobs, key_public, cardsn); - xfree (cardsn); - if (err) - { - if (opt.debug) - gcry_log_debugsxp ("pubkey", key_public); - if (gpg_err_code (err) == GPG_ERR_UNKNOWN_CURVE - || gpg_err_code (err) == GPG_ERR_INV_CURVE) - { - /* For example a Brainpool curve or a curve we don't - * support at all but a smartcard lists that curve. - * We ignore them. */ - } - else - { - gcry_sexp_release (key_public); - break; - } - } - else /* Success. */ - (*key_counter_p)++; - - gcry_sexp_release (key_public); - } - + leave: agent_card_free_keyinfo (keyinfo_on_cards); - + free_key_array (&keyarray); return err; } diff --git a/agent/findkey.c b/agent/findkey.c index 20962bd43..d3a3b335c 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -1422,10 +1422,11 @@ agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip, /* Return the public key for the keygrip GRIP. The result is stored at RESULT. This function extracts the public key from the private key database. On failure an error code is returned and NULL stored - at RESULT. */ + at RESULT. If R_SSHORDER is not NULL the ordinal from the + Use-for-ssh attribute is stored at that address. */ static gpg_error_t public_key_from_file (ctrl_t ctrl, const unsigned char *grip, - gcry_sexp_t *result, int for_ssh) + gcry_sexp_t *result, int for_ssh, int *r_sshorder) { gpg_error_t err; int i, idx; @@ -1451,6 +1452,8 @@ public_key_from_file (ctrl_t ctrl, const unsigned char *grip, (void)ctrl; *result = NULL; + if (r_sshorder) + *r_sshorder = 0; err = read_key_file (grip, &s_skey, for_ssh? &keymeta : NULL); if (err) @@ -1470,6 +1473,8 @@ public_key_from_file (ctrl_t ctrl, const unsigned char *grip, if (!is_ssh) return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + if (r_sshorder) + *r_sshorder = is_ssh; } for (i=0; i < DIM (array); i++) @@ -1565,15 +1570,15 @@ agent_public_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result) { - return public_key_from_file (ctrl, grip, result, 0); + return public_key_from_file (ctrl, grip, result, 0, NULL); } gpg_error_t agent_ssh_key_from_file (ctrl_t ctrl, const unsigned char *grip, - gcry_sexp_t *result) + gcry_sexp_t *result, int *r_order) { - return public_key_from_file (ctrl, grip, result, 1); + return public_key_from_file (ctrl, grip, result, 1, r_order); } diff --git a/agent/keyformat.txt b/agent/keyformat.txt index 42e6d215e..bbcaa7e2c 100644 --- a/agent/keyformat.txt +++ b/agent/keyformat.txt @@ -122,7 +122,9 @@ similar to the "shadow" parameter: If given and the value is "yes" or "1" the key is allowed for use by gpg-agent's ssh-agent implementation. This is thus the same as putting the keygrip into the 'sshcontrol' file. Only one such item -should exist. +should exist. If another non-zero value between 1 and 99999 is used, +this is taken to establish the order in which the keys are returned to +ssh; lower numbers are returned first. *** Use-for-p11 If given and the value is "yes" or "1" the key is allowed for use by From 103acfe9ca6e314049671f5b5a760a620046788f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 7 Feb 2023 14:25:58 +0100 Subject: [PATCH 11/60] gpg: New list-option --show-unusable-sigs. * g10/options.h (LIST_SHOW_UNUSABLE_SIGS): New. * g10/gpg.c (parse_list_options): Add "show-unusable-sigs". * g10/keydb.h (keyid_eq): New. (pk_is_primary): New. * g10/keylist.c (list_signature_print): Early return for weak key signatures. Print "self-signature" instead of user-id. (list_keyblock_print): Simplify and always set self-sig node flag. -- This patch avoid the printing of often hundreds of "Invalid digest algorithm" notices during key signature listings if those key signatures were done with SHA1. The new option can be used to revert the behaviour. We now also print "[self-signature]" with --check-sigs or --list-sigs instead of the primary user id. This makes such listing easier to read. --- NEWS | 8 +++++- doc/gpg.texi | 4 +++ g10/getkey.c | 2 +- g10/gpg.c | 2 ++ g10/keydb.h | 9 ++++++- g10/keylist.c | 73 ++++++++++++++++++++++++++++++--------------------- g10/options.h | 1 + 7 files changed, 66 insertions(+), 33 deletions(-) diff --git a/NEWS b/NEWS index d2bb1c65e..176f92740 100644 --- a/NEWS +++ b/NEWS @@ -1,10 +1,16 @@ Noteworthy changes in version 2.4.1 (unreleased) ------------------------------------------------ - * gpg: Make "--list-options show-sig-subpackets" work again. + * gpg: New list-option "show-unusable-sigs". + + * gpg: Show "[self-signature]" instead of the user-id in key + signature listings. + + * gpg: Make list-options "show-sig-subpackets" work again. Fixes regression in 2.4.0. + Noteworthy changes in version 2.4.0 (2022-12-16) ------------------------------------------------ diff --git a/doc/gpg.texi b/doc/gpg.texi index 47aa0a4d0..55b45e6bf 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -1405,6 +1405,10 @@ give the opposite meaning. The options are: @opindex list-options:show-unusable-subkeys Show revoked and expired subkeys in key listings. Defaults to no. + @item show-unusable-sigs + @opindex list-options:show-unusable-sigs + Show key signature made using weak or unsupported algorithms. + @item show-keyring @opindex list-options:show-keyring Display the keyring name at the head of key listings to show which diff --git a/g10/getkey.c b/g10/getkey.c index 6363fea9f..f0843d154 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -3247,7 +3247,7 @@ buf_to_sig (const byte * buf, size_t len) * has_expired * expired_date * - * On this subkey's most revent valid self-signed packet, the + * On this subkey's most recent valid self-signed packet, the * following field is set: * * flags.chosen_selfsig diff --git a/g10/gpg.c b/g10/gpg.c index b9a81510f..c490ff72b 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -2071,6 +2071,8 @@ parse_list_options(char *str) N_("show revoked and expired user IDs in key listings")}, {"show-unusable-subkeys",LIST_SHOW_UNUSABLE_SUBKEYS,NULL, N_("show revoked and expired subkeys in key listings")}, + {"show-unusable-sigs",LIST_SHOW_UNUSABLE_SIGS,NULL, + N_("show signatures with invalid algorithms during signature listings")}, {"show-keyring",LIST_SHOW_KEYRING,NULL, N_("show the keyring name in key listings")}, {"show-sig-expire",LIST_SHOW_SIG_EXPIRE,NULL, diff --git a/g10/keydb.h b/g10/keydb.h index 771bc8e16..28b61d4a1 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -511,11 +511,18 @@ keyid_cmp (const u32 *a, const u32 *b) return 0; } +/* Return true if both keyids are equal. */ +static int GPGRT_ATTR_UNUSED +keyid_eq (const u32 *a, const u32 *b) +{ + return a[0] == b[0] && a[1] == b[1]; +} + /* Return whether PK is a primary key. */ static int GPGRT_ATTR_UNUSED pk_is_primary (PKT_public_key *pk) { - return keyid_cmp (pk_keyid (pk), pk_main_keyid (pk)) == 0; + return keyid_eq (pk_keyid (pk), pk_main_keyid (pk)); } /* Copy the keyid in SRC to DEST and return DEST. */ diff --git a/g10/keylist.c b/g10/keylist.c index 1ced732a4..8b7c597cb 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -1216,7 +1216,8 @@ cmp_signodes (const void *av, const void *bv) } -/* Helper for list_keyblock_print. */ +/* Helper for list_keyblock_print. The caller must have set + * NODFLG_MARK_B to indicate self-signatures. */ static void list_signature_print (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node, struct keylist_context *listctx) @@ -1247,6 +1248,11 @@ list_signature_print (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node, case GPG_ERR_UNUSABLE_PUBKEY: listctx->no_key++; return; + case GPG_ERR_DIGEST_ALGO: + case GPG_ERR_PUBKEY_ALGO: + if (!(opt.list_options & LIST_SHOW_UNUSABLE_SIGS)) + return; + /* fallthru. */ default: listctx->oth_err++; sigrc = '%'; @@ -1259,6 +1265,15 @@ list_signature_print (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node, } else { + if (!(opt.list_options & LIST_SHOW_UNUSABLE_SIGS) + && (gpg_err_code (openpgp_pk_test_algo (sig->pubkey_algo) + == GPG_ERR_PUBKEY_ALGO) + || gpg_err_code (openpgp_md_test_algo (sig->digest_algo) + == GPG_ERR_DIGEST_ALGO) + || (sig->digest_algo == DIGEST_ALGO_SHA1 + && !(node->flag & NODFLG_MARK_B) /*no selfsig*/ + && !opt.flags.allow_weak_key_signatures))) + return; rc = 0; sigrc = ' '; } @@ -1306,7 +1321,9 @@ list_signature_print (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node, es_fprintf (es_stdout, "[%s] ", gpg_strerror (rc)); else if (sigrc == '?') ; - else if (!opt.fast_list_mode) + else if ((node->flag & NODFLG_MARK_B)) + es_fputs (_("[self-signature]"), es_stdout); + else if (!opt.fast_list_mode ) { size_t n; char *p = get_user_id (ctrl, sig->keyid, &n, NULL); @@ -1585,37 +1602,33 @@ list_keyblock_print (ctrl_t ctrl, kbnode_t keyblock, int secret, int fpr, else if (opt.list_sigs && node->pkt->pkttype == PKT_SIGNATURE && !skip_sigs) { - if ((opt.list_options & LIST_SORT_SIGS)) + kbnode_t n; + unsigned int sigcount = 0; + kbnode_t *sigarray; + unsigned int idx; + + for (n=node; n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next) + sigcount++; + sigarray = xcalloc (sigcount, sizeof *sigarray); + + sigcount = 0; + for (n=node; n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next) { - kbnode_t n; - unsigned int sigcount = 0; - kbnode_t *sigarray; - unsigned int idx; + if (keyid_eq (mainkid, n->pkt->pkt.signature->keyid)) + n->flag |= NODFLG_MARK_B; /* Is a self-sig. */ + else + n->flag &= ~NODFLG_MARK_B; - for (n=node; n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next) - sigcount++; - sigarray = xcalloc (sigcount, sizeof *sigarray); - - sigcount = 0; - for (n=node; n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next) - { - if (!keyid_cmp (mainkid, n->pkt->pkt.signature->keyid)) - n->flag |= NODFLG_MARK_B; /* Is a self-sig. */ - else - n->flag &= ~NODFLG_MARK_B; - - sigarray[sigcount++] = node = n; - } - /* Note that NODE is now at the last signature. */ - - qsort (sigarray, sigcount, sizeof *sigarray, cmp_signodes); - - for (idx=0; idx < sigcount; idx++) - list_signature_print (ctrl, keyblock, sigarray[idx], listctx); - xfree (sigarray); + sigarray[sigcount++] = node = n; } - else - list_signature_print (ctrl, keyblock, node, listctx); + /* Note that NODE is now at the last signature. */ + + if ((opt.list_options & LIST_SORT_SIGS)) + qsort (sigarray, sigcount, sizeof *sigarray, cmp_signodes); + + for (idx=0; idx < sigcount; idx++) + list_signature_print (ctrl, keyblock, sigarray[idx], listctx); + xfree (sigarray); } } es_putc ('\n', es_stdout); diff --git a/g10/options.h b/g10/options.h index 74a6cdb16..499544cdf 100644 --- a/g10/options.h +++ b/g10/options.h @@ -426,6 +426,7 @@ EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode; #define LIST_SORT_SIGS (1<<13) #define LIST_SHOW_PREF (1<<14) #define LIST_SHOW_PREF_VERBOSE (1<<15) +#define LIST_SHOW_UNUSABLE_SIGS (1<<16) #define VERIFY_SHOW_PHOTOS (1<<0) #define VERIFY_SHOW_POLICY_URLS (1<<1) From 3ab6538433fdbed6903e1c5f5b63054e0d056666 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 8 Feb 2023 08:23:31 +0100 Subject: [PATCH 12/60] tools: Return a better error message if sendmail is not usable. * tools/send-mail.c: Include unistd.h (run_sendmail): Check for bad sendmail. -- GnuPG-bug-id: 6321 --- tools/send-mail.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/send-mail.c b/tools/send-mail.c index 6492c43c1..d348f28cb 100644 --- a/tools/send-mail.c +++ b/tools/send-mail.c @@ -22,6 +22,7 @@ #include #include #include +#include /* for X_OK */ #include "../common/util.h" #include "../common/exectool.h" @@ -36,6 +37,12 @@ run_sendmail (estream_t data) const char pgmname[] = NAME_OF_SENDMAIL; const char *argv[3]; + if (!*pgmname || gnupg_access (pgmname, X_OK)) + { + log_error ("sendmail tool '%s' is not correctly installed\n", pgmname); + return gpg_error (GPG_ERR_ENOENT); + } + argv[0] = "-oi"; argv[1] = "-t"; argv[2] = NULL; From f118e3b101cad615101f88799333a6a5e61ed81f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 16 Feb 2023 13:14:30 +0100 Subject: [PATCH 13/60] gpg: --gen-random code cleanup by using es_set_binary. * g10/gpg.c (main): Replace setmode by es_set_binary and use only when needed. -- It is better to use our es_set_binary than to use a Windows specific method which still worked but is fragile because estream might be changed. We now set binary only when needed. Note that it does not harm to call es_set_binary more often than needed. --- g10/getkey.c | 2 +- g10/gpg.c | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/g10/getkey.c b/g10/getkey.c index f0843d154..093ce61b0 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -2707,7 +2707,7 @@ merge_selfsigs_main (ctrl_t ctrl, kbnode_t keyblock, int *r_revoked, * and there was no way to change it, so we start with the one * from the key packet. We do not support v3 keys anymore but * we keep the code in case a future key versions introduces a - * hadr expire time again. */ + * hard expire time again. */ key_expire = pk->max_expiredate; key_expire_seen = 1; } diff --git a/g10/gpg.c b/g10/gpg.c index c490ff72b..31cb066c9 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -5057,9 +5057,6 @@ main (int argc, char **argv) size_t nn; p = gcry_random_bytes (n, level); -#ifdef HAVE_DOSISH_SYSTEM - setmode ( fileno(stdout), O_BINARY ); -#endif if (hexhack) { for (nn = 0; nn < n; nn++) @@ -5077,6 +5074,7 @@ main (int argc, char **argv) } else { + es_set_binary (es_stdout); es_fwrite( p, n, 1, es_stdout ); } xfree(p); From 49fe6a2821f30fe7fde82be8a85840c9e2f09348 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 16 Feb 2023 15:07:40 +0100 Subject: [PATCH 14/60] doc: Put the Unattended Usage of GPG section also into the man page. -- --- doc/gpg.texi | 45 +++++++++++++++++---------------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/doc/gpg.texi b/doc/gpg.texi index 55b45e6bf..eb8b65242 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -4331,7 +4331,7 @@ already been reported to our bug tracker at @url{https://bugs.gnupg.org}. @c *************** UNATTENDED ************** @c *************** ************** @c ******************************************* -@manpause +@mansect notes @node Unattended Usage of GPG @section Unattended Usage @@ -4402,32 +4402,21 @@ previous subsection ``The quick key manipulation interface''. The parameters for the key are either read from stdin or given as a file on the command line. The format of the parameter file is as -follows: - -@itemize @bullet - @item Text only, line length is limited to about 1000 characters. - @item UTF-8 encoding must be used to specify non-ASCII characters. - @item Empty lines are ignored. - @item Leading and trailing white space is ignored. - @item A hash sign as the first non white space character indicates - a comment line. - @item Control statements are indicated by a leading percent sign, the - arguments are separated by white space from the keyword. - @item Parameters are specified by a keyword, followed by a colon. Arguments - are separated by white space. - @item - The first parameter must be @samp{Key-Type}; control statements may be - placed anywhere. - @item - The order of the parameters does not matter except for @samp{Key-Type} - which must be the first parameter. The parameters are only used for - the generated keyblock (primary and subkeys); parameters from previous - sets are not used. Some syntactically checks may be performed. - @item - Key generation takes place when either the end of the parameter file - is reached, the next @samp{Key-Type} parameter is encountered or at the - control statement @samp{%commit} is encountered. -@end itemize +follows: Text only, line length is limited to about 1000 characters. +UTF-8 encoding must be used to specify non-ASCII characters. Empty +lines are ignored. Leading and trailing white space is ignored. A +hash sign as the first non white space character indicates a comment +line. Control statements are indicated by a leading percent sign, +their arguments are separated by white space from the keyword. +Parameters are specified by a keyword, followed by a colon; arguments +are separated by white space. The first parameter must be +@samp{Key-Type} but control statements may be placed anywhere. The +order of the parameters does not matter except for @samp{Key-Type}. +The parameters are only used for the generated keyblock (primary and +subkeys); parameters from previous sets are not used. Some syntax +checks may be performed. Key commences when either the end of the +parameter file is reached, the next @samp{Key-Type} parameter is +encountered, or the control statement @samp{%commit} is encountered. @noindent Control statements: @@ -4463,7 +4452,7 @@ See the previous subsection ``Ephemeral home directories''. @item %ask-passphrase @itemx %no-ask-passphrase -This option is a no-op for GnuPG 2.1 and later. +This option is a no-op since GnuPG version 2.1. @item %no-protection Using this option allows the creation of keys without any passphrase From 3d094e2bcf6c9ed2cd405623f2dbc6131d04366f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 16 Feb 2023 18:09:22 +0100 Subject: [PATCH 15/60] gpg: New option --add-desig-revoker * g10/gpg.c (oAddDesigRevoker): New. (opts): Add new option. * g10/options.h (opt): Add field desig_revokers. * g10/keygen.c (get_parameter_idx): New. (get_parameter): Make use of get_parameter_idx. (prepare_desig_revoker): New. (get_parameter_revkey): Add arg idx. (proc_parameter_file): Add designated revokers. (do_generate_keypair): Write all designated revokers. --- NEWS | 2 + doc/gpg.texi | 13 ++++++ g10/gpg.c | 9 ++++ g10/keygen.c | 114 +++++++++++++++++++++++++++++++++++++++++++++----- g10/options.h | 3 ++ g10/trust.c | 2 +- 6 files changed, 132 insertions(+), 11 deletions(-) diff --git a/NEWS b/NEWS index 176f92740..340742c20 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ Noteworthy changes in version 2.4.1 (unreleased) ------------------------------------------------ + * gpg: New option --add-desig-revoker. + * gpg: New list-option "show-unusable-sigs". * gpg: Show "[self-signature]" instead of the user-id in key diff --git a/doc/gpg.texi b/doc/gpg.texi index eb8b65242..45cd97241 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -1750,6 +1750,19 @@ recipient's or signator's key. If the given key is not locally available but an LDAP keyserver is configured the missing key is imported from that server. +@item --add-desig-revoker [sensitive:]@var{fingerprint} +@opindex add-desig-revoker +Add the key specified by @var{fingerprint} as a designated revoker to +newly created keys. If the fingerprint is prefixed with the keyword +``sensitive:'' that info is normally not exported wit the key. This +option may be given several time to add more than one designated +revoker. If the keyword ``clear'' is used instead of a fingerprint, +all designated options previously encountered are discarded. +Designated revokers are marked on the key as non-revocable. Note that +a designated revoker specified using a parameter file will also be +added to the key. + + @item --trust-model @{pgp|classic|tofu|tofu+pgp|direct|always|auto@} @opindex trust-model Set what trust model GnuPG should follow. The models are: diff --git a/g10/gpg.c b/g10/gpg.c index 31cb066c9..9ec956ac3 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -443,6 +443,7 @@ enum cmd_and_opt_values oForbidGenKey, oRequireCompliance, oCompatibilityFlags, + oAddDesigRevoker, oNoop }; @@ -702,6 +703,7 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_s (oForceOwnertrust, "force-ownertrust", "@"), ARGPARSE_s_n (oNoAutoTrustNewKey, "no-auto-trust-new-key", "@"), #endif + ARGPARSE_s_s (oAddDesigRevoker, "add-desig-revoker", "@"), ARGPARSE_header ("Input", N_("Options controlling the input")), @@ -3716,6 +3718,13 @@ main (int argc, char **argv) opt.flags.require_compliance = 1; break; + case oAddDesigRevoker: + if (!strcmp (pargs.r.ret_str, "clear")) + FREE_STRLIST (opt.desig_revokers); + else + append_to_strlist (&opt.desig_revokers, pargs.r.ret_str); + break; + case oNoop: break; default: diff --git a/g10/keygen.c b/g10/keygen.c index 4dcf7a494..2e07015c6 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -3792,14 +3792,29 @@ release_parameter_list (struct para_data_s *r) } } +/* Return the N-th parameter of name KEY from PARA. An IDX of 0 + * returns the first and so on. */ static struct para_data_s * -get_parameter( struct para_data_s *para, enum para_name key ) +get_parameter_idx (struct para_data_s *para, enum para_name key, + unsigned int idx) { - struct para_data_s *r; + struct para_data_s *r; - for( r = para; r && r->key != key; r = r->next ) - ; - return r; + for(r = para; r; r = r->next) + if (r->key == key) + { + if (!idx) + return r; + idx--; + } + return NULL; +} + +/* Return the first parameter of name KEY from PARA. */ +static struct para_data_s * +get_parameter (struct para_data_s *para, enum para_name key) +{ + return get_parameter_idx (para, key, 0); } static const char * @@ -3947,6 +3962,69 @@ parse_parameter_usage (const char *fname, } +/* Parse the revocation key specified by NAME, check that the public + * key exists (so that we can get the required public key algorithm), + * and return a parameter wit the revocation key information. On + * error print a diagnostic and return NULL. */ +static struct para_data_s * +prepare_desig_revoker (ctrl_t ctrl, const char *name) +{ + gpg_error_t err; + struct para_data_s *para = NULL; + KEYDB_SEARCH_DESC desc; + int sensitive = 0; + struct revocation_key revkey; + PKT_public_key *revoker_pk = NULL; + size_t fprlen; + + if (!ascii_strncasecmp (name, "sensitive:", 10) && !spacep (name+10)) + { + name += 10; + sensitive = 1; + } + + if (classify_user_id (name, &desc, 1) + || desc.mode != KEYDB_SEARCH_MODE_FPR) + { + log_info (_("\"%s\" is not a fingerprint\n"), name); + err = gpg_error (GPG_ERR_INV_NAME); + goto leave; + } + + revoker_pk = xcalloc (1, sizeof *revoker_pk); + revoker_pk->req_usage = PUBKEY_USAGE_CERT; + err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, + NULL, revoker_pk, name, NULL, NULL, 1); + if (err) + goto leave; + + fingerprint_from_pk (revoker_pk, revkey.fpr, &fprlen); + if (fprlen != 20 && fprlen != 32) + { + log_info (_("cannot appoint a PGP 2.x style key as a " + "designated revoker\n")); + err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY); + goto leave; + } + revkey.fprlen = fprlen; + revkey.class = 0x80; + if (sensitive) + revkey.class |= 0x40; + revkey.algid = revoker_pk->pubkey_algo; + + para = xcalloc (1, sizeof *para); + para->key = pREVOKER; + memcpy (¶->u.revkey, &revkey, sizeof revkey); + + leave: + if (err) + log_error ("invalid revocation key '%s': %s\n", name, gpg_strerror (err)); + free_public_key (revoker_pk); + return para; +} + + +/* Parse a pREVOKER parameter into its dedicated parts. */ static int parse_revocation_key (const char *fname, struct para_data_s *para, enum para_name key) @@ -4030,10 +4108,11 @@ get_parameter_uint( struct para_data_s *para, enum para_name key ) } static struct revocation_key * -get_parameter_revkey( struct para_data_s *para, enum para_name key ) +get_parameter_revkey (struct para_data_s *para, enum para_name key, + unsigned int idx) { - struct para_data_s *r = get_parameter( para, key ); - return r? &r->u.revkey : NULL; + struct para_data_s *r = get_parameter_idx (para, key, idx); + return r? &r->u.revkey : NULL; } static int @@ -4052,6 +4131,7 @@ proc_parameter_file (ctrl_t ctrl, struct para_data_s *para, const char *fname, const char *s1, *s2, *s3; size_t n; char *p; + strlist_t sl; int is_default = 0; int have_user_id = 0; int err, algo; @@ -4197,10 +4277,20 @@ proc_parameter_file (ctrl_t ctrl, struct para_data_s *para, const char *fname, } } - /* Set revoker, if any. */ + /* Set revoker from parameter file, if any. Must be done first so + * that we don't find a parameter set via prepare_desig_revoker. */ if (parse_revocation_key (fname, para, pREVOKER)) return -1; + /* Check and appened revokers from the config file. */ + for (sl = opt.desig_revokers; sl; sl = sl->next) + { + r = prepare_desig_revoker (ctrl, sl->d); + if (!r) + return -1; + append_to_parameter (para, r); + } + /* Make KEYCREATIONDATE from Creation-Date. We ignore this if the * key has been taken from a card and a keycreationtime has already @@ -5330,6 +5420,7 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, const char *key_from_hexgrip = NULL; int cardkey; unsigned int keygen_flags; + unsigned int idx; if (outctrl->dryrun) { @@ -5464,7 +5555,10 @@ do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, keyid_copy (pri_psk->main_keyid, pri_psk->keyid); } - if (!err && (revkey = get_parameter_revkey (para, pREVOKER))) + /* Write all signatures specifying designated revokers. */ + for (idx=0; + !err && (revkey = get_parameter_revkey (para, pREVOKER, idx)); + idx++) err = write_direct_sig (ctrl, pub_root, pri_psk, revkey, signtimestamp, cache_nonce); diff --git a/g10/options.h b/g10/options.h index 499544cdf..3ecf57ffb 100644 --- a/g10/options.h +++ b/g10/options.h @@ -111,6 +111,9 @@ struct * the option --sender. */ strlist_t sender_list; + /* A list of fingerprints added as designated revokers to new keys. */ + strlist_t desig_revokers; + int def_cert_level; int min_cert_level; int ask_cert_level; diff --git a/g10/trust.c b/g10/trust.c index 9749bd786..f11dfb759 100644 --- a/g10/trust.c +++ b/g10/trust.c @@ -59,7 +59,7 @@ register_trusted_key (const char *string) /* Some users have conf files with entries like * trusted-key 0x1234567812345678 # foo * That is obviously wrong. Before fixing bug#1206 trailing garbage - * on a key specification if was ignored. We detect the above use case + * on a key specification was ignored. We detect the above use case * here and cut off the junk-looking-like-a comment. */ if (strchr (string, '#')) { From 71c11c20f41d660d468de642b33cdc330ff682c7 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 21 Feb 2023 12:14:31 +0100 Subject: [PATCH 16/60] gpg: Prepare to accept shorter OIDs for ed25519 and cv25519. * common/openpgp-oid.c (oidtable): Add them. (oid_ed25519_v5, oid_cv25519_v5): New. (openpgp_oidbuf_is_ed25519): Take new OID in account. (openpgp_oidbuf_is_cv25519): Ditto. -- ed25519 is used in GnuPG and other implementations since 2015 and thus we can't simply switch to the shorter OIDs. However, we have not widely used them with v5 keys (only ed448 forced the use of v5) and thus it might be possible to use the new OIDs with v5 keys. Note that Libgcrypt supports the new OIDs even in 1.8. --- common/openpgp-oid.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/common/openpgp-oid.c b/common/openpgp-oid.c index f0460b068..510e09f4a 100644 --- a/common/openpgp-oid.c +++ b/common/openpgp-oid.c @@ -48,6 +48,8 @@ static struct { { "Curve25519", "1.3.6.1.4.1.3029.1.5.1", 255, "cv25519", PUBKEY_ALGO_ECDH }, { "Ed25519", "1.3.6.1.4.1.11591.15.1", 255, "ed25519", PUBKEY_ALGO_EDDSA }, + { "Curve25519", "1.3.101.110", 255, "cv25519", PUBKEY_ALGO_ECDH }, + { "Ed25519", "1.3.101.112", 255, "ed25519", PUBKEY_ALGO_EDDSA }, { "X448", "1.3.101.111", 448, "cv448", PUBKEY_ALGO_ECDH }, { "Ed448", "1.3.101.113", 456, "ed448", PUBKEY_ALGO_EDDSA }, @@ -65,13 +67,17 @@ static struct { }; -/* The OID for Curve Ed25519 in OpenPGP format. */ +/* The OID for Curve Ed25519 in OpenPGP format. The shorter v5 + * variant may only be used with v5 keys. */ static const char oid_ed25519[] = { 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xda, 0x47, 0x0f, 0x01 }; +static const char oid_ed25519_v5[] = { 0x03, 0x2b, 0x65, 0x70 }; -/* The OID for Curve25519 in OpenPGP format. */ +/* The OID for Curve25519 in OpenPGP format. The shorter v5 + * variant may only be used with v5 keys. */ static const char oid_cv25519[] = { 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01 }; +static const char oid_cv25519_v5[] = { 0x03, 0x2b, 0x65, 0x6e }; /* The OID for X448 in OpenPGP format. */ /* @@ -321,8 +327,12 @@ openpgp_oid_to_str (gcry_mpi_t a) int openpgp_oidbuf_is_ed25519 (const void *buf, size_t len) { - return (buf && len == DIM (oid_ed25519) - && !memcmp (buf, oid_ed25519, DIM (oid_ed25519))); + if (!buf) + return 0; + return ((len == DIM (oid_ed25519) + && !memcmp (buf, oid_ed25519, DIM (oid_ed25519))) + || (len == DIM (oid_ed25519_v5) + && !memcmp (buf, oid_ed25519_v5, DIM (oid_ed25519_v5)))); } @@ -345,8 +355,12 @@ openpgp_oid_is_ed25519 (gcry_mpi_t a) int openpgp_oidbuf_is_cv25519 (const void *buf, size_t len) { - return (buf && len == DIM (oid_cv25519) - && !memcmp (buf, oid_cv25519, DIM (oid_cv25519))); + if (!buf) + return 0; + return ((len == DIM (oid_cv25519) + && !memcmp (buf, oid_cv25519, DIM (oid_cv25519))) + || (len == DIM (oid_cv25519_v5) + && !memcmp (buf, oid_cv25519_v5, DIM (oid_cv25519_v5)))); } From 23b4c6e7c2f715145695e8ec3141d248876d1e25 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 23 Feb 2023 10:20:33 +0100 Subject: [PATCH 17/60] dirmngr: New debug flag "keeptmp". * dirmngr/dirmngr.c (debug_flags): Add "keeptmp". (set_debug): Don't set in with "guru". * dirmngr/dirmngr.h (DBG_KEEPTMP_VALUE): New. -- Note that flag is not yet used. --- dirmngr/dirmngr.c | 3 ++- dirmngr/dirmngr.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index cbc693bd8..650770dab 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -324,6 +324,7 @@ static struct debug_flags_s debug_flags [] = { DBG_NETWORK_VALUE, "network" }, { DBG_LOOKUP_VALUE , "lookup" }, { DBG_EXTPROG_VALUE, "extprog" }, + { DBG_KEEPTMP_VALUE, "keeptmp" }, { 77, NULL } /* 77 := Do not exit on "help" or "?". */ }; @@ -534,7 +535,7 @@ set_debug (void) select the highest debug value and would then clutter their disk with debug files which may reveal confidential data. */ if (numok) - opt.debug &= ~(DBG_HASHING_VALUE); + opt.debug &= ~(DBG_HASHING_VALUE|DBG_KEEPTMP_VALUE); } else { diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index e7591b998..f65bcd119 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -166,6 +166,7 @@ struct #define DBG_NETWORK_VALUE 2048 /* debug network I/O. */ #define DBG_LOOKUP_VALUE 8192 /* debug lookup details */ #define DBG_EXTPROG_VALUE 16384 /* debug external program calls */ +#define DBG_KEEPTMP_VALUE 32768 /* keep some temporary files */ #define DBG_X509 (opt.debug & DBG_X509_VALUE) #define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) @@ -177,6 +178,7 @@ struct #define DBG_NETWORK (opt.debug & DBG_NETWORK_VALUE) #define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE) #define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE) +#define DBG_KEEPTMP (opt.debug & DBG_KEEPTMP_VALUE) /* A simple list of certificate references. FIXME: Better use certlist_t also for references (Store NULL at .cert) */ From 9de180c6d22253d26b4a75c568490a4932c8b96b Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 23 Feb 2023 10:23:56 +0100 Subject: [PATCH 18/60] doc: Minor comment fixes. -- --- common/recsel.c | 3 ++- scd/app-p15.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common/recsel.c b/common/recsel.c index 60da4d3c9..ea0858c84 100644 --- a/common/recsel.c +++ b/common/recsel.c @@ -174,6 +174,7 @@ find_next_lc (char *string) * -c The string match in this part is done case-sensitive. * -t Do not trim leading and trailing spaces from VALUE. * Note that a space after is here required. + * -r RFU: String match uses a regular expression * * For example four calls to recsel_parse_expr() with these values for * EXPR @@ -192,7 +193,7 @@ find_next_lc (char *string) * * The caller must pass the address of a selector variable to this * function and initialize the value of the function to NULL before - * the first call. recset_release needs to be called to free the + * the first call. recsel_release needs to be called to free the * selector. */ gpg_error_t diff --git a/scd/app-p15.c b/scd/app-p15.c index bfd693466..29241af6b 100644 --- a/scd/app-p15.c +++ b/scd/app-p15.c @@ -326,7 +326,7 @@ struct prkdf_object_s char *serial_number; /* KDF/KEK parameter for OpenPGP's ECDH. First byte is zero if not - * available. .*/ + * available. */ unsigned char ecdh_kdf[4]; /* Length and allocated buffer with the Id of this object. */ From 1952a0e5e41c5f27cac81ff876eba5373f4cfc5f Mon Sep 17 00:00:00 2001 From: Andre Heinecke Date: Tue, 23 Oct 2018 12:46:38 +0200 Subject: [PATCH 19/60] sm: Fix dirmngr loadcrl for intermediate certs * sm/call-dirmngr.c (run_command_inq_cb): Support ISTRUSTED. (inq_certificate): Distinguish unsupported inquiry error. -- When loading a CRL through "gpgsm --call-dirmngr loadcrl foo" dirmngr can ask gpgsm back if a certificate used ISTRUSTED, which previously resulted in an error. (cherry picked from commit 6b36c16f77722d17f4f317c788701cbc1e9552b2) That commit was from the 2.2 branch and we forgot to forward port it. --- sm/call-dirmngr.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c index cc958ccf8..da3839349 100644 --- a/sm/call-dirmngr.c +++ b/sm/call-dirmngr.c @@ -362,7 +362,7 @@ inq_certificate (void *opaque, const char *line) } else { - log_error ("unsupported inquiry '%s'\n", line); + log_error ("unsupported certificate inquiry '%s'\n", line); return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } @@ -1035,9 +1035,33 @@ run_command_inq_cb (void *opaque, const char *line) line = s; log_info ("dirmngr: %s\n", line); } + else if ((s = has_leading_keyword (line, "ISTRUSTED"))) + { + /* The server is asking us whether the certificate is a trusted + root certificate. */ + char fpr[41]; + struct rootca_flags_s rootca_flags; + int n; + + line = s; + + for (s=line,n=0; hexdigitp (s); s++, n++) + ; + if (*s || n != 40) + return gpg_error (GPG_ERR_ASS_PARAMETER); + for (s=line, n=0; n < 40; s++, n++) + fpr[n] = (*s >= 'a')? (*s & 0xdf): *s; + fpr[n] = 0; + + if (!gpgsm_agent_istrusted (parm->ctrl, NULL, fpr, &rootca_flags)) + rc = assuan_send_data (parm->ctx, "1", 1); + else + rc = 0; + return rc; + } else { - log_error ("unsupported inquiry '%s'\n", line); + log_error ("unsupported command inquiry '%s'\n", line); rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } From 5d96aab27dcf1b1c826c483e39d7265b89736b53 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sun, 26 Feb 2023 19:11:27 +0100 Subject: [PATCH 20/60] gpgsm: Improve cert lookup callback from dirmngr. * sm/gpgsm.h (FIND_CERT_ALLOW_AMBIG): New. (FIND_CERT_WITH_EPHEM): New. * sm/certlist.c (gpgsm_find_cert): Replace arg allow_ambiguous by a generic flags arg. Implement the new flag FIND_CERT_WITH_EPHEM. * sm/call-dirmngr.c (inq_certificate): Return also ephemeral marked certs. -- The dirmngr may need to get a certificate from gpgsm's store in the course of verifying a CRL. In some cases the certificate is still marked as epehemeral - this needs to be returned as well. This _may_ also fix GnuPG-bug-id: 4436 --- sm/call-dirmngr.c | 7 ++++--- sm/certlist.c | 6 +++++- sm/gpgsm.h | 5 ++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c index da3839349..8e2761b1e 100644 --- a/sm/call-dirmngr.c +++ b/sm/call-dirmngr.c @@ -386,8 +386,8 @@ inq_certificate (void *opaque, const char *line) int err; ksba_cert_t cert; - - err = gpgsm_find_cert (parm->ctrl, line, ski, &cert, 1); + err = gpgsm_find_cert (parm->ctrl, line, ski, &cert, + FIND_CERT_ALLOW_AMBIG|FIND_CERT_WITH_EPHEM); if (err) { log_error ("certificate not found: %s\n", gpg_strerror (err)); @@ -1014,7 +1014,8 @@ run_command_inq_cb (void *opaque, const char *line) if (!*line) return gpg_error (GPG_ERR_ASS_PARAMETER); - err = gpgsm_find_cert (parm->ctrl, line, NULL, &cert, 1); + err = gpgsm_find_cert (parm->ctrl, line, NULL, &cert, + FIND_CERT_ALLOW_AMBIG); if (err) { log_error ("certificate not found: %s\n", gpg_strerror (err)); diff --git a/sm/certlist.c b/sm/certlist.c index b5f9f7874..fdf31a198 100644 --- a/sm/certlist.c +++ b/sm/certlist.c @@ -508,11 +508,12 @@ gpgsm_release_certlist (certlist_t list) int gpgsm_find_cert (ctrl_t ctrl, const char *name, ksba_sexp_t keyid, ksba_cert_t *r_cert, - int allow_ambiguous) + unsigned int flags) { int rc; KEYDB_SEARCH_DESC desc; KEYDB_HANDLE kh = NULL; + int allow_ambiguous = (flags & FIND_CERT_ALLOW_AMBIG); *r_cert = NULL; rc = classify_user_id (name, &desc, 0); @@ -523,6 +524,9 @@ gpgsm_find_cert (ctrl_t ctrl, rc = gpg_error (GPG_ERR_ENOMEM); else { + if ((flags & FIND_CERT_WITH_EPHEM)) + keydb_set_ephemeral (kh, 1); + nextone: rc = keydb_search (ctrl, kh, &desc, 1); if (!rc) diff --git a/sm/gpgsm.h b/sm/gpgsm.h index ced2d679f..e09dd75e9 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -404,8 +404,11 @@ int gpgsm_add_cert_to_certlist (ctrl_t ctrl, ksba_cert_t cert, int gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret, certlist_t *listaddr, int is_encrypt_to); void gpgsm_release_certlist (certlist_t list); + +#define FIND_CERT_ALLOW_AMBIG 1 +#define FIND_CERT_WITH_EPHEM 2 int gpgsm_find_cert (ctrl_t ctrl, const char *name, ksba_sexp_t keyid, - ksba_cert_t *r_cert, int allow_ambiguous); + ksba_cert_t *r_cert, unsigned int flags); /*-- keylist.c --*/ gpg_error_t gpgsm_list_keys (ctrl_t ctrl, strlist_t names, From 523b3e1773f51195b5ac1a07ed0c630d1f9d462e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 28 Feb 2023 14:41:04 +0100 Subject: [PATCH 21/60] gpgconf: Print some standard envvars with -X * tools/gpgconf.c (show_configs): Add a list of envvars and print them. -- Note that for simplicity we to not distinguish between Windows and Linux here. --- tools/gpgconf.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/gpgconf.c b/tools/gpgconf.c index 4afc4e9fd..6dcdc9f3c 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -1509,6 +1509,11 @@ show_configs (estream_t outfp) static const char *names[] = { "common.conf", "gpg-agent.conf", "scdaemon.conf", "dirmngr.conf", "gpg.conf", "gpgsm.conf" }; + static const char *envvars[] = { "PATH", + "http_proxy", "HTTP_PROXY", + "https_proxy", "HTTPS_PROXY", + "LD_LIBRARY_PATH", "LD_PRELOAD", + "LD_AUDIT", "LD_ORIGIN_PATH" }; gpg_error_t err; int idx; char *fname; @@ -1539,6 +1544,11 @@ show_configs (estream_t outfp) list_dirs (outfp, NULL, 1); es_fprintf (outfp, "\n"); + for (idx=0; idx < DIM(envvars); idx++) + if ((s = getenv (envvars[idx]))) + es_fprintf (outfp, "%s=%s\n", envvars[idx], s); + es_fprintf (outfp, "\n"); + fname = make_filename (gnupg_sysconfdir (), "gpgconf.conf", NULL); if (!gnupg_access (fname, F_OK)) { From 1aaadede76ccd17a6636b26ec75954d6b709f3fa Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 1 Mar 2023 16:49:40 +0100 Subject: [PATCH 22/60] agent: Show "no secret key" instead of "card removed". * agent/findkey.c (agent_key_from_file): Check the error of read_key_file again. * agent/pkdecrypt.c (agent_pkdecrypt): Restore error if no card was found. Also remove useless condition. -- The first patch fixes a likely merge error. The second is about the actual return code: If we have no smardcard but simply try to decrypt with the current smartcard we should return the originla error code. GnuPG-bug-id: 5170 Fixes-commit: eda3c688fc2e85c7cd63029cb9caf06552d203b4 --- agent/findkey.c | 9 +++++++++ agent/pkdecrypt.c | 15 +++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/agent/findkey.c b/agent/findkey.c index d3a3b335c..060cb786d 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -1186,6 +1186,15 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, return gpg_error (GPG_ERR_NO_SECKEY); err = read_key_file (grip? grip : ctrl->keygrip, &s_skey, &keymeta); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_ENOENT) + err = gpg_error (GPG_ERR_NO_SECKEY); + else + log_error ("findkey: error reading key file: %s\n", + gpg_strerror (err)); + return err; + } /* For use with the protection functions we also need the key as an canonical encoded S-expression in a buffer. Create this buffer diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index 82818f863..c26f21d35 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -74,8 +74,7 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, no_shadow_info = 1; else if (err) { - if (gpg_err_code (err) != GPG_ERR_NO_SECKEY) - log_error ("failed to read the secret key\n"); + log_error ("failed to read the secret key\n"); goto leave; } @@ -88,7 +87,7 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, goto leave; } - if (agent_is_tpm2_key (s_skey)) + if (s_skey && agent_is_tpm2_key (s_skey)) err = divert_tpm2_pkdecrypt (ctrl, ciphertext, shadow_info, &buf, &len, r_padding); else @@ -96,7 +95,15 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, &buf, &len, r_padding); if (err) { - log_error ("smartcard decryption failed: %s\n", gpg_strerror (err)); + /* We restore the original error (ie. no seckey) is no card + * has been found and we have no shadow key. This avoids a + * surprising "card removed" error code. */ + if ((gpg_err_code (err) == GPG_ERR_CARD_REMOVED + || gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) + && no_shadow_info) + err = gpg_error (GPG_ERR_NO_SECKEY); + else + log_error ("smartcard decryption failed: %s\n", gpg_strerror (err)); goto leave; } From 3a18378a92af63f5bccbe78efa546acb04e8a0f8 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 1 Mar 2023 17:22:20 +0100 Subject: [PATCH 23/60] gpg: Allow adding of Additional Decryption Subkeys. * g10/free-packet.c (copy_public_key): Factor some code out to ... (copy_public_key_basics): new. * g10/build-packet.c (build_sig_subpkt_from_sig): New arg signhints. * g10/packet.h (PUBKEY_USAGE_RENC): Fix value. (SIGNHINT_KEYSIG, SIGNHINT_SELFSIG): Moved from sign.c. (SIGNHINT_ADSK): New. (PKT_public_key): Change pubkey_usage from byte to u16. (PKT_user_id): Cosmetic fix: change help_key_usage from int to u16. * g10/getkey.c (parse_key_usage): Make public. * g10/misc.c (openpgp_pk_algo_usage): Take PUBKEY_USAGE_RENC in account. * g10/sign.c (update_keysig_packet): Set SIGNHINT_ADSK. (make_keysig_packet): Ditto. (do_sign): No time warp check in ADSK mode. * g10/sig-check.c (check_signature_metadata_validity): Ditto. * g10/keygen.c (struct opaque_data_usage_and_pk): Remove. (write_keybinding): Do not use the removed struct. (do_add_key_flags): Support PUBKEY_USAGE_RENC and others. (keygen_add_key_flags_and_expire): Rewrite and make public. * g10/keyedit.c (enum cmdids): Add cmdADDADSK. (keyedit_menu): Add command "addadsk". (menu_addadsk): New. -- This makes use of a new encryption flag: The "restricted encryption key" (2nd,0x04) does not take part in any automatic selection of encryption keys. It is only found on a subkey signature (type 0x18), one that refers to the key the flag applies to. Followup patches will add encryption support and a --quick command. GnuPG-bug-id: 6395 --- doc/DETAILS | 15 ++-- doc/gpg.texi | 9 +++ g10/build-packet.c | 12 ++-- g10/free-packet.c | 30 ++++++-- g10/getkey.c | 7 +- g10/keydb.h | 3 + g10/keyedit.c | 169 ++++++++++++++++++++++++++++++++++++++++++++- g10/keygen.c | 49 ++++++------- g10/main.h | 1 + g10/misc.c | 8 +-- g10/packet.h | 18 +++-- g10/sig-check.c | 3 +- g10/sign.c | 30 ++++---- 13 files changed, 278 insertions(+), 76 deletions(-) diff --git a/doc/DETAILS b/doc/DETAILS index e01b74ac1..0d86e4750 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -1724,17 +1724,10 @@ Description of some debug flags: ** gnupg.org notations - - adsk@gnupg.org :: Additional decryption subkey. This notation - gives a list of keys an implementation SHOULD - also encrypt to. The data consists of an array - of eight-octet numbers holding the Key ID of an - encryption subkey. This notation is only valid - on an encryption subkey (i.e. with first octet - of the key flags 0x04 or 0x08). Subkeys not on - the same keyblock MUST NOT be considered. For - interoperability this notation SHOULD NOT be - marked as criticial. Due to its nature it MUST - NOT be marked as human readable. + - rem@gnupg.org :: Used by Kleopatra to implement the tag feature. + These tags are used to mark keys for easier + searching and grouping. + ** Simplified revocation certificates Revocation certificates consist only of the signature packet; diff --git a/doc/gpg.texi b/doc/gpg.texi index 45cd97241..01b91abec 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -1067,6 +1067,15 @@ signing. "sensitive". If a designated revoker is marked as sensitive, it will not be exported by default (see export-options). + @item addadsk + @opindex keyedit:addadsk + Add an Additional Decryption Subkey. The user is asked to enter the + fingerprint of another encryption subkey. Note that the exact + fingerprint of another key's encryption subkey needs to be entered. + This is because commonly the primary key has no encryption + capability. Use the option @option{--with-subkey-fingerprint} with + a list command to display the subkey fingerprints. + @item passwd @opindex keyedit:passwd Change the passphrase of the secret key. diff --git a/g10/build-packet.c b/g10/build-packet.c index f33d156b3..192dfaef5 100644 --- a/g10/build-packet.c +++ b/g10/build-packet.c @@ -1345,19 +1345,23 @@ build_sig_subpkt (PKT_signature *sig, sigsubpkttype_t type, /* * Put all the required stuff from SIG into subpackets of sig. - * PKSK is the signing key. + * PKSK is the signing key. SIGNHINTS are various flags like + * SIGNHINT_ADSK. * Hmmm, should we delete those subpackets which are in a wrong area? */ void -build_sig_subpkt_from_sig (PKT_signature *sig, PKT_public_key *pksk) +build_sig_subpkt_from_sig (PKT_signature *sig, PKT_public_key *pksk, + unsigned int signhints) { u32 u; byte buf[1+MAX_FINGERPRINT_LEN]; size_t fprlen; /* For v4 keys we need to write the ISSUER subpacket. We do not - * want that for a future v5 format. */ - if (pksk->version < 5) + * want that for a future v5 format. We also don't write it if + * only the new RENC keyflag is set (implementations with support + * for this key flag should understand the ISSUER_FPR). */ + if (pksk->version < 5 && !(signhints & SIGNHINT_ADSK)) { u = sig->keyid[0]; buf[0] = (u >> 24) & 0xff; diff --git a/g10/free-packet.c b/g10/free-packet.c index 243cc7518..b0642043e 100644 --- a/g10/free-packet.c +++ b/g10/free-packet.c @@ -211,10 +211,10 @@ copy_prefs (const prefitem_t *prefs) /* Copy the public key S to D. If D is NULL allocate a new public key - structure. If S has seckret key infos, only the public stuff is - copied. */ + * structure. Only the basic stuff is copied; not any ancillary + * data. */ PKT_public_key * -copy_public_key (PKT_public_key *d, PKT_public_key *s) +copy_public_key_basics (PKT_public_key *d, PKT_public_key *s) { int n, i; @@ -222,8 +222,8 @@ copy_public_key (PKT_public_key *d, PKT_public_key *s) d = xmalloc (sizeof *d); memcpy (d, s, sizeof *d); d->seckey_info = NULL; - d->user_id = scopy_user_id (s->user_id); - d->prefs = copy_prefs (s->prefs); + d->user_id = NULL; + d->prefs = NULL; n = pubkey_get_npkey (s->pubkey_algo); i = 0; @@ -237,6 +237,24 @@ copy_public_key (PKT_public_key *d, PKT_public_key *s) for (; i < PUBKEY_MAX_NSKEY; i++) d->pkey[i] = NULL; + d->revkey = NULL; + d->serialno = NULL; + d->updateurl = NULL; + + return d; +} + + +/* Copy the public key S to D. If D is NULL allocate a new public key + structure. If S has seckret key infos, only the public stuff is + copied. */ +PKT_public_key * +copy_public_key (PKT_public_key *d, PKT_public_key *s) +{ + d = copy_public_key_basics (d, s); + d->user_id = scopy_user_id (s->user_id); + d->prefs = copy_prefs (s->prefs); + if (!s->revkey && s->numrevkeys) BUG(); if (s->numrevkeys) @@ -244,8 +262,6 @@ copy_public_key (PKT_public_key *d, PKT_public_key *s) d->revkey = xmalloc(sizeof(struct revocation_key)*s->numrevkeys); memcpy(d->revkey,s->revkey,sizeof(struct revocation_key)*s->numrevkeys); } - else - d->revkey = NULL; if (s->serialno) d->serialno = xstrdup (s->serialno); diff --git a/g10/getkey.c b/g10/getkey.c index 093ce61b0..3e94875b2 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -1809,12 +1809,12 @@ get_pubkey_from_buffer (ctrl_t ctrl, PKT_public_key *pkbuf, * returned public key may be a subkey rather than the primary key. * Note: The self-signed data has already been merged into the public * key using merge_selfsigs. Free *PK by calling - * release_public_key_parts (or, if PK was allocated using xfree, you + * release_public_key_parts (or, if PK was allocated using xmalloc, you * can use free_public_key, which calls release_public_key_parts(PK) * and then xfree(PK)). * * If PK->REQ_USAGE is set, it is used to filter the search results. - * (Thus, if PK is not NULL, PK->REQ_USAGE must be valid!!!) See the + * Thus, if PK is not NULL, PK->REQ_USAGE must be valid! See the * documentation for finish_lookup to understand exactly how this is * used. * @@ -2417,7 +2417,8 @@ merge_keys_and_selfsig (ctrl_t ctrl, kbnode_t keyblock) } -static int +/* This function parses the key flags and returns PUBKEY_USAGE_ flags. */ +unsigned int parse_key_usage (PKT_signature * sig) { int key_usage = 0; diff --git a/g10/keydb.h b/g10/keydb.h index 28b61d4a1..edbae5c3c 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -468,6 +468,9 @@ void setup_main_keyids (kbnode_t keyblock); data structures. */ void merge_keys_and_selfsig (ctrl_t ctrl, kbnode_t keyblock); +/* This function parses the key flags and returns PUBKEY_USAGE_ flags. */ +unsigned int parse_key_usage (PKT_signature *sig); + char *get_user_id_string_native (ctrl_t ctrl, u32 *keyid); char *get_long_user_id_string (ctrl_t ctrl, u32 *keyid); char *get_user_id (ctrl_t ctrl, u32 *keyid, size_t *rn, int *r_nouid); diff --git a/g10/keyedit.c b/g10/keyedit.c index 83c20b846..6c45fd640 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -1,7 +1,7 @@ /* keyedit.c - Edit properties of a key * Copyright (C) 1998-2010 Free Software Foundation, Inc. * Copyright (C) 1998-2017 Werner Koch - * Copyright (C) 2015, 2016, 2022 g10 Code GmbH + * Copyright (C) 2015, 2016, 2022-2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -73,6 +73,7 @@ static int menu_delsig (ctrl_t ctrl, kbnode_t pub_keyblock); static int menu_clean (ctrl_t ctrl, kbnode_t keyblock, int self_only); static void menu_delkey (KBNODE pub_keyblock); static int menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive); +static int menu_addadsk (ctrl_t ctrl, kbnode_t pub_keyblock); static gpg_error_t menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock, int unattended, u32 newexpiration); static int menu_changeusage (ctrl_t ctrl, kbnode_t keyblock); @@ -1243,7 +1244,7 @@ enum cmdids cmdREVSIG, cmdREVKEY, cmdREVUID, cmdDELSIG, cmdPRIMARY, cmdDEBUG, cmdSAVE, cmdADDUID, cmdADDPHOTO, cmdDELUID, cmdADDKEY, cmdDELKEY, cmdADDREVOKER, cmdTOGGLE, cmdSELKEY, cmdPASSWD, cmdTRUST, cmdPREF, - cmdEXPIRE, cmdCHANGEUSAGE, cmdBACKSIGN, + cmdEXPIRE, cmdCHANGEUSAGE, cmdBACKSIGN, cmdADDADSK, #ifndef NO_TRUST_MODELS cmdENABLEKEY, cmdDISABLEKEY, #endif /*!NO_TRUST_MODELS*/ @@ -1308,6 +1309,8 @@ static struct { "delkey", cmdDELKEY, 0, N_("delete selected subkeys")}, { "addrevoker", cmdADDREVOKER, KEYEDIT_NEED_SK, N_("add a revocation key")}, + { "addadsk", cmdADDADSK, KEYEDIT_NEED_SK, + N_("add additional decryption subkeys")}, { "delsig", cmdDELSIG, 0, N_("delete signatures from the selected user IDs")}, { "expire", cmdEXPIRE, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, @@ -2000,6 +2003,15 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, } break; + case cmdADDADSK: + if (menu_addadsk (ctrl, keyblock)) + { + redisplay = 1; + modified = 1; + merge_keys_and_selfsig (ctrl, keyblock); + } + break; + case cmdREVUID: { int n1; @@ -4643,6 +4655,159 @@ fail: } +/* + * Ask for a new additional decryption subkey and add it to the key + * block. Returns true if the keybloxk was changed and false + * otherwise. + */ +static int +menu_addadsk (ctrl_t ctrl, kbnode_t pub_keyblock) +{ + PKT_public_key *pk; + PKT_public_key *sub_pk; + PKT_public_key *main_pk; + PKT_public_key *adsk_pk = NULL; + kbnode_t adsk_keyblock = NULL; + PKT_signature *sig = NULL; + char *answer = NULL; + gpg_error_t err; + KEYDB_SEARCH_DESC desc; + byte fpr[MAX_FINGERPRINT_LEN]; + size_t fprlen; + kbnode_t node, node2; + kbnode_t subkeynode = NULL; + PACKET *pkt; /* (temp. use; will be put into a kbnode_t) */ + + log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY); + main_pk = pub_keyblock->pkt->pkt.public_key; + + for (;;) + { + xfree (answer); + answer = cpr_get_utf8 + ("keyedit.addadsk", + _("Enter the fingerprint of the additional decryption subkey: ")); + if (answer[0] == '\0' || answer[0] == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } + if (classify_user_id (answer, &desc, 1) + || desc.mode != KEYDB_SEARCH_MODE_FPR) + { + log_info (_("\"%s\" is not a fingerprint\n"), answer); + continue; + } + + free_public_key (adsk_pk); + adsk_pk = xcalloc (1, sizeof *adsk_pk); + adsk_pk->req_usage = PUBKEY_USAGE_ENC; + release_kbnode (adsk_keyblock); + adsk_keyblock = NULL; + err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, + NULL, adsk_pk, answer, &adsk_keyblock, NULL, 1); + if (err) + { + log_info (_("key \"%s\" not found: %s\n"), answer, + gpg_strerror (err)); + if (!opt.batch && gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY) + log_info (_("Did you specify the fingerprint of a subkey?\n")); + continue; + } + + for (node = adsk_keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + pk = node->pkt->pkt.public_key; + fingerprint_from_pk (pk, fpr, &fprlen); + if (fprlen == desc.fprlen + && !memcmp (fpr, desc.u.fpr, fprlen) + && (pk->pubkey_usage & PUBKEY_USAGE_ENC)) + break; + } + } + if (!node) + { + err = gpg_error (GPG_ERR_WRONG_KEY_USAGE); + log_info (_("key \"%s\" not found: %s\n"), answer, + gpg_strerror (err)); + if (!opt.batch) + log_info (_("Did you specify the fingerprint of a subkey?\n")); + continue; + } + + /* Check that the selected subkey is not yet on our keyblock. */ + for (node2 = pub_keyblock; node2; node2 = node2->next) + { + if (node2->pkt->pkttype == PKT_PUBLIC_KEY + || node2->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + pk = node2->pkt->pkt.public_key; + fingerprint_from_pk (pk, fpr, &fprlen); + if (fprlen == desc.fprlen + && !memcmp (fpr, desc.u.fpr, fprlen)) + break; + } + } + if (node2) + { + log_info (_("key \"%s\" is already on this keyblock\n"), answer); + continue; + } + + break; + } + + /* Append the subkey. + * Note that we don't use the ADSK_PK directly because this is the + * primary key and in general we use a subkey to which NODE points. + * ADSK_PK has only been used to pass the requested key usage to + * get_pubkey_byname. SUB_PK will point to the actual adsk. */ + log_assert (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY); + sub_pk = copy_public_key_basics (NULL, node->pkt->pkt.public_key); + keyid_from_pk (main_pk, sub_pk->main_keyid); /* Fixup main keyid. */ + log_assert ((sub_pk->pubkey_usage & PUBKEY_USAGE_ENC)); + sub_pk->pubkey_usage = PUBKEY_USAGE_RENC; /* 'e' -> 'r' */ + pkt = xcalloc (1, sizeof *pkt); + pkt->pkttype = PKT_PUBLIC_SUBKEY; /* Make sure it is a subkey. */ + pkt->pkt.public_key = sub_pk; + subkeynode = new_kbnode (pkt); + + /* Make the signature. */ + err = make_keysig_packet (ctrl, &sig, main_pk, NULL, sub_pk, main_pk, 0x18, + sub_pk->timestamp, 0, + keygen_add_key_flags_and_expire, sub_pk, NULL); + if (err) + { + write_status_error ("keysig", err); + log_error ("creating key binding failed: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Append the subkey packet and the binding signature. */ + add_kbnode (pub_keyblock, subkeynode); + subkeynode = NULL; + pkt = xcalloc (1, sizeof *pkt); + pkt->pkttype = PKT_SIGNATURE; + pkt->pkt.signature = sig; + add_kbnode (pub_keyblock, new_kbnode (pkt)); + + leave: + xfree (answer); + free_public_key (adsk_pk); + release_kbnode (adsk_keyblock); + release_kbnode (subkeynode); + if (!err) + return 1; /* The keyblock was modified. */ + else + return 0; /* Not modified. */ + +} + + /* With FORCE_MAINKEY cleared this function handles the interactive * menu option "expire". With UNATTENDED set to 1 this function only * sets the expiration date of the primary key to NEWEXPIRATION and diff --git a/g10/keygen.c b/g10/keygen.c index 2e07015c6..c97783124 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -130,12 +130,6 @@ struct output_control_s }; -struct opaque_data_usage_and_pk { - unsigned int usage; - PKT_public_key *pk; -}; - - /* FIXME: These globals vars are ugly. And using MAX_PREFS even for * aeads is useless, given that we don't expects more than a very few * algorithms. */ @@ -256,22 +250,27 @@ write_uid (kbnode_t root, const char *s) static void do_add_key_flags (PKT_signature *sig, unsigned int use) { - byte buf[1]; + byte buf[2] = { 0, 0 }; - buf[0] = 0; + /* The spec says that all primary keys MUST be able to certify. */ + if ( sig->sig_class != 0x18 ) + buf[0] |= 0x01; - /* The spec says that all primary keys MUST be able to certify. */ - if(sig->sig_class!=0x18) - buf[0] |= 0x01; + if (use & PUBKEY_USAGE_SIG) + buf[0] |= 0x02; + if (use & PUBKEY_USAGE_ENC) + buf[0] |= 0x04 | 0x08; + if (use & PUBKEY_USAGE_AUTH) + buf[0] |= 0x20; + if (use & PUBKEY_USAGE_GROUP) + buf[0] |= 0x80; - if (use & PUBKEY_USAGE_SIG) - buf[0] |= 0x02; - if (use & PUBKEY_USAGE_ENC) - buf[0] |= 0x04 | 0x08; - if (use & PUBKEY_USAGE_AUTH) - buf[0] |= 0x20; + if (use & PUBKEY_USAGE_RENC) + buf[1] |= 0x04; + if (use & PUBKEY_USAGE_TIME) + buf[1] |= 0x08; - build_sig_subpkt (sig, SIGSUBPKT_KEY_FLAGS, buf, 1); + build_sig_subpkt (sig, SIGSUBPKT_KEY_FLAGS, buf, buf[1]? 2:1); } @@ -318,13 +317,11 @@ keygen_add_key_flags (PKT_signature *sig, void *opaque) } -static int +int keygen_add_key_flags_and_expire (PKT_signature *sig, void *opaque) { - struct opaque_data_usage_and_pk *oduap = opaque; - - do_add_key_flags (sig, oduap->usage); - return keygen_add_key_expire (sig, oduap->pk); + keygen_add_key_flags (sig, opaque); + return keygen_add_key_expire (sig, opaque); } @@ -1215,7 +1212,6 @@ write_keybinding (ctrl_t ctrl, kbnode_t root, PKT_signature *sig; KBNODE node; PKT_public_key *pri_pk, *sub_pk; - struct opaque_data_usage_and_pk oduap; if (opt.verbose) log_info(_("writing key binding signature\n")); @@ -1241,11 +1237,10 @@ write_keybinding (ctrl_t ctrl, kbnode_t root, BUG(); /* Make the signature. */ - oduap.usage = use; - oduap.pk = sub_pk; + sub_pk->pubkey_usage = use; err = make_keysig_packet (ctrl, &sig, pri_pk, NULL, sub_pk, pri_psk, 0x18, timestamp, 0, - keygen_add_key_flags_and_expire, &oduap, + keygen_add_key_flags_and_expire, sub_pk, cache_nonce); if (err) { diff --git a/g10/main.h b/g10/main.h index 62d2651be..f66f3ef0c 100644 --- a/g10/main.h +++ b/g10/main.h @@ -315,6 +315,7 @@ int keygen_set_std_prefs (const char *string,int personal); PKT_user_id *keygen_get_std_prefs (void); int keygen_add_key_expire( PKT_signature *sig, void *opaque ); int keygen_add_key_flags (PKT_signature *sig, void *opaque); +int keygen_add_key_flags_and_expire (PKT_signature *sig, void *opaque); int keygen_add_std_prefs( PKT_signature *sig, void *opaque ); int keygen_upd_std_prefs( PKT_signature *sig, void *opaque ); int keygen_add_keyserver_url(PKT_signature *sig, void *opaque); diff --git a/g10/misc.c b/g10/misc.c index 05d232f8f..2f4b452dd 100644 --- a/g10/misc.c +++ b/g10/misc.c @@ -777,21 +777,21 @@ openpgp_pk_algo_usage ( int algo ) switch ( algo ) { case PUBKEY_ALGO_RSA: use = (PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG - | PUBKEY_USAGE_ENC | PUBKEY_USAGE_AUTH); + | PUBKEY_USAGE_ENC | PUBKEY_USAGE_RENC | PUBKEY_USAGE_AUTH); break; case PUBKEY_ALGO_RSA_E: case PUBKEY_ALGO_ECDH: - use = PUBKEY_USAGE_ENC; + use = PUBKEY_USAGE_ENC | PUBKEY_USAGE_RENC; break; case PUBKEY_ALGO_RSA_S: use = PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG; break; case PUBKEY_ALGO_ELGAMAL: if (RFC2440) - use = PUBKEY_USAGE_ENC; + use = PUBKEY_USAGE_ENC | PUBKEY_USAGE_RENC; break; case PUBKEY_ALGO_ELGAMAL_E: - use = PUBKEY_USAGE_ENC; + use = PUBKEY_USAGE_ENC | PUBKEY_USAGE_RENC; break; case PUBKEY_ALGO_DSA: use = PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG | PUBKEY_USAGE_AUTH; diff --git a/g10/packet.h b/g10/packet.h index eeea9b450..39dab96c9 100644 --- a/g10/packet.h +++ b/g10/packet.h @@ -56,9 +56,15 @@ | GCRY_PK_USAGE_AUTH | GCRY_PK_USAGE_UNKN) >= 256 # error Please choose another value for PUBKEY_USAGE_NONE #endif -#define PUBKEY_USAGE_RENC 512 /* Restricted encryption. */ -#define PUBKEY_USAGE_TIME 1024 /* Timestamp use. */ #define PUBKEY_USAGE_GROUP 512 /* Group flag. */ +#define PUBKEY_USAGE_RENC 1024 /* Restricted encryption. */ +#define PUBKEY_USAGE_TIME 2048 /* Timestamp use. */ + +/* Bitflags to convey hints on what kind of signature is created. */ +#define SIGNHINT_KEYSIG 1 +#define SIGNHINT_SELFSIG 2 +#define SIGNHINT_ADSK 4 + /* Helper macros. */ #define is_RSA(a) ((a)==PUBKEY_ALGO_RSA || (a)==PUBKEY_ALGO_RSA_E \ @@ -287,7 +293,7 @@ typedef struct /* The length of ATTRIB_DATA. */ unsigned long attrib_len; byte *namehash; - int help_key_usage; + u16 help_key_usage; u32 help_key_expire; int help_full_count; int help_marginal_count; @@ -388,7 +394,7 @@ typedef struct byte selfsigversion; /* highest version of all of the self-sigs */ /* The public key algorithm. (Serialized.) */ byte pubkey_algo; - byte pubkey_usage; /* for now only used to pass it to getkey() */ + u16 pubkey_usage; /* carries the usage info. */ byte req_usage; /* hack to pass a request to getkey() */ byte fprlen; /* 0 or length of FPR. */ u32 has_expired; /* set to the expiration date if expired */ @@ -861,7 +867,8 @@ gpg_error_t gpg_mpi_write_nohdr (iobuf_t out, gcry_mpi_t a); u32 calc_packet_length( PACKET *pkt ); void build_sig_subpkt( PKT_signature *sig, sigsubpkttype_t type, const byte *buffer, size_t buflen ); -void build_sig_subpkt_from_sig (PKT_signature *sig, PKT_public_key *pksk); +void build_sig_subpkt_from_sig (PKT_signature *sig, PKT_public_key *pksk, + unsigned int signhints); int delete_sig_subpkt(subpktarea_t *buffer, sigsubpkttype_t type ); void build_attribute_subpkt(PKT_user_id *uid,byte type, const void *buf,u32 buflen, @@ -883,6 +890,7 @@ void free_user_id( PKT_user_id *uid ); void free_comment( PKT_comment *rem ); void free_packet (PACKET *pkt, parse_packet_ctx_t parsectx); prefitem_t *copy_prefs (const prefitem_t *prefs); +PKT_public_key *copy_public_key_basics (PKT_public_key *d, PKT_public_key *s); PKT_public_key *copy_public_key( PKT_public_key *d, PKT_public_key *s ); PKT_signature *copy_signature( PKT_signature *d, PKT_signature *s ); PKT_user_id *scopy_user_id (PKT_user_id *sd ); diff --git a/g10/sig-check.c b/g10/sig-check.c index 7a2c934cd..06329f659 100644 --- a/g10/sig-check.c +++ b/g10/sig-check.c @@ -363,7 +363,8 @@ check_signature_metadata_validity (PKT_public_key *pk, PKT_signature *sig, if (r_revoked) *r_revoked = 0; - if (pk->timestamp > sig->timestamp ) + if (pk->timestamp > sig->timestamp + && !(parse_key_usage (sig) & PUBKEY_USAGE_RENC)) { ulong d = pk->timestamp - sig->timestamp; if ( d < 86400 ) diff --git a/g10/sign.c b/g10/sign.c index a66410ebd..b5e9d422d 100644 --- a/g10/sign.c +++ b/g10/sign.c @@ -50,11 +50,6 @@ #endif -/* Bitflags to convey hints on what kind of signayire is created. */ -#define SIGNHINT_KEYSIG 1 -#define SIGNHINT_SELFSIG 2 - - /* Hack */ static int recipient_digest_algo; @@ -416,7 +411,10 @@ do_sign (ctrl_t ctrl, PKT_public_key *pksk, PKT_signature *sig, byte *dp; char *hexgrip; - if (pksk->timestamp > sig->timestamp ) + /* An ADSK key commonly has a creation date older than the primary + * key. For example because the ADSK is used as an archive key for + * a group of users. */ + if (pksk->timestamp > sig->timestamp && !(signhints & SIGNHINT_ADSK)) { ulong d = pksk->timestamp - sig->timestamp; log_info (ngettext("key %s was created %lu second" @@ -964,7 +962,7 @@ write_signature_packets (ctrl_t ctrl, if (gcry_md_copy (&md, hash)) BUG (); - build_sig_subpkt_from_sig (sig, pk); + build_sig_subpkt_from_sig (sig, pk, 0); mk_notation_policy_etc (ctrl, sig, NULL, pk); if (opt.flags.include_key_block && IS_SIG (sig)) err = mk_sig_subpkt_key_block (ctrl, sig, pk); @@ -1758,14 +1756,14 @@ sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t locusr) * * SIGCLASS is the type of signature to create. * - * DIGEST_ALGO is the digest algorithm. If it is 0 the function - * selects an appropriate one. - * * TIMESTAMP is the timestamp to use for the signature. 0 means "now" * * DURATION is the amount of time (in seconds) until the signature * expires. * + * If CACHED_NONCE is not NULL the agent may use it to avoid + * additional pinnetry popups for the same keyblock. + * * This function creates the following subpackets: issuer, created, * and expire (if duration is not 0). Additional subpackets can be * added using MKSUBPKT, which is called after these subpackets are @@ -1833,6 +1831,8 @@ make_keysig_packet (ctrl_t ctrl, { /* Hash the subkey binding/backsig/revocation. */ hash_public_key (md, subpk); + if ((subpk->pubkey_usage & PUBKEY_USAGE_RENC)) + signhints |= SIGNHINT_ADSK; } else if (sigclass != 0x1F && sigclass != 0x20) { @@ -1852,7 +1852,7 @@ make_keysig_packet (ctrl_t ctrl, sig->expiredate = sig->timestamp + duration; sig->sig_class = sigclass; - build_sig_subpkt_from_sig (sig, pksk); + build_sig_subpkt_from_sig (sig, pksk, signhints); mk_notation_policy_etc (ctrl, sig, pk, pksk); /* Crucial that the call to mksubpkt comes LAST before the calls @@ -1976,6 +1976,12 @@ update_keysig_packet (ctrl_t ctrl, } } + /* Detect an ADSK key binding signature. */ + if ((sig->sig_class == 0x18 + || sig->sig_class == 0x19 || sig->sig_class == 0x28) + && (pk->pubkey_usage & PUBKEY_USAGE_RENC)) + signhints |= SIGNHINT_ADSK; + /* Note that already expired sigs will remain expired (with a * duration of 1) since build-packet.c:build_sig_subpkt_from_sig * detects this case. */ @@ -1984,7 +1990,7 @@ update_keysig_packet (ctrl_t ctrl, * automagically lower any sig expiration dates to correctly * correspond to the differences in the timestamps (i.e. the * duration will shrink). */ - build_sig_subpkt_from_sig (sig, pksk); + build_sig_subpkt_from_sig (sig, pksk, signhints); if (mksubpkt) rc = (*mksubpkt)(sig, opaque); From ef5a48dd5178f61fd0ab1801d980102b2fe4d464 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 1 Mar 2023 18:56:29 +0100 Subject: [PATCH 24/60] gpg: Actually encrypt to ADSKs. * g10/getkey.c (get_pubkey_fromfile): Add optional arg r_keyblock. * g10/pkclist.c (find_and_check_key): Also encrypt to RENC subkeys. -- GnuPG-bug-id: 6395 --- g10/getkey.c | 14 +++++++++++--- g10/keydb.h | 3 ++- g10/pkclist.c | 36 ++++++++++++++++++++++++++---------- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/g10/getkey.c b/g10/getkey.c index 3e94875b2..1b37c597d 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -1718,7 +1718,8 @@ get_best_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode, * * This function returns 0 on success. Otherwise, an error code is * returned. In particular, GPG_ERR_NO_PUBKEY is returned if the key - * is not found. + * is not found. If R_KEYBLOCK is not NULL and a key was found the + * keyblock is stored there; otherwiese NULL is stored there. * * The self-signed data has already been merged into the public key * using merge_selfsigs. The caller must release the content of PK by @@ -1726,13 +1727,17 @@ get_best_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode, * free_public_key). */ gpg_error_t -get_pubkey_fromfile (ctrl_t ctrl, PKT_public_key *pk, const char *fname) +get_pubkey_fromfile (ctrl_t ctrl, PKT_public_key *pk, const char *fname, + kbnode_t *r_keyblock) { gpg_error_t err; kbnode_t keyblock; kbnode_t found_key; unsigned int infoflags; + if (r_keyblock) + *r_keyblock = NULL; + err = read_key_from_file_or_buffer (ctrl, fname, NULL, 0, &keyblock); if (!err) { @@ -1747,7 +1752,10 @@ get_pubkey_fromfile (ctrl_t ctrl, PKT_public_key *pk, const char *fname) err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY); } - release_kbnode (keyblock); + if (!err && r_keyblock) + *r_keyblock = keyblock; + else + release_kbnode (keyblock); return err; } diff --git a/g10/keydb.h b/g10/keydb.h index edbae5c3c..9323e3137 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -391,7 +391,8 @@ gpg_error_t get_best_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode, /* Get a public key directly from file FNAME. */ gpg_error_t get_pubkey_fromfile (ctrl_t ctrl, - PKT_public_key *pk, const char *fname); + PKT_public_key *pk, const char *fname, + kbnode_t *r_keyblock); /* Get a public key from a buffer. */ gpg_error_t get_pubkey_from_buffer (ctrl_t ctrl, PKT_public_key *pkbuf, diff --git a/g10/pkclist.c b/g10/pkclist.c index 459e7595a..2e8932b9c 100644 --- a/g10/pkclist.c +++ b/g10/pkclist.c @@ -845,7 +845,8 @@ find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use, { int rc; PKT_public_key *pk; - KBNODE keyblock = NULL; + kbnode_t keyblock = NULL; + kbnode_t node; if (!name || !*name) return gpg_error (GPG_ERR_INV_USER_ID); @@ -856,7 +857,7 @@ find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use, pk->req_usage = use; if (from_file) - rc = get_pubkey_fromfile (ctrl, pk, name); + rc = get_pubkey_fromfile (ctrl, pk, name, &keyblock); else rc = get_best_pubkey_byname (ctrl, GET_PUBKEY_NORMAL, NULL, pk, name, &keyblock, 0); @@ -895,10 +896,10 @@ find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use, int trustlevel; trustlevel = get_validity (ctrl, keyblock, pk, pk->user_id, NULL, 1); - release_kbnode (keyblock); if ( (trustlevel & TRUST_FLAG_DISABLED) ) { /* Key has been disabled. */ + release_kbnode (keyblock); send_status_inv_recp (13, name); log_info (_("%s: skipped: public key is disabled\n"), name); free_public_key (pk); @@ -908,6 +909,7 @@ find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use, if ( !do_we_trust_pre (ctrl, pk, trustlevel) ) { /* We don't trust this key. */ + release_kbnode (keyblock); send_status_inv_recp (10, name); free_public_key (pk); return GPG_ERR_UNUSABLE_PUBKEY; @@ -926,19 +928,33 @@ find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use, { pk_list_t r; - r = xtrymalloc (sizeof *r); - if (!r) - { - rc = gpg_error_from_syserror (); - free_public_key (pk); - return rc; - } + r = xmalloc (sizeof *r); r->pk = pk; r->next = *pk_list_addr; r->flags = mark_hidden? 1:0; *pk_list_addr = r; } + for (node = keyblock; node; node = node->next) + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + && ((pk=node->pkt->pkt.public_key)->pubkey_usage & PUBKEY_USAGE_RENC) + && pk->flags.valid + && !pk->flags.revoked + && !pk->flags.disabled + && !pk->has_expired + && key_present_in_pk_list (*pk_list_addr, pk)) + { + pk_list_t r; + + r = xmalloc (sizeof *r); + r->pk = copy_public_key (NULL, pk); + r->next = *pk_list_addr; + r->flags = mark_hidden? 1:0; /* FIXME: Use PK_LIST_HIDDEN ? */ + *pk_list_addr = r; + } + + + release_kbnode (keyblock); return 0; } From 6bfb4a8d1202056efe4760c26292c6beaec7bcbb Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 3 Mar 2023 08:50:08 +0100 Subject: [PATCH 25/60] doc: Typo fixes and new notes in DETAILS -- --- dirmngr/ldap.c | 2 +- doc/DETAILS | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/dirmngr/ldap.c b/dirmngr/ldap.c index f9f6d5f1a..b80012d03 100644 --- a/dirmngr/ldap.c +++ b/dirmngr/ldap.c @@ -525,7 +525,7 @@ make_one_filter (const char *pattern, char **r_result) { /* We need just the BaseDN. This assumes that the Subject * is correcly stored in the DT. This is however not always - * the case and the actual DN is different ffrom the + * the case and the actual DN is different from the * subject. In this case we won't find anything. */ if (extfilt_need_escape (pattern) && !(pattern = pattern_buffer = extfilt_escape (pattern))) diff --git a/doc/DETAILS b/doc/DETAILS index 0d86e4750..10307bae0 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -1706,15 +1706,21 @@ Description of some debug flags: - RFC-5915 :: ECC Private Key Structure - RFC-5958 :: Asymmetric Key Packages - RFC-6337 :: ECC in OpenPGP + - RFC-7748 :: Elliptic Curves for Security (X25519 and X448) + - RFC-8410 :: Algorithm Identifiers for Ed25519, Ed448, X25519, and X448 - RFC-7292 :: PKCS #12: Personal Information Exchange Syntax v1.1 - RFC-8351 :: The PKCS #8 EncryptedPrivateKeyInfo Media Type - RFC-8550 :: S/MIME Version 4.0 Certificate Handling - RFC-8551 :: S/MIME Version 4.0 Message Specification - RFC-2634 :: Enhanced Security Services for S/MIME - RFC-5035 :: Enhanced Security Services (ESS) Update + - RFC-7253 :: The OCB Authenticated-Encryption Algorithm - draft-koch-openpgp-2015-rfc4880bis :: Updates to RFC-4880 + - T6390 :: Notes on use of X25519 in GnuPG (https://dev.gnupg.org/T6390) + + ** v3 fingerprints For packet version 3 we calculate the keyids this way: - RSA :: Low 64 bits of n From 4e391d95e0711646af649167c7de018cb6367097 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 8 Mar 2023 11:33:18 +0900 Subject: [PATCH 26/60] scd: Fix checking memory allocation. * scd/app-openpgp.c (read_public_key): Fix the memory. -- Signed-off-by: NIIBE Yutaka --- scd/app-openpgp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index e445b2409..f5d0b5111 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -1863,7 +1863,7 @@ read_public_key (app_t app, ctrl_t ctrl, u32 created_at, int keyno, len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); keybuf = xtrymalloc (len); - if (!data) + if (!keybuf) { err = gpg_error_from_syserror (); gcry_sexp_release (s_pkey); From 2a13f7f9dc75265ece649e30fecd3dc694b1240e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 8 Mar 2023 10:57:25 +0100 Subject: [PATCH 27/60] gpgsm: Strip trailing zeroes from detached signatures. * common/ksba-io-support.c: Include tlv.h (struct reader_cb_parm_s): Add new fields. (starts_with_sequence): New. (simple_reader_cb): Handle stripping. * common/ksba-io-support.h (GNUPG_KSBA_IO_STRIP): New. (gnupg_ksba_create_reader): Handle the new flag. * sm/verify.c (gpgsm_verify): Use the new flag for detached signatures. -- Note that this works only if --assume-binary is given. The use case for the feature is PDF signature checking where the PDF specs require that the detached signature is padded with zeroes. --- common/ksba-io-support.c | 107 ++++++++++++++++++++++++++++++++++++++- common/ksba-io-support.h | 1 + common/tlv.c | 3 +- doc/gpgsm.texi | 6 ++- sm/verify.c | 10 +++- 5 files changed, 120 insertions(+), 7 deletions(-) diff --git a/common/ksba-io-support.c b/common/ksba-io-support.c index 2832a4f3d..a279b67ad 100644 --- a/common/ksba-io-support.c +++ b/common/ksba-io-support.c @@ -40,6 +40,7 @@ #include "util.h" #include "i18n.h" +#include "tlv.h" #include "ksba-io-support.h" @@ -65,6 +66,12 @@ struct reader_cb_parm_s int autodetect; /* Try to detect the input encoding. */ int assume_pem; /* Assume input encoding is PEM. */ int assume_base64; /* Assume input is base64 encoded. */ + int strip_zeroes; /* Expect a SEQUENCE followed by zero padding. */ + /* 1 = check state; 2 = reading; 3 = checking */ + /* for zeroes. */ + int use_maxread; /* If true read not more than MAXREAD. */ + unsigned int maxread; /* # of bytes left to read. */ + off_t nzeroes; /* Number of padding zeroes red. */ int identified; int is_pem; @@ -390,6 +397,55 @@ base64_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) } +/* Read up to 10 bytes to test whether the data consist of a sequence; + * if that is true, set the limited flag and record the length of the + * entire sequence in PARM. Unget everything then. Return true if we + * have a sequence with a fixed length. */ +static int +starts_with_sequence (struct reader_cb_parm_s *parm) +{ + gpg_error_t err; + unsigned char peekbuf[10]; + int npeeked, c; + int found = 0; + const unsigned char *p; + size_t n, objlen, hdrlen; + int class, tag, constructed, ndef; + + for (npeeked=0; npeeked < sizeof peekbuf; npeeked++) + { + c = es_getc (parm->fp); + if (c == EOF) + goto leave; + peekbuf[npeeked] = c; + } + /* Enough to check for a sequence. */ + + p = peekbuf; + n = npeeked; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + { + log_debug ("%s: error parsing data: %s\n", __func__, gpg_strerror (err)); + goto leave; + } + + if (class == CLASS_UNIVERSAL && constructed && tag == TAG_SEQUENCE && !ndef) + { + /* We need to add 1 due to the way we implement the limit. */ + parm->maxread = objlen + hdrlen + 1; + if (!(parm->maxread < objlen + hdrlen) && parm->maxread) + parm->use_maxread = 1; + found = 1; + } + + leave: + while (npeeked) + es_ungetc (peekbuf[--npeeked], parm->fp); + return found; +} + static int simple_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) @@ -402,9 +458,55 @@ simple_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) if (!buffer) return -1; /* not supported */ + restart: + if (parm->strip_zeroes) + { + if (parm->strip_zeroes == 1) + { + if (starts_with_sequence (parm)) + parm->strip_zeroes = 2; /* Found fixed length sequence. */ + else + parm->strip_zeroes = 0; /* Disable zero padding check. */ + } + else if (parm->strip_zeroes == 3) + { + /* Limit reached - check that only zeroes follow. */ + while (!(c = es_getc (parm->fp))) + parm->nzeroes++; + if (c == EOF) + { /* only zeroes found. Reset zero padding engine and + * return EOF. */ + parm->strip_zeroes = 0; + parm->eof_seen = 1; + return -1; + } + /* Not only zeroes. Reset engine and continue. */ + parm->strip_zeroes = 0; + } + } + for (n=0; n < count; n++) { - c = es_getc (parm->fp); + if (parm->use_maxread && !--parm->maxread) + { + parm->use_maxread = 0; + if (parm->strip_zeroes) + { + parm->strip_zeroes = 3; + parm->nzeroes = 0; + if (n) + goto leave; /* Return what we already got. */ + goto restart; /* Immediately check for trailing zeroes. */ + } + } + + if (parm->nzeroes) + { + parm->nzeroes--; + c = 0; + } + else + c = es_getc (parm->fp); if (c == EOF) { parm->eof_seen = 1; @@ -417,6 +519,7 @@ simple_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) *(byte *)buffer++ = c; } + leave: *nread = n; return 0; } @@ -575,6 +678,7 @@ base64_finish_write (struct writer_cb_parm_s *parm) * GNUPG_KSBA_IO_MULTIPEM - The reader expects that the caller uses * ksba_reader_clear after EOF until no more * objects were found. + * GNUPG_KSBA_IO_STRIP - Strip zero padding from some CMS objects. * * Note that the PEM flag has a higher priority than the BASE64 flag * which in turn has a gight priority than the AUTODETECT flag. @@ -592,6 +696,7 @@ gnupg_ksba_create_reader (gnupg_ksba_io_t *ctx, if (!*ctx) return out_of_core (); (*ctx)->u.rparm.allow_multi_pem = !!(flags & GNUPG_KSBA_IO_MULTIPEM); + (*ctx)->u.rparm.strip_zeroes = !!(flags & GNUPG_KSBA_IO_STRIP); rc = ksba_reader_new (&r); if (rc) diff --git a/common/ksba-io-support.h b/common/ksba-io-support.h index e33e0ed74..02e541b16 100644 --- a/common/ksba-io-support.h +++ b/common/ksba-io-support.h @@ -36,6 +36,7 @@ #define GNUPG_KSBA_IO_BASE64 2 /* Plain Base64 format. */ #define GNUPG_KSBA_IO_AUTODETECT 4 /* Try to autodetect the format. */ #define GNUPG_KSBA_IO_MULTIPEM 8 /* Allow more than one PEM chunk. */ +#define GNUPG_KSBA_IO_STRIP 16 /* Strip off zero padding. */ /* Context object. */ diff --git a/common/tlv.c b/common/tlv.c index 9618d04cb..4cc1dc7cf 100644 --- a/common/tlv.c +++ b/common/tlv.c @@ -156,8 +156,7 @@ gpg_error_t parse_ber_header (unsigned char const **buffer, size_t *size, int *r_class, int *r_tag, int *r_constructed, int *r_ndef, - size_t *r_length, size_t *r_nhdr) -{ + size_t *r_length, size_t *r_nhdr){ int c; unsigned long tag; const unsigned char *buf = *buffer; diff --git a/doc/gpgsm.texi b/doc/gpgsm.texi index a328ea5f0..42090a93f 100644 --- a/doc/gpgsm.texi +++ b/doc/gpgsm.texi @@ -492,8 +492,10 @@ This usually means that Dirmngr is employed to search for the certificate. Note that this option makes a "web bug" like behavior possible. LDAP server operators can see which keys you request, so by sending you a message signed by a brand new key (which you naturally -will not have on your local keybox), the operator can tell both your IP -address and the time when you verified the signature. +will not have on your local keybox), the operator can tell both your +IP address and the time when you verified the signature. Note that if +CRL checking is not disabled issuer certificates are retrieved in any +case using the caIssuers authorityInfoAccess method. @anchor{gpgsm-option --validation-model} diff --git a/sm/verify.c b/sm/verify.c index 9f1216f83..a07d1c9c7 100644 --- a/sm/verify.c +++ b/sm/verify.c @@ -105,12 +105,17 @@ gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, estream_t out_fp) int signer; const char *algoid; int algo; - int is_detached; + int is_detached, maybe_detached; estream_t in_fp = NULL; char *p; audit_set_type (ctrl->audit, AUDIT_TYPE_VERIFY); + /* Although we detect detached signatures during the parsing phase, + * we need to know it earlier and thus accept the caller idea of + * what to verify. */ + maybe_detached = (data_fd != -1); + kh = keydb_new (ctrl); if (!kh) { @@ -131,7 +136,8 @@ gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, estream_t out_fp) rc = gnupg_ksba_create_reader (&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0) | (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0) - | (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0)), + | (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0) + | (maybe_detached? GNUPG_KSBA_IO_STRIP : 0)), in_fp, &reader); if (rc) { From 2d088176b4bdfc38f2a5bb853e14207a85a15f73 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 8 Mar 2023 14:36:11 +0100 Subject: [PATCH 28/60] dirmngr: Minor code cleanup in the CRL cache. * dirmngr/crlcache.c (INVCRL_TOO_OLD): New. (INVCRL_UNKNOWN_EXTN, INVCRL_GENERAL): New. (open_dir, crl_cache_insert): Use the new constants. (list_one_crl_entry): Make diagnostics robust for new INVCRL codes. --- dirmngr/crlcache.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c index befc6b94b..5d793494e 100644 --- a/dirmngr/crlcache.c +++ b/dirmngr/crlcache.c @@ -125,6 +125,13 @@ # define O_BINARY 0 #endif + +/* Reason flags for an invalid CRL. */ +#define INVCRL_TOO_OLD 1 +#define INVCRL_UNKNOWN_EXTN 2 +#define INVCRL_GENERAL 127 + + static const char oidstr_crlNumber[] = "2.5.29.20"; /* static const char oidstr_issuingDistributionPoint[] = "2.5.29.28"; */ static const char oidstr_authorityKeyIdentifier[] = "2.5.29.35"; @@ -569,8 +576,8 @@ open_dir (crl_cache_t *r_cache) if (*line == 'i') { entry->invalid = atoi (line+1); - if (entry->invalid < 1) - entry->invalid = 1; + if (!entry->invalid) + entry->invalid = INVCRL_GENERAL; } else if (*line == 'u') entry->user_trust_req = 1; @@ -2338,7 +2345,7 @@ crl_cache_insert (ctrl_t ctrl, const char *url, ksba_reader_t reader) nextupdate); if (!err2) err2 = gpg_error (GPG_ERR_CRL_TOO_OLD); - invalidate_crl |= 1; + invalidate_crl |= INVCRL_TOO_OLD; } } @@ -2353,7 +2360,7 @@ crl_cache_insert (ctrl_t ctrl, const char *url, ksba_reader_t reader) log_error (_("unknown critical CRL extension %s\n"), oid); if (!err2) err2 = gpg_error (GPG_ERR_INV_CRL); - invalidate_crl |= 2; + invalidate_crl |= INVCRL_UNKNOWN_EXTN; } if (gpg_err_code (err) == GPG_ERR_EOF || gpg_err_code (err) == GPG_ERR_NO_DATA ) @@ -2492,6 +2499,7 @@ list_one_crl_entry (crl_cache_t cache, crl_cache_entry_t e, estream_t fp) int rc; int warn = 0; const unsigned char *s; + unsigned int invalid; es_fputs ("--------------------------------------------------------\n", fp ); es_fprintf (fp, _("Begin CRL dump (retrieved via %s)\n"), e->url ); @@ -2516,13 +2524,20 @@ list_one_crl_entry (crl_cache_t cache, crl_cache_entry_t e, estream_t fp) !e->user_trust_req? "[system]" : e->check_trust_anchor? e->check_trust_anchor:"[missing]"); - if ((e->invalid & 1)) - es_fprintf (fp, _(" ERROR: The CRL will not be used " - "because it was still too old after an update!\n")); - if ((e->invalid & 2)) - es_fprintf (fp, _(" ERROR: The CRL will not be used " + invalid = e->invalid; + if ((invalid & INVCRL_TOO_OLD)) + { + invalid &= ~INVCRL_TOO_OLD; + es_fprintf (fp, _(" ERROR: The CRL will not be used " + "because it was still too old after an update!\n")); + } + if ((invalid & INVCRL_UNKNOWN_EXTN)) + { + invalid &= ~INVCRL_UNKNOWN_EXTN; + es_fprintf (fp, _(" ERROR: The CRL will not be used " "due to an unknown critical extension!\n")); - if ((e->invalid & ~3)) + } + if (invalid) /* INVCRL_GENERAL or some other bits are set. */ es_fprintf (fp, _(" ERROR: The CRL will not be used\n")); cdb = lock_db_file (cache, e); From d2d1db88608349bbe00da3adabddc167f6852f9e Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 8 Mar 2023 15:10:52 +0100 Subject: [PATCH 29/60] gpg,gpgsm: New option --log-time * g10/gpg.c (oLogTime): New. (opts): Add "log-time". (opt_log_time): New var. (main): Implement. * sm/gpgsm.c (oLogTime): New. (opts): Add "log-time". (opt_log_time): New var. (main): Implement. --- doc/gpg.texi | 4 ++++ doc/gpgsm.texi | 4 ++++ g10/gpg.c | 9 +++++++++ sm/gpgsm.c | 10 ++++++++++ 4 files changed, 27 insertions(+) diff --git a/doc/gpg.texi b/doc/gpg.texi index 01b91abec..a6ab4d57d 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -3199,6 +3199,10 @@ Write log output to file descriptor @var{n} and not to STDERR. Same as @option{--logger-fd}, except the logger data is written to file @var{file}. Use @file{socket://} to log to s socket. +@item --log-time +@opindex log-time +Prefix all log output with a timestamp even if no log file is used. + @item --attribute-fd @var{n} @opindex attribute-fd Write attribute subpackets to the file descriptor @var{n}. This is most diff --git a/doc/gpgsm.texi b/doc/gpgsm.texi index 42090a93f..a117009bd 100644 --- a/doc/gpgsm.texi +++ b/doc/gpgsm.texi @@ -408,6 +408,10 @@ Do not print a warning when the so called "secure memory" cannot be used. When running in server mode, append all logging output to @var{file}. Use @file{socket://} to log to socket. +@item --log-time +@opindex log-time +Prefix all log output with a timestamp even if no log file is used. + @end table diff --git a/g10/gpg.c b/g10/gpg.c index 9ec956ac3..632fbb90f 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -337,6 +337,7 @@ enum cmd_and_opt_values oEncryptToDefaultKey, oLoggerFD, oLoggerFile, + oLogTime, oUtf8Strings, oNoUtf8Strings, oDisableCipherAlgo, @@ -600,6 +601,7 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_s (oLoggerFile, "log-file", N_("|FILE|write server mode logs to FILE")), ARGPARSE_s_s (oLoggerFile, "logger-file", "@"), /* 1.4 compatibility. */ + ARGPARSE_s_n (oLogTime, "log-time", "@"), ARGPARSE_s_n (oQuickRandom, "debug-quick-random", "@"), @@ -1041,6 +1043,7 @@ static int utf8_strings = static int maybe_setuid = 1; static unsigned int opt_set_iobuf_size; static unsigned int opt_set_iobuf_size_used; +static int opt_log_time; /* Collection of options used only in this module. */ static struct { @@ -2864,6 +2867,9 @@ main (int argc, char **argv) case oLoggerFile: logfile = pargs.r.ret_str; break; + case oLogTime: + opt_log_time = 1; + break; case oWithFingerprint: opt.with_fingerprint = 1; @@ -3829,6 +3835,9 @@ main (int argc, char **argv) | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID )); } + else if (opt_log_time) + log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY + |GPGRT_LOG_WITH_TIME)); if (opt.verbose > 2) log_info ("using character set '%s'\n", get_native_charset ()); diff --git a/sm/gpgsm.c b/sm/gpgsm.c index f8b3856c2..8ca398360 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -114,6 +114,7 @@ enum cmd_and_opt_values { oNoLogFile, oAuditLog, oHtmlAuditLog, + oLogTime, oEnableSpecialFilenames, @@ -288,6 +289,7 @@ static gpgrt_opt_t opts[] = { N_("|FILE|write server mode logs to FILE")), ARGPARSE_s_n (oNoLogFile, "no-log-file", "@"), ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), + ARGPARSE_s_n (oLogTime, "log-time", "@"), ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"), @@ -499,6 +501,9 @@ static int maybe_setuid = 1; static const char *debug_level; static unsigned int debug_value; +/* Helper for --log-time; */ +static int opt_log_time; + /* Default value for include-certs. We need an extra macro for gpgconf-list because the variable will be changed by the command line option. @@ -1247,6 +1252,7 @@ main ( int argc, char **argv) case oLogFile: logfile = pargs.r.ret_str; break; case oNoLogFile: logfile = NULL; break; + case oLogTime: opt_log_time = 1; break; case oAuditLog: auditlog = pargs.r.ret_str; break; case oHtmlAuditLog: htmlauditlog = pargs.r.ret_str; break; @@ -1579,6 +1585,10 @@ main ( int argc, char **argv) log_set_file (logfile); log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); } + else if (opt_log_time) + log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY + |GPGRT_LOG_WITH_TIME)); + if (gnupg_faked_time_p ()) { From 65288fc52f0c60e99f32d6d1981ade08a9ec860b Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 8 Mar 2023 16:09:56 +0100 Subject: [PATCH 30/60] keyboxd: Allow import of v0 certificates. * kbx/backend-support.c (be_is_x509_blob): Loose detection. -- Here is a sample v0 cert: -----BEGIN CERTIFICATE----- MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQsw CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0Ns YXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH MjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y aXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazAe Fw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJVUzEX MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGlj IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMx KGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s eTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazCBnzANBgkqhkiG9w0B AQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjxnNuX6Zr8wgQGE75fUsjM HiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRCwiNPStjw DqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cC AwEAATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9ji nb3/7aHmZuovCfTK1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAX rXfMSTWqz9iP0b63GJZHc2pUIjRkLbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnIn jBJ7xUS0rg== -----END CERTIFICATE----- --- kbx/backend-support.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kbx/backend-support.c b/kbx/backend-support.c index 7ab63985c..4d3738064 100644 --- a/kbx/backend-support.c +++ b/kbx/backend-support.c @@ -207,6 +207,7 @@ be_is_x509_blob (const unsigned char *blob, size_t bloblen) * SEQUENCE SEQUENCE [0] INTEGER INTEGER * (tbs) (version) (s/n) * + * Note that v0 certificates don't have an explict version number. */ p = blob; @@ -226,7 +227,11 @@ be_is_x509_blob (const unsigned char *blob, size_t bloblen) if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen)) return 0; /* Not a proper BER object. */ if (!(class == CLASS_CONTEXT && tag == 0 && cons)) - return 0; /* No context tag. */ + { + if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER && !cons) + return 1; /* Might be a X.509 v0 cert with implict version. */ + return 0; /* No context tag. */ + } if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen)) return 0; /* Not a proper BER object. */ From b52a0e244ae18aec4b9c93f90432a551fac95a40 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 9 Mar 2023 18:28:39 +0100 Subject: [PATCH 31/60] dirmngr: Distinguish between "no crl" and "crl not trusted". * dirmngr/crlcache.h (CRL_CACHE_NOTTRUSTED): New. * dirmngr/crlcache.c (cache_isvalid): Set this status. (crl_cache_cert_isvalid): Map it to GPG_ERR_NOT_TRUSTED. (crl_cache_reload_crl): Move diagnostic to ... * dirmngr/crlfetch.c (crl_fetch): here. * dirmngr/server.c (cmd_isvalid): Map it to GPG_ERR_NOT_TRUSTED. * dirmngr/validate.c (check_revocations): Handle new status. Improve diagnostics. * common/status.c (get_inv_recpsgnr_code): Map INV_CRL_OBJ. * common/audit.c (proc_type_verify): Ditto. -- This avoids repeated loading of CRLs in case of untrusted root certificates. --- common/audit.c | 1 + common/status.c | 3 ++- dirmngr/crlcache.c | 13 +++++++------ dirmngr/crlcache.h | 1 + dirmngr/crlfetch.c | 3 +++ dirmngr/server.c | 7 +++++-- dirmngr/validate.c | 16 +++++++++++++--- sm/call-dirmngr.c | 1 + 8 files changed, 33 insertions(+), 12 deletions(-) diff --git a/common/audit.c b/common/audit.c index 803523c94..ae0d45216 100644 --- a/common/audit.c +++ b/common/audit.c @@ -1109,6 +1109,7 @@ proc_type_verify (audit_ctx_t ctx) case GPG_ERR_CERT_REVOKED: ok = "bad"; break; case GPG_ERR_NOT_ENABLED: ok = "disabled"; break; case GPG_ERR_NO_CRL_KNOWN: + case GPG_ERR_INV_CRL_OBJ: ok = _("no CRL found for certificate"); break; case GPG_ERR_CRL_TOO_OLD: diff --git a/common/status.c b/common/status.c index b752c12c6..b7dc1de39 100644 --- a/common/status.c +++ b/common/status.c @@ -158,7 +158,8 @@ get_inv_recpsgnr_code (gpg_error_t err) case GPG_ERR_WRONG_KEY_USAGE: errstr = "3"; break; case GPG_ERR_CERT_REVOKED: errstr = "4"; break; case GPG_ERR_CERT_EXPIRED: errstr = "5"; break; - case GPG_ERR_NO_CRL_KNOWN: errstr = "6"; break; + case GPG_ERR_NO_CRL_KNOWN: + case GPG_ERR_INV_CRL_OBJ: errstr = "6"; break; case GPG_ERR_CRL_TOO_OLD: errstr = "7"; break; case GPG_ERR_NO_POLICY_MATCH: errstr = "8"; break; diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c index 5d793494e..9f0b910f3 100644 --- a/dirmngr/crlcache.c +++ b/dirmngr/crlcache.c @@ -164,7 +164,7 @@ struct crl_cache_entry_s unsigned int cdb_use_count; /* Current use count. */ unsigned int cdb_lru_count; /* Used for LRU purposes. */ int dbfile_checked; /* Set to true if the dbfile_hash value has - been checked one. */ + been checked once. */ }; @@ -1402,7 +1402,7 @@ cache_isvalid (ctrl_t ctrl, const char *issuer_hash, { if (opt.verbose) log_info ("no system trust and client does not trust either\n"); - retval = CRL_CACHE_CANTUSE; + retval = CRL_CACHE_NOTTRUSTED; } else { @@ -1522,8 +1522,11 @@ crl_cache_cert_isvalid (ctrl_t ctrl, ksba_cert_t cert, case CRL_CACHE_DONTKNOW: err = gpg_error (GPG_ERR_NO_CRL_KNOWN); break; + case CRL_CACHE_NOTTRUSTED: + err = gpg_error (GPG_ERR_NOT_TRUSTED); + break; case CRL_CACHE_CANTUSE: - err = gpg_error (GPG_ERR_NO_CRL_KNOWN); + err = gpg_error (GPG_ERR_INV_CRL_OBJ); break; default: log_fatal ("cache_isvalid returned invalid status code %d\n", result); @@ -2104,7 +2107,7 @@ crl_parse_insert (ctrl_t ctrl, ksba_crl_t crl, } } while (stopreason != KSBA_SR_READY); - assert (!err); + log_assert (!err); failure: @@ -2729,8 +2732,6 @@ crl_cache_reload_crl (ctrl_t ctrl, ksba_cert_t cert) any_dist_point = 1; - if (opt.verbose) - log_info ("fetching CRL from '%s'\n", distpoint_uri); crl_close_reader (reader); err = crl_fetch (ctrl, distpoint_uri, &reader); if (err) diff --git a/dirmngr/crlcache.h b/dirmngr/crlcache.h index 0e60def8f..7db8f01cc 100644 --- a/dirmngr/crlcache.h +++ b/dirmngr/crlcache.h @@ -27,6 +27,7 @@ typedef enum CRL_CACHE_VALID = 0, CRL_CACHE_INVALID, CRL_CACHE_DONTKNOW, + CRL_CACHE_NOTTRUSTED, CRL_CACHE_CANTUSE } crl_cache_result_t; diff --git a/dirmngr/crlfetch.c b/dirmngr/crlfetch.c index a591a2b5a..5b6b648e2 100644 --- a/dirmngr/crlfetch.c +++ b/dirmngr/crlfetch.c @@ -175,6 +175,9 @@ crl_fetch (ctrl_t ctrl, const char *url, ksba_reader_t *reader) if (!url) return gpg_error (GPG_ERR_INV_ARG); + if (opt.verbose) + log_info ("fetching CRL from '%s'\n", url); + err = http_parse_uri (&uri, url, 0); http_release_parsed_uri (uri); if (!err) /* Yes, our HTTP code groks that. */ diff --git a/dirmngr/server.c b/dirmngr/server.c index fba2233d4..da7e707f9 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -1360,8 +1360,11 @@ cmd_isvalid (assuan_context_t ctx, char *line) goto again; } break; + case CRL_CACHE_NOTTRUSTED: + err = gpg_error (GPG_ERR_NOT_TRUSTED); + break; case CRL_CACHE_CANTUSE: - err = gpg_error (GPG_ERR_NO_CRL_KNOWN); + err = gpg_error (GPG_ERR_INV_CRL_OBJ); break; default: log_fatal ("crl_cache_isvalid returned invalid code\n"); @@ -1469,7 +1472,7 @@ cmd_checkcrl (assuan_context_t ctx, char *line) goto leave; } - assert (cert); + log_assert (cert); err = crl_cache_cert_isvalid (ctrl, cert, ctrl->force_crl_refresh); if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN) diff --git a/dirmngr/validate.c b/dirmngr/validate.c index 399cca3a4..02db3c270 100644 --- a/dirmngr/validate.c +++ b/dirmngr/validate.c @@ -255,6 +255,7 @@ check_revocations (ctrl_t ctrl, chain_item_t chain) int any_revoked = 0; int any_no_crl = 0; int any_crl_too_old = 0; + int any_not_trusted = 0; chain_item_t ci; log_assert (ctrl->check_revocations_nest_level >= 0); @@ -266,7 +267,8 @@ check_revocations (ctrl_t ctrl, chain_item_t chain) return gpg_error(GPG_ERR_BAD_CERT_CHAIN); } ctrl->check_revocations_nest_level++; - + if (opt.verbose) + log_info ("[%d] start checking CRLs\n", ctrl->check_revocations_nest_level); for (ci=chain; ci; ci = ci->next) { @@ -293,17 +295,19 @@ check_revocations (ctrl_t ctrl, chain_item_t chain) if (!err) err = crl_cache_cert_isvalid (ctrl, ci->cert, 0); } + if (opt.verbose) + log_info ("[%d] result of checking this CRL: %s\n", + ctrl->check_revocations_nest_level, gpg_strerror (err)); switch (gpg_err_code (err)) { case 0: err = 0; break; case GPG_ERR_CERT_REVOKED: any_revoked = 1; err = 0; break; case GPG_ERR_NO_CRL_KNOWN: any_no_crl = 1; err = 0; break; + case GPG_ERR_NOT_TRUSTED: any_not_trusted = 1; err = 0; break; case GPG_ERR_CRL_TOO_OLD: any_crl_too_old = 1; err = 0; break; default: break; } } - ctrl->check_revocations_nest_level--; - if (err) ; @@ -311,10 +315,16 @@ check_revocations (ctrl_t ctrl, chain_item_t chain) err = gpg_error (GPG_ERR_CERT_REVOKED); else if (any_no_crl) err = gpg_error (GPG_ERR_NO_CRL_KNOWN); + else if (any_not_trusted) + err = gpg_error (GPG_ERR_NOT_TRUSTED); else if (any_crl_too_old) err = gpg_error (GPG_ERR_CRL_TOO_OLD); else err = 0; + if (opt.verbose) + log_info ("[%d] result of checking all CRLs: %s\n", + ctrl->check_revocations_nest_level, gpg_strerror (err)); + ctrl->check_revocations_nest_level--; return err; } diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c index 8e2761b1e..86beeedc1 100644 --- a/sm/call-dirmngr.c +++ b/sm/call-dirmngr.c @@ -521,6 +521,7 @@ isvalid_status_cb (void *opaque, const char *line) GPG_ERR_CERTIFICATE_REVOKED GPG_ERR_NO_CRL_KNOWN + GPG_ERR_INV_CRL_OBJ GPG_ERR_CRL_TOO_OLD Values for USE_OCSP: From be77a7ab8a8bdbde6f40a233a1a60f7924a21d4a Mon Sep 17 00:00:00 2001 From: Ben Kibbey Date: Sun, 5 Mar 2023 17:14:07 -0800 Subject: [PATCH 32/60] agent: Try to SETREPEATOK if the pinentry supports it. * agent/call-pinentry.c (agent_get_passphrase): Do SETREPEATOK. (agent_askpin): Ditto. Signed-off-by: Ben Kibbey --- agent/call-pinentry.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index c6c52be74..656d5f623 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -1543,6 +1543,17 @@ agent_askpin (ctrl_t ctrl, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) pininfo->with_repeat = 0; /* Pinentry does not support it. */ + + if (pininfo->with_repeat) + { + snprintf (line, DIM(line), "SETREPEATOK %s", + L_("Passphrases match.")); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + rc = 0; /* Pinentry does not support it. */ + } + } pininfo->repeat_okay = 0; pininfo->status = 0; @@ -1802,6 +1813,16 @@ agent_get_passphrase (ctrl_t ctrl, if (rc) pininfo->with_repeat = 0; /* Pinentry does not support it. */ + if (pininfo->with_repeat) + { + snprintf (line, DIM(line), "SETREPEATOK %s", + L_("Passphrases match.")); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + rc = 0; /* Pinentry does not support it. */ + } + (void)setup_genpin (ctrl); rc = setup_enforced_constraints (ctrl); From 56ca164684b69bcb20eb98a1adc70531c8991576 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 10 Mar 2023 10:52:43 +0100 Subject: [PATCH 33/60] dirmngr: Add command "GETINFO stats". * dirmngr/server.c (cmd_getinfo): New sub-command "stats". (dirmngr_status_helpf): Allow for a CTRL of NULL. * dirmngr/certcache.c (cert_cache_print_stats): Add arg ctrl and use dirmngr_status_helpf. Adjust all callers. * dirmngr/domaininfo.c (domaininfo_print_stats): Ditto. * sm/certchain.c (ask_marktrusted): Flush stdout before printing the fingerprint. --- dirmngr/certcache.c | 27 +++++++++++++++------------ dirmngr/certcache.h | 2 +- dirmngr/dirmngr.c | 5 +++-- dirmngr/dirmngr.h | 2 +- dirmngr/domaininfo.c | 13 +++++++------ dirmngr/server.c | 36 ++++++++++++++++++++++++++---------- dirmngr/workqueue.c | 6 +++--- sm/certchain.c | 3 +++ 8 files changed, 59 insertions(+), 35 deletions(-) diff --git a/dirmngr/certcache.c b/dirmngr/certcache.c index 1e73d6f85..6b194f31c 100644 --- a/dirmngr/certcache.c +++ b/dirmngr/certcache.c @@ -768,7 +768,7 @@ cert_cache_init (strlist_t hkp_cacerts) initialization_done = 1; release_cache_lock (); - cert_cache_print_stats (); + cert_cache_print_stats (NULL); } /* Deinitialize the certificate cache. With FULL set to true even the @@ -811,7 +811,7 @@ cert_cache_deinit (int full) /* Print some statistics to the log file. */ void -cert_cache_print_stats (void) +cert_cache_print_stats (ctrl_t ctrl) { cert_item_t ci; int idx; @@ -848,16 +848,19 @@ cert_cache_print_stats (void) release_cache_lock (); - log_info (_("permanently loaded certificates: %u\n"), - n_permanent); - log_info (_(" runtime cached certificates: %u\n"), - n_nonperm); - log_info (_(" trusted certificates: %u (%u,%u,%u,%u)\n"), - n_trusted, - n_trustclass_system, - n_trustclass_config, - n_trustclass_hkp, - n_trustclass_hkpspool); + dirmngr_status_helpf (ctrl, + _("permanently loaded certificates: %u\n"), + n_permanent); + dirmngr_status_helpf (ctrl, + _(" runtime cached certificates: %u\n"), + n_nonperm); + dirmngr_status_helpf (ctrl, + _(" trusted certificates: %u (%u,%u,%u,%u)\n"), + n_trusted, + n_trustclass_system, + n_trustclass_config, + n_trustclass_hkp, + n_trustclass_hkpspool); } diff --git a/dirmngr/certcache.h b/dirmngr/certcache.h index 8d645836d..3a773636f 100644 --- a/dirmngr/certcache.h +++ b/dirmngr/certcache.h @@ -37,7 +37,7 @@ void cert_cache_init (strlist_t hkp_cacerts); void cert_cache_deinit (int full); /* Print some statistics to the log file. */ -void cert_cache_print_stats (void); +void cert_cache_print_stats (ctrl_t ctrl); /* Return true if any cert of a class in MASK is permanently loaded. */ int cert_cache_any_in_class (unsigned int mask); diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 650770dab..66b7878e5 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -2031,8 +2031,9 @@ handle_signal (int signo) break; case SIGUSR1: - cert_cache_print_stats (); - domaininfo_print_stats (); + /* See also cmd_getinfo:"stats". */ + cert_cache_print_stats (NULL); + domaininfo_print_stats (NULL); break; case SIGUSR2: diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index f65bcd119..bd4660422 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -266,7 +266,7 @@ gpg_error_t dirmngr_load_swdb (ctrl_t ctrl, int force); /*-- domaininfo.c --*/ -void domaininfo_print_stats (void); +void domaininfo_print_stats (ctrl_t ctrl); int domaininfo_is_wkd_not_supported (const char *domain); void domaininfo_set_no_name (const char *domain); void domaininfo_set_wkd_supported (const char *domain); diff --git a/dirmngr/domaininfo.c b/dirmngr/domaininfo.c index b41aef366..b6043be53 100644 --- a/dirmngr/domaininfo.c +++ b/dirmngr/domaininfo.c @@ -81,7 +81,7 @@ hash_domain (const char *domain) void -domaininfo_print_stats (void) +domaininfo_print_stats (ctrl_t ctrl) { int bidx; domaininfo_t di; @@ -112,11 +112,12 @@ domaininfo_print_stats (void) if (minlen == -1 || len < minlen) minlen = len; } - log_info ("domaininfo: items=%d chainlen=%d..%d nn=%d nf=%d ns=%d s=%d\n", - count, - minlen > 0? minlen : 0, - maxlen, - no_name, wkd_not_found, wkd_not_supported, wkd_supported); + dirmngr_status_helpf + (ctrl, "domaininfo: items=%d chainlen=%d..%d nn=%d nf=%d ns=%d s=%d\n", + count, + minlen > 0? minlen : 0, + maxlen, + no_name, wkd_not_found, wkd_not_supported, wkd_supported); } diff --git a/dirmngr/server.c b/dirmngr/server.c index da7e707f9..cd71592a4 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -2788,13 +2788,14 @@ static const char hlp_getinfo[] = "Multi purpose command to return certain information. \n" "Supported values of WHAT are:\n" "\n" - "version - Return the version of the program.\n" - "pid - Return the process id of the server.\n" + "version - Return the version of the program\n" + "pid - Return the process id of the server\n" "tor - Return OK if running in Tor mode\n" "dnsinfo - Return info about the DNS resolver\n" - "socket_name - Return the name of the socket.\n" - "session_id - Return the current session_id.\n" + "socket_name - Return the name of the socket\n" + "session_id - Return the current session_id\n" "workqueue - Inspect the work queue\n" + "stats - Print stats\n" "getenv NAME - Return value of envvar NAME\n"; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) @@ -2863,6 +2864,12 @@ cmd_getinfo (assuan_context_t ctx, char *line) workqueue_dump_queue (ctrl); err = 0; } + else if (!strcmp (line, "stats")) + { + cert_cache_print_stats (ctrl); + domaininfo_print_stats (ctrl); + err = 0; + } else if (!strncmp (line, "getenv", 6) && (line[6] == ' ' || line[6] == '\t' || !line[6])) { @@ -3221,7 +3228,8 @@ dirmngr_status_help (ctrl_t ctrl, const char *text) /* Print a help status line using a printf like format. The function - * splits text at LFs. */ + * splits text at LFs. With CTRL beeing NULL, the function behaves + * like log_info. */ gpg_error_t dirmngr_status_helpf (ctrl_t ctrl, const char *format, ...) { @@ -3230,12 +3238,20 @@ dirmngr_status_helpf (ctrl_t ctrl, const char *format, ...) char *buf; va_start (arg_ptr, format); - buf = es_vbsprintf (format, arg_ptr); - err = buf? 0 : gpg_error_from_syserror (); + if (ctrl) + { + buf = es_vbsprintf (format, arg_ptr); + err = buf? 0 : gpg_error_from_syserror (); + if (!err) + err = dirmngr_status_help (ctrl, buf); + es_free (buf); + } + else + { + log_logv (GPGRT_LOGLVL_INFO, format, arg_ptr); + err = 0; + } va_end (arg_ptr); - if (!err) - err = dirmngr_status_help (ctrl, buf); - es_free (buf); return err; } diff --git a/dirmngr/workqueue.c b/dirmngr/workqueue.c index 2974f5d08..dcac48024 100644 --- a/dirmngr/workqueue.c +++ b/dirmngr/workqueue.c @@ -59,7 +59,7 @@ workqueue_dump_queue (ctrl_t ctrl) wqitem_t item; unsigned int count; - /* Temporarily detach the entiere workqueue so that other threads don't + /* Temporarily detach the entire workqueue so that other threads don't * get into our way. */ saved_workqueue = workqueue; workqueue = NULL; @@ -74,8 +74,8 @@ workqueue_dump_queue (ctrl_t ctrl) item->func? item->func (NULL, NULL): "nop", item->args, strlen (item->args) > 100? "[...]":""); - /* Restore then workqueue. Actually we append the saved queue do a - * possibly updated workqueue. */ + /* Restore the workqueue. Actually we append the saved queue to + * handle a possibly updated workqueue. */ if (!(item=workqueue)) workqueue = saved_workqueue; else diff --git a/sm/certchain.c b/sm/certchain.c index cbb6e1127..7b782190b 100644 --- a/sm/certchain.c +++ b/sm/certchain.c @@ -122,6 +122,7 @@ do_list (int is_error, int listmode, estream_t fp, const char *format, ...) } else { + es_fflush (es_stdout); log_logv (is_error? GPGRT_LOGLVL_ERROR: GPGRT_LOGLVL_INFO, format, arg_ptr); log_printf ("\n"); @@ -1480,6 +1481,7 @@ ask_marktrusted (ctrl_t ctrl, ksba_cert_t cert, int listmode) int success = 0; fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1); + es_fflush (es_stdout); log_info (_("fingerprint=%s\n"), fpr? fpr : "?"); xfree (fpr); @@ -2277,6 +2279,7 @@ gpgsm_basic_cert_check (ctrl_t ctrl, ksba_cert_t cert) { if (!opt.quiet) { + es_fflush (es_stdout); log_info ("issuer certificate (#/"); gpgsm_dump_string (issuer); log_printf (") not found\n"); From 6d792ae2eb46b3c411d36a87f0d08fbfc1b65cc9 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 13 Mar 2023 08:49:49 +0100 Subject: [PATCH 34/60] agent: Make --disable-extended-key-format a dummy option. * agent/agent.h (opt): Remove enable_extended_key_format. * agent/gpg-agent.c (enum cmd_and_opt_values): Turn oDisableExtendedKeyFormat and oEnableExtendedKeyFormat into dummy options. * agent/protect.c (do_encryption): Remove arg use_ocb and corresponding code. (agent_protect): Ditto. Change all callers. * agent/findkey.c (agent_write_private_key): Simplify due to the removal of disable-extended-key-format. (write_extended_private_key): Fold into agent_write_private_key. -- This change is related to GnuPG-bug-id: 6386 but should have no visible effect except for the removal of option --disable-extended-key-format. --- agent/agent.h | 9 +- agent/command-ssh.c | 2 +- agent/command.c | 8 +- agent/cvt-openpgp.c | 2 +- agent/findkey.c | 245 +++++++++++++++++++------------------------ agent/genkey.c | 2 +- agent/gpg-agent.c | 16 +-- agent/protect-tool.c | 6 +- agent/protect.c | 136 ++++++------------------ agent/t-protect.c | 2 +- doc/gpg-agent.texi | 14 +-- 11 files changed, 157 insertions(+), 285 deletions(-) diff --git a/agent/agent.h b/agent/agent.h index 303f92e50..4e7452eee 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -141,13 +141,6 @@ struct passphrase change. */ int enable_passphrase_history; - /* If set the extended key format is used for new keys. Note that - * this may have the value 2 in which case - * --disable-extended-key-format won't have any effect and thus - * effectivley locking it. This is required to support existing - * profiles which lock the use of --enable-extended-key-format. */ - int enable_extended_key_format; - int running_detached; /* We are running detached from the tty. */ /* If this global option is true, the passphrase cache is ignored @@ -566,7 +559,7 @@ unsigned char get_standard_s2k_count_rfc4880 (void); unsigned long get_standard_s2k_time (void); int agent_protect (const unsigned char *plainkey, const char *passphrase, unsigned char **result, size_t *resultlen, - unsigned long s2k_count, int use_ocb); + unsigned long s2k_count); gpg_error_t agent_unprotect (ctrl_t ctrl, const unsigned char *protectedkey, const char *passphrase, gnupg_isotime_t protected_at, diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 7621e7c2f..b41177be6 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -3142,7 +3142,7 @@ ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase, buffer_new, buffer_new_n); if (*passphrase) - err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0, -1); + err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0); else { /* The key derivation function does not support zero length diff --git a/agent/command.c b/agent/command.c index c113caba7..2e996d096 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1218,12 +1218,6 @@ cmd_keyattr (assuan_context_t ctx, char *line) if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); - if (!opt.enable_extended_key_format) - { - err = gpg_error (GPG_ERR_NOT_SUPPORTED); - goto leave; - } - opt_delete = has_option (line, "--delete"); line = skip_options (line); @@ -2910,7 +2904,7 @@ cmd_import_key (assuan_context_t ctx, char *line) if (passphrase) { err = agent_protect (key, passphrase, &finalkey, &finalkeylen, - ctrl->s2k_count, -1); + ctrl->s2k_count); if (!err) err = agent_write_private_key (grip, finalkey, finalkeylen, force, NULL, NULL, opt_timestamp); diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c index d170fdedc..9bb815ff8 100644 --- a/agent/cvt-openpgp.c +++ b/agent/cvt-openpgp.c @@ -1146,7 +1146,7 @@ convert_from_openpgp_native (ctrl_t ctrl, if (!agent_protect (*r_key, passphrase, &protectedkey, &protectedkeylen, - ctrl->s2k_count, -1)) + ctrl->s2k_count)) agent_write_private_key (grip, protectedkey, protectedkeylen, 1, NULL, NULL, 0); xfree (protectedkey); diff --git a/agent/findkey.c b/agent/findkey.c index 060cb786d..098d5224f 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -2,6 +2,7 @@ * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, * 2010, 2011 Free Software Foundation, Inc. * Copyright (C) 2014, 2019 Werner Koch + * Copyright (C) 2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -79,19 +80,114 @@ linefeed_to_percent0A (const char *string) } -/* Note: Ownership of FNAME and FP are moved to this function. */ -static gpg_error_t -write_extended_private_key (char *fname, estream_t fp, int update, int newkey, - const void *buf, size_t len, - const char *serialno, const char *keyref, - time_t timestamp) +/* Write the S-expression formatted key (BUFFER,LENGTH) to our key + * storage. With FORCE passed as true an existing key with the given + * GRIP will get overwritten. If SERIALNO and KEYREF are given a + * Token line is added to the key if the extended format is used. If + * TIMESTAMP is not zero and the key doies not yet exists it will be + * recorded as creation date. */ +int +agent_write_private_key (const unsigned char *grip, + const void *buffer, size_t length, int force, + const char *serialno, const char *keyref, + time_t timestamp) { gpg_error_t err; + char *fname; + estream_t fp; + char hexgrip[40+4+1]; + int update, newkey; nvc_t pk = NULL; gcry_sexp_t key = NULL; int remove = 0; char *token = NULL; + bin2hex (grip, 20, hexgrip); + strcpy (hexgrip+40, ".key"); + + fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, + hexgrip, NULL); + + /* FIXME: Write to a temp file first so that write failures during + key updates won't lead to a key loss. */ + + if (!force && !gnupg_access (fname, F_OK)) + { + log_error ("secret key file '%s' already exists\n", fname); + xfree (fname); + return gpg_error (GPG_ERR_EEXIST); + } + + fp = es_fopen (fname, force? "rb+,mode=-rw" : "wbx,mode=-rw"); + if (!fp) + { + gpg_error_t tmperr = gpg_error_from_syserror (); + + if (force && gpg_err_code (tmperr) == GPG_ERR_ENOENT) + { + fp = es_fopen (fname, "wbx,mode=-rw"); + if (!fp) + tmperr = gpg_error_from_syserror (); + } + if (!fp) + { + log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr)); + xfree (fname); + return tmperr; + } + update = 0; + newkey = 1; + } + else if (force) + { + gpg_error_t rc; + char first; + + /* See if an existing key is in extended format. */ + if (es_fread (&first, 1, 1, fp) != 1) + { + rc = gpg_error_from_syserror (); + log_error ("error reading first byte from '%s': %s\n", + fname, strerror (errno)); + xfree (fname); + es_fclose (fp); + return rc; + } + + rc = es_fseek (fp, 0, SEEK_SET); + if (rc) + { + log_error ("error seeking in '%s': %s\n", fname, strerror (errno)); + xfree (fname); + es_fclose (fp); + return rc; + } + + if (first == '(') + { + /* Key is still in the old format - force it into extended + * format. We do not request an update here because an + * existing key is not yet in extended key format and no + * extended infos are yet available. */ + update = 0; + newkey = 0; + } + else + { + /* Key is already in the extended format. */ + update = 1; + newkey = 0; + } + } + else + { + /* The key file did not exist: we assume this is a new key and + * write the Created: entry. */ + update = 0; + newkey = 1; + } + + if (update) { int line; @@ -115,10 +211,11 @@ write_extended_private_key (char *fname, estream_t fp, int update, int newkey, } es_clearerr (fp); - err = gcry_sexp_sscan (&key, NULL, buf, len); + /* Turn (BUFFER,LENGTH) into a gcrypt s-expression and set it into + * our name value container. */ + err = gcry_sexp_sscan (&key, NULL, buffer, length); if (err) goto leave; - err = nvc_set_private_key (pk, key); if (err) goto leave; @@ -153,7 +250,7 @@ write_extended_private_key (char *fname, estream_t fp, int update, int newkey, } } - /* If a timestamp has been supplied and the key is new write a + /* If a timestamp has been supplied and the key is new, write a * creation timestamp. (We douple check that there is no Created * item yet.)*/ if (timestamp && newkey && !nvc_lookup (pk, "Created:")) @@ -166,7 +263,7 @@ write_extended_private_key (char *fname, estream_t fp, int update, int newkey, goto leave; } - + /* Back to start and write. */ err = es_fseek (fp, 0, SEEK_SET); if (err) goto leave; @@ -212,133 +309,6 @@ write_extended_private_key (char *fname, estream_t fp, int update, int newkey, return err; } -/* Write an S-expression formatted key to our key storage. With FORCE - * passed as true an existing key with the given GRIP will get - * overwritten. If SERIALNO and KEYREF are given a Token line is - * added to the key if the extended format is used. If TIMESTAMP is - * not zero and the key doies not yet exists it will be recorded as - * creation date. */ -int -agent_write_private_key (const unsigned char *grip, - const void *buffer, size_t length, int force, - const char *serialno, const char *keyref, - time_t timestamp) -{ - char *fname; - estream_t fp; - char hexgrip[40+4+1]; - - bin2hex (grip, 20, hexgrip); - strcpy (hexgrip+40, ".key"); - - fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, - hexgrip, NULL); - - /* FIXME: Write to a temp file first so that write failures during - key updates won't lead to a key loss. */ - - if (!force && !gnupg_access (fname, F_OK)) - { - log_error ("secret key file '%s' already exists\n", fname); - xfree (fname); - return gpg_error (GPG_ERR_EEXIST); - } - - fp = es_fopen (fname, force? "rb+,mode=-rw" : "wbx,mode=-rw"); - if (!fp) - { - gpg_error_t tmperr = gpg_error_from_syserror (); - - if (force && gpg_err_code (tmperr) == GPG_ERR_ENOENT) - { - fp = es_fopen (fname, "wbx,mode=-rw"); - if (!fp) - tmperr = gpg_error_from_syserror (); - } - if (!fp) - { - log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr)); - xfree (fname); - return tmperr; - } - } - else if (force) - { - gpg_error_t rc; - char first; - - /* See if an existing key is in extended format. */ - if (es_fread (&first, 1, 1, fp) != 1) - { - rc = gpg_error_from_syserror (); - log_error ("error reading first byte from '%s': %s\n", - fname, strerror (errno)); - xfree (fname); - es_fclose (fp); - return rc; - } - - rc = es_fseek (fp, 0, SEEK_SET); - if (rc) - { - log_error ("error seeking in '%s': %s\n", fname, strerror (errno)); - xfree (fname); - es_fclose (fp); - return rc; - } - - if (first != '(') - { - /* Key is already in the extended format. */ - return write_extended_private_key (fname, fp, 1, 0, buffer, length, - serialno, keyref, timestamp); - } - if (first == '(' && opt.enable_extended_key_format) - { - /* Key is in the old format - but we want the extended format. */ - return write_extended_private_key (fname, fp, 0, 0, buffer, length, - serialno, keyref, timestamp); - } - } - - if (opt.enable_extended_key_format) - return write_extended_private_key (fname, fp, 0, 1, buffer, length, - serialno, keyref, timestamp); - - if (es_fwrite (buffer, length, 1, fp) != 1) - { - gpg_error_t tmperr = gpg_error_from_syserror (); - log_error ("error writing '%s': %s\n", fname, gpg_strerror (tmperr)); - es_fclose (fp); - gnupg_remove (fname); - xfree (fname); - return tmperr; - } - - /* When force is given, the file might have to be truncated. */ - if (force && ftruncate (es_fileno (fp), es_ftello (fp))) - { - gpg_error_t tmperr = gpg_error_from_syserror (); - log_error ("error truncating '%s': %s\n", fname, gpg_strerror (tmperr)); - es_fclose (fp); - gnupg_remove (fname); - xfree (fname); - return tmperr; - } - - if (es_fclose (fp)) - { - gpg_error_t tmperr = gpg_error_from_syserror (); - log_error ("error closing '%s': %s\n", fname, gpg_strerror (tmperr)); - gnupg_remove (fname); - xfree (fname); - return tmperr; - } - bump_key_eventcounter (); - xfree (fname); - return 0; -} - gpg_error_t agent_update_private_key (const unsigned char *grip, nvc_t pk) @@ -393,6 +363,7 @@ agent_update_private_key (const unsigned char *grip, nvc_t pk) return err; } + /* Callback function to try the unprotection from the passphrase query code. */ static gpg_error_t diff --git a/agent/genkey.c b/agent/genkey.c index eb6791dca..7660443ca 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -57,7 +57,7 @@ store_key (gcry_sexp_t private, const char *passphrase, int force, { unsigned char *p; - rc = agent_protect (buf, passphrase, &p, &len, s2k_count, -1); + rc = agent_protect (buf, passphrase, &p, &len, s2k_count); if (rc) { xfree (buf); diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 381999cea..1db422737 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -116,8 +116,6 @@ enum cmd_and_opt_values oCheckSymPassphrasePattern, oMaxPassphraseDays, oEnablePassphraseHistory, - oDisableExtendedKeyFormat, - oEnableExtendedKeyFormat, oStealSocket, oUseStandardSocket, oNoUseStandardSocket, @@ -238,8 +236,6 @@ static gpgrt_opt_t opts[] = { /* */ "@" #endif ), - ARGPARSE_s_n (oDisableExtendedKeyFormat, "disable-extended-key-format", "@"), - ARGPARSE_s_n (oEnableExtendedKeyFormat, "enable-extended-key-format", "@"), ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"), ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), @@ -315,7 +311,8 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"), /* Dummy options. */ - + ARGPARSE_s_n (oNoop, "disable-extended-key-format", "@"), + ARGPARSE_s_n (oNoop, "enable-extended-key-format", "@"), ARGPARSE_end () /* End of list */ }; @@ -885,7 +882,6 @@ parse_rereadable_options (gpgrt_argparse_t *pargs, int reread) opt.check_sym_passphrase_pattern = NULL; opt.max_passphrase_days = MAX_PASSPHRASE_DAYS; opt.enable_passphrase_history = 0; - opt.enable_extended_key_format = 1; opt.ignore_cache_for_signing = 0; opt.allow_mark_trusted = 1; opt.sys_trustlist_name = NULL; @@ -974,14 +970,6 @@ parse_rereadable_options (gpgrt_argparse_t *pargs, int reread) opt.enable_passphrase_history = 1; break; - case oEnableExtendedKeyFormat: - opt.enable_extended_key_format = 2; - break; - case oDisableExtendedKeyFormat: - if (opt.enable_extended_key_format != 2) - opt.enable_extended_key_format = 0; - break; - case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break; case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break; diff --git a/agent/protect-tool.c b/agent/protect-tool.c index bb17033a8..87cf36814 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -97,7 +97,6 @@ static const char *opt_passphrase; static char *opt_prompt; static int opt_status_msg; static const char *opt_agent_program; -static int opt_debug_use_ocb; static char *get_passphrase (int promptno); static void release_passphrase (char *pw); @@ -343,8 +342,7 @@ read_and_protect (const char *fname) return; pw = get_passphrase (1); - rc = agent_protect (key, pw, &result, &resultlen, 0, - opt_debug_use_ocb? 1 : -1); + rc = agent_protect (key, pw, &result, &resultlen, 0); release_passphrase (pw); xfree (key); if (rc) @@ -610,7 +608,7 @@ main (int argc, char **argv ) case oHaveCert: opt_have_cert = 1; break; case oPrompt: opt_prompt = pargs.r.ret_str; break; case oStatusMsg: opt_status_msg = 1; break; - case oDebugUseOCB: opt_debug_use_ocb = 1; break; + case oDebugUseOCB: /* dummy */; break; default: pargs.err = ARGPARSE_PRINT_ERROR; break; } diff --git a/agent/protect.c b/agent/protect.c index 1084ee208..7197cf7e6 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -379,12 +379,11 @@ do_encryption (const unsigned char *hashbegin, size_t hashlen, const char *passphrase, const char *timestamp_exp, size_t timestamp_exp_len, unsigned char **result, size_t *resultlen, - unsigned long s2k_count, int use_ocb) + unsigned long s2k_count) { gcry_cipher_hd_t hd; const char *modestr; - unsigned char hashvalue[20]; - int blklen, enclen, outlen; + int enclen, outlen; unsigned char *iv = NULL; unsigned int ivsize; /* Size of the buffer allocated for IV. */ const unsigned char *s2ksalt; /* Points into IV. */ @@ -398,44 +397,26 @@ do_encryption (const unsigned char *hashbegin, size_t hashlen, *resultlen = 0; *result = NULL; - modestr = (use_ocb? "openpgp-s2k3-ocb-aes" - /* */: "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc"); + modestr = "openpgp-s2k3-ocb-aes"; rc = gcry_cipher_open (&hd, PROT_CIPHER, - use_ocb? GCRY_CIPHER_MODE_OCB : - GCRY_CIPHER_MODE_CBC, + GCRY_CIPHER_MODE_OCB, GCRY_CIPHER_SECURE); if (rc) return rc; /* We need to work on a copy of the data because this makes it * easier to add the trailer and the padding and more important we - * have to prefix the text with 2 parenthesis. In CBC mode we - * have to allocate enough space for: - * - * (()(4:hash4:sha120:)) + padding - * - * we always append a full block of random bytes as padding but - * encrypt only what is needed for a full blocksize. In OCB mode we + * have to prefix the text with 2 parenthesis. Due to OCB mode we * have to allocate enough space for just: * * (()) */ - blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER); - if (use_ocb) - { - /* (( )) */ - outlen = 2 + protlen + 2 ; - enclen = outlen + 16 /* taglen */; - outbuf = gcry_malloc_secure (enclen); - } - else - { - /* (( )( 4:hash 4:sha1 20: )) */ - outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen; - enclen = outlen/blklen * blklen; - outbuf = gcry_malloc_secure (outlen); - } + + /* (( )) */ + outlen = 2 + protlen + 2 ; + enclen = outlen + 16 /* taglen */; + outbuf = gcry_malloc_secure (enclen); if (!outbuf) { rc = out_of_core (); @@ -445,10 +426,10 @@ do_encryption (const unsigned char *hashbegin, size_t hashlen, /* Allocate a buffer for the nonce and the salt. */ if (!rc) { - /* Allocate random bytes to be used as IV, padding and s2k salt - * or in OCB mode for a nonce and the s2k salt. The IV/nonce is - * set later because for OCB we need to set the key first. */ - ivsize = (use_ocb? 12 : (blklen*2)) + 8; + /* Allocate random bytes to be used as nonce and s2k salt. The + * nonce is set later because for OCB we need to set the key + * first. */ + ivsize = 12 + 8; iv = xtrymalloc (ivsize); if (!iv) rc = gpg_error_from_syserror (); @@ -484,40 +465,17 @@ do_encryption (const unsigned char *hashbegin, size_t hashlen, goto leave; /* Set the IV/nonce. */ - rc = gcry_cipher_setiv (hd, iv, use_ocb? 12 : blklen); + rc = gcry_cipher_setiv (hd, iv, 12); if (rc) goto leave; - if (use_ocb) - { - /* In OCB Mode we use only the public key parameters as AAD. */ - rc = gcry_cipher_authenticate (hd, hashbegin, protbegin - hashbegin); - if (!rc) - rc = gcry_cipher_authenticate (hd, timestamp_exp, timestamp_exp_len); - if (!rc) - rc = gcry_cipher_authenticate - (hd, protbegin+protlen, hashlen - (protbegin+protlen - hashbegin)); - } - else - { - /* Hash the entire expression for CBC mode. Because - * TIMESTAMP_EXP won't get protected, we can't simply hash a - * continuous buffer but need to call md_write several times. */ - gcry_md_hd_t md; - - rc = gcry_md_open (&md, GCRY_MD_SHA1, 0 ); - if (!rc) - { - gcry_md_write (md, hashbegin, protbegin - hashbegin); - gcry_md_write (md, protbegin, protlen); - gcry_md_write (md, timestamp_exp, timestamp_exp_len); - gcry_md_write (md, protbegin+protlen, - hashlen - (protbegin+protlen - hashbegin)); - memcpy (hashvalue, gcry_md_read (md, GCRY_MD_SHA1), 20); - gcry_md_close (md); - } - } - + /* In OCB Mode we use only the public key parameters as AAD. */ + rc = gcry_cipher_authenticate (hd, hashbegin, protbegin - hashbegin); + if (!rc) + rc = gcry_cipher_authenticate (hd, timestamp_exp, timestamp_exp_len); + if (!rc) + rc = gcry_cipher_authenticate + (hd, protbegin+protlen, hashlen - (protbegin+protlen - hashbegin)); /* Encrypt. */ if (!rc) @@ -527,36 +485,15 @@ do_encryption (const unsigned char *hashbegin, size_t hashlen, *p++ = '('; memcpy (p, protbegin, protlen); p += protlen; - if (use_ocb) - { - *p++ = ')'; - *p++ = ')'; - } - else - { - memcpy (p, ")(4:hash4:sha120:", 17); - p += 17; - memcpy (p, hashvalue, 20); - p += 20; - *p++ = ')'; - *p++ = ')'; - memcpy (p, iv+blklen, blklen); /* Add padding. */ - p += blklen; - } + *p++ = ')'; + *p++ = ')'; log_assert ( p - outbuf == outlen); - if (use_ocb) + gcry_cipher_final (hd); + rc = gcry_cipher_encrypt (hd, outbuf, outlen, NULL, 0); + if (!rc) { - gcry_cipher_final (hd); - rc = gcry_cipher_encrypt (hd, outbuf, outlen, NULL, 0); - if (!rc) - { - log_assert (outlen + 16 == enclen); - rc = gcry_cipher_gettag (hd, outbuf + outlen, 16); - } - } - else - { - rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0); + log_assert (outlen + 16 == enclen); + rc = gcry_cipher_gettag (hd, outbuf + outlen, 16); } } @@ -584,7 +521,7 @@ do_encryption (const unsigned char *hashbegin, size_t hashlen, (int)strlen (modestr), modestr, &saltpos, (unsigned int)strlen (countbuf), countbuf, - use_ocb? 12 : blklen, &ivpos, use_ocb? 12 : blklen, "", + 12, &ivpos, 12, "", enclen, &encpos, enclen, ""); if (!p) { @@ -598,7 +535,7 @@ do_encryption (const unsigned char *hashbegin, size_t hashlen, *resultlen = strlen (p); *result = (unsigned char*)p; memcpy (p+saltpos, s2ksalt, 8); - memcpy (p+ivpos, iv, use_ocb? 12 : blklen); + memcpy (p+ivpos, iv, 12); memcpy (p+encpos, outbuf, enclen); xfree (iv); xfree (outbuf); @@ -614,13 +551,11 @@ do_encryption (const unsigned char *hashbegin, size_t hashlen, /* Protect the key encoded in canonical format in PLAINKEY. We assume - a valid S-Exp here. With USE_UCB set to -1 the default scheme is - used (ie. either CBC or OCB), set to 0 the old CBC mode is used, - and set to 1 OCB is used. */ + * a valid S-Exp here. */ int agent_protect (const unsigned char *plainkey, const char *passphrase, unsigned char **result, size_t *resultlen, - unsigned long s2k_count, int use_ocb) + unsigned long s2k_count) { int rc; const char *parmlist; @@ -637,9 +572,6 @@ agent_protect (const unsigned char *plainkey, const char *passphrase, unsigned char *p; int have_curve = 0; - if (use_ocb == -1) - use_ocb = !!opt.enable_extended_key_format; - /* Create an S-expression with the protected-at timestamp. */ memcpy (timestamp_exp, "(12:protected-at15:", 19); gnupg_get_isotime (timestamp_exp+19); @@ -743,7 +675,7 @@ agent_protect (const unsigned char *plainkey, const char *passphrase, rc = do_encryption (hash_begin, hash_end - hash_begin + 1, prot_begin, prot_end - prot_begin + 1, passphrase, timestamp_exp, sizeof (timestamp_exp), - &protected, &protectedlen, s2k_count, use_ocb); + &protected, &protectedlen, s2k_count); if (rc) return rc; diff --git a/agent/t-protect.c b/agent/t-protect.c index 88b552585..e6edbffba 100644 --- a/agent/t-protect.c +++ b/agent/t-protect.c @@ -196,7 +196,7 @@ test_agent_protect (void) { ret = agent_protect ((const unsigned char*)specs[i].key, specs[i].passphrase, - &specs[i].result, &specs[i].resultlen, 0, -1); + &specs[i].result, &specs[i].resultlen, 0); if (gpg_err_code (ret) != specs[i].ret_expected) { printf ("agent_protect(%d) returned '%i/%s'; expected '%i/%s'\n", diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index 921522d53..1a03de010 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -615,15 +615,11 @@ remote machine. @itemx --disable-extended-key-format @opindex enable-extended-key-format @opindex disable-extended-key-format -Since version 2.3 keys are created in the extended private key format. -Changing the passphrase of a key will also convert the key to that new -format. This new key format is supported since GnuPG version 2.1.12 -and thus there should be no need to disable it. The disable option -allows to revert to the old behavior for new keys; be aware that keys -are never migrated back to the old format. However if the enable -option has been used the disable option won't have an effect. The -advantage of the extended private key format is that it is text based -and can carry additional meta data. +These options are obsolete and have no effect. The extended key format +is used for years now and has been supported since 2.1.12. Existing +keys in the old format are migrated to the new format as soon as they +are touched. + @anchor{option --enable-ssh-support} @item --enable-ssh-support From 2e065b4bd2d392a389652511264b5cbe19f90ba6 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 14 Mar 2023 16:16:40 +0100 Subject: [PATCH 35/60] scd,openpgp: Switch key attributes between RSA and ECC in writekey. * common/sexputil.c (get_rsa_pk_from_canon_sexp): Also allow private keys. (pubkey_algo_string): Ditto. * scd/app-openpgp.c (do_writekey): Switch key attributes -- The scd WRITEKEY command for OpenPGP cards missed proper support to aautomagically switch key attributes based on the new key. We had this only in GENKEY. GnuPG-bug-id: 6378 --- common/sexputil.c | 5 ++++- scd/app-openpgp.c | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/common/sexputil.c b/common/sexputil.c index b7ddea8fc..29fe508b6 100644 --- a/common/sexputil.c +++ b/common/sexputil.c @@ -536,7 +536,8 @@ get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen, return err; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; - if (!tok || toklen != 10 || memcmp ("public-key", tok, toklen)) + if (!tok || !((toklen == 10 && !memcmp ("public-key", tok, toklen)) + || (toklen == 11 && !memcmp ("private-key", tok, toklen)))) return gpg_error (GPG_ERR_BAD_PUBKEY); if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; @@ -1074,6 +1075,8 @@ pubkey_algo_string (gcry_sexp_t s_pkey, enum gcry_pk_algos *r_algoid) *r_algoid = 0; l1 = gcry_sexp_find_token (s_pkey, "public-key", 0); + if (!l1) + l1 = gcry_sexp_find_token (s_pkey, "private-key", 0); if (!l1) return xtrystrdup ("E_no_key"); { diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index f5d0b5111..d3f460106 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -4824,6 +4824,7 @@ do_writekey (app_t app, ctrl_t ctrl, const unsigned char *buf, *tok; size_t buflen, toklen; int depth; + char *algostr = NULL; (void)ctrl; @@ -4866,17 +4867,41 @@ do_writekey (app_t app, ctrl_t ctrl, goto leave; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; - if (tok && toklen == 3 && memcmp ("rsa", tok, toklen) == 0) - err = rsa_writekey (app, ctrl, pincb, pincb_arg, keyno, buf, buflen, depth); - else if (tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0) - err = ecc_writekey (app, ctrl, pincb, pincb_arg, keyno, buf, buflen, depth); + + if (tok && toklen == 3 && (!memcmp ("rsa", tok, toklen) + || !memcmp ("ecc", tok, toklen))) + { + gcry_sexp_t stmp; + if (!gcry_sexp_new (&stmp, keydata, keydatalen, 0)) + algostr = pubkey_algo_string (stmp, NULL); + else + algostr = NULL; + gcry_sexp_release (stmp); + if (app->app_local->keyattr[keyno].keyalgo && algostr + && strcmp (app->app_local->keyattr[keyno].keyalgo, algostr)) + { + log_info ("openpgp: changing key attribute from %s to %s\n", + app->app_local->keyattr[keyno].keyalgo, algostr); + err = change_keyattr_from_string (app, ctrl, pincb, pincb_arg, + keyid, algostr, NULL, 0); + if (err) + return err; + } + + if (*tok == 'r') + err = rsa_writekey (app, ctrl, pincb, pincb_arg, keyno, + buf,buflen,depth); + else + err = ecc_writekey (app, ctrl, pincb, pincb_arg, keyno, + buf, buflen, depth); + } else { err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); - goto leave; } leave: + xfree (algostr); return err; } From 5118beeec18f731fe3c0084b181eff9531181be6 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 15 Mar 2023 09:36:36 +0100 Subject: [PATCH 36/60] gpg: Delete secret key after "keytocard". * g10/card-util.c (card_store_subkey): Add arg processed_keys. * g10/keyedit.c (keyedit_menu): Delete secret key. -- This used to work using the gpg-agent: learn we called at "save" time. However, the recent change inhibited the creation of a shadow key by learn if a regular key still exists. Now we do an explicit delete key at save time. This syncs the behaviour with the description of the man page. GnuPG-bug-id: 6378 --- g10/card-util.c | 17 +++++++++++------ g10/keyedit.c | 38 ++++++++++++++++++++++++++++++++++++-- g10/main.h | 2 +- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/g10/card-util.c b/g10/card-util.c index 02de241f2..6451b31e7 100644 --- a/g10/card-util.c +++ b/g10/card-util.c @@ -1781,12 +1781,13 @@ card_generate_subkey (ctrl_t ctrl, kbnode_t pub_keyblock) } -/* Store the key at NODE into the smartcard and modify NODE to - carry the serialno stuff instead of the actual secret key - parameters. USE is the usage for that key; 0 means any - usage. */ +/* Store the key at NODE into the smartcard and modify NODE to carry + the serialno stuff instead of the actual secret key parameters. + USE is the usage for that key; 0 means any usage. If + PROCESSED_KEYS is not NULL it is a poiter to an strlist which will + be filled with the keygrips of successfully stored keys. */ int -card_store_subkey (KBNODE node, int use) +card_store_subkey (KBNODE node, int use, strlist_t *processed_keys) { struct agent_card_info_s info; int okay = 0; @@ -1875,7 +1876,11 @@ card_store_subkey (KBNODE node, int use) if (rc) log_error (_("KEYTOCARD failed: %s\n"), gpg_strerror (rc)); else - okay = 1; + { + okay = 1; + if (processed_keys) + add_to_strlist (processed_keys, hexgrip); + } xfree (hexgrip); leave: diff --git a/g10/keyedit.c b/g10/keyedit.c index 6c45fd640..d21064a21 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -1424,6 +1424,8 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, int sec_shadowing = 0; int run_subkey_warnings = 0; int have_commands = !!commands; + strlist_t delseckey_list = NULL; + int delseckey_list_warn = 0; if (opt.command_fd != -1) ; @@ -1500,6 +1502,14 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, subkey_expire_warning (keyblock); } + if (delseckey_list_warn) + { + delseckey_list_warn = 0; + tty_printf + (_("Note: the local copy of the secret key" + " will only be deleted with \"save\".\n")); + } + do { xfree (answer); @@ -1872,10 +1882,12 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, if (node) { PKT_public_key *xxpk = node->pkt->pkt.public_key; - if (card_store_subkey (node, xxpk ? xxpk->pubkey_usage : 0)) + if (card_store_subkey (node, xxpk ? xxpk->pubkey_usage : 0, + &delseckey_list)) { redisplay = 1; sec_shadowing = 1; + delseckey_list_warn = 1; } } } @@ -1952,7 +1964,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, pkt->pkttype = PKT_PUBLIC_KEY; /* Ask gpg-agent to store the secret key to card. */ - if (card_store_subkey (node, 0)) + if (card_store_subkey (node, 0, NULL)) { redisplay = 1; sec_shadowing = 1; @@ -2262,6 +2274,27 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, } } + if (delseckey_list) + { + strlist_t sl; + for (err = 0, sl = delseckey_list; sl; sl = sl->next) + { + if (*sl->d) + { + err = agent_delete_key (ctrl, sl->d, NULL, 1/*force*/); + if (err) + break; + *sl->d = 0; /* Mark deleted. */ + } + } + if (err) + { + log_error (_("deleting copy of secret key failed: %s\n"), + gpg_strerror (err)); + break; /* the "save". */ + } + } + if (sec_shadowing) { err = agent_scd_learn (NULL, 1); @@ -2291,6 +2324,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, } /* End of the main command loop. */ leave: + free_strlist (delseckey_list); release_kbnode (keyblock); keydb_release (kdbhd); xfree (answer); diff --git a/g10/main.h b/g10/main.h index f66f3ef0c..dbaa0c6f3 100644 --- a/g10/main.h +++ b/g10/main.h @@ -516,7 +516,7 @@ void change_pin (int no, int allow_admin); void card_status (ctrl_t ctrl, estream_t fp, const char *serialno); void card_edit (ctrl_t ctrl, strlist_t commands); gpg_error_t card_generate_subkey (ctrl_t ctrl, kbnode_t pub_keyblock); -int card_store_subkey (KBNODE node, int use); +int card_store_subkey (KBNODE node, int use, strlist_t *processed_keys); #endif /*-- migrate.c --*/ From 56b65f33d261ae906244edb1170bc1cd4a39d7f3 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 15 Mar 2023 11:18:29 +0100 Subject: [PATCH 37/60] gpgtar: Print a result status with skiupped files. * tools/gpgtar.h (struct tarinfo_s): Add new fields. * tools/gpgtar-extract.c (check_suspicious_name): Add arg info. (extract_regular): Count files. (gpgtar_extract): Print stats. --- doc/DETAILS | 11 ++++++++ tools/gpgtar-extract.c | 60 +++++++++++++++++++++++++++++++++++------- tools/gpgtar.h | 8 +++++- 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/doc/DETAILS b/doc/DETAILS index 10307bae0..4c1e9b67c 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -1183,6 +1183,17 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: send to the client instead of this status line. Such an inquiry may be used to sync with Pinentry +*** GPGTAR_EXTRACT + This status line is emitted after gpgtar has extracted files. + + - tot :: Total number of files extracted and stored + - skp :: Total number of files skipped during extraction + - bad :: Number of files skipped due to a bad file name + - sus :: Number of files skipped due to a suspicious file name + - sym :: Number of symlinks not restored + - hrd :: Number of hard links not restored + - oth :: Number of files not extracted due to other reasons. + ** Obsolete status codes *** SIGEXPIRED Removed on 2011-02-04. This is deprecated in favor of KEYEXPIRED. diff --git a/tools/gpgtar-extract.c b/tools/gpgtar-extract.c index e690dde7a..936d03e3e 100644 --- a/tools/gpgtar-extract.c +++ b/tools/gpgtar-extract.c @@ -37,7 +37,7 @@ #include "gpgtar.h" static gpg_error_t -check_suspicious_name (const char *name) +check_suspicious_name (const char *name, tarinfo_t info) { size_t n; @@ -47,6 +47,7 @@ check_suspicious_name (const char *name) { log_error ("filename '%s' contains a backslash - " "can't extract on this system\n", name); + info->skipped_badname++; return gpg_error (GPG_ERR_INV_NAME); } #endif /*HAVE_DOSISH_SYSTEM*/ @@ -59,6 +60,7 @@ check_suspicious_name (const char *name) { log_error ("filename '%s' has suspicious parts - not extracting\n", name); + info->skipped_suspicious++; return gpg_error (GPG_ERR_INV_NAME); } @@ -83,7 +85,7 @@ extract_regular (estream_t stream, const char *dirname, if (sl->flags == 1) fname = sl->d; - err = check_suspicious_name (fname); + err = check_suspicious_name (fname, info); if (err) goto leave; @@ -131,8 +133,12 @@ extract_regular (estream_t stream, const char *dirname, /* Fixme: Set permissions etc. */ leave: - if (!err && opt.verbose) - log_info ("extracted '%s'\n", fname); + if (!err) + { + if (opt.verbose) + log_info ("extracted '%s'\n", fname); + info->nextracted++; + } es_fclose (outfp); if (err && fname && outfp) { @@ -146,7 +152,8 @@ extract_regular (estream_t stream, const char *dirname, static gpg_error_t -extract_directory (const char *dirname, tar_header_t hdr, strlist_t exthdr) +extract_directory (const char *dirname, tarinfo_t info, + tar_header_t hdr, strlist_t exthdr) { gpg_error_t err; const char *name; @@ -158,7 +165,7 @@ extract_directory (const char *dirname, tar_header_t hdr, strlist_t exthdr) if (sl->flags == 1) name = sl->d; - err = check_suspicious_name (name); + err = check_suspicious_name (name, info); if (err) goto leave; @@ -230,13 +237,19 @@ extract (estream_t stream, const char *dirname, tarinfo_t info, if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN) err = extract_regular (stream, dirname, info, hdr, exthdr); else if (hdr->typeflag == TF_DIRECTORY) - err = extract_directory (dirname, hdr, exthdr); + err = extract_directory (dirname, info, hdr, exthdr); else { char record[RECORDSIZE]; log_info ("unsupported file type %d for '%s' - skipped\n", (int)hdr->typeflag, hdr->name); + if (hdr->typeflag == TF_SYMLINK) + info->skipped_symlinks++; + else if (hdr->typeflag == TF_HARDLINK) + info->skipped_hardlinks++; + else + info->skipped_other++; for (err = 0, n=0; !err && n < hdr->nrecords; n++) { err = read_record (stream, record); @@ -328,7 +341,7 @@ gpgtar_extract (const char *filename, int decrypt) tarinfo_t tarinfo = &tarinfo_buffer; pid_t pid = (pid_t)(-1); char *logfilename = NULL; - + unsigned long long notextracted; memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer); @@ -478,8 +491,37 @@ gpgtar_extract (const char *filename, int decrypt) } } - leave: + notextracted = tarinfo->skipped_badname; + notextracted += tarinfo->skipped_suspicious; + notextracted += tarinfo->skipped_symlinks; + notextracted += tarinfo->skipped_hardlinks; + notextracted += tarinfo->skipped_other; + if (opt.status_stream) + es_fprintf (opt.status_stream, "[GNUPG:] GPGTAR_EXTRACT" + " %llu %llu %lu %lu %lu %lu %lu\n", + tarinfo->nextracted, + notextracted, + tarinfo->skipped_badname, + tarinfo->skipped_suspicious, + tarinfo->skipped_symlinks, + tarinfo->skipped_hardlinks, + tarinfo->skipped_other); + if (notextracted && !opt.quiet) + { + log_info ("Number of files not extracted: %llu\n", notextracted); + if (tarinfo->skipped_badname) + log_info (" invalid name: %lu\n", tarinfo->skipped_badname); + if (tarinfo->skipped_suspicious) + log_info (" suspicious name: %lu\n", tarinfo->skipped_suspicious); + if (tarinfo->skipped_symlinks) + log_info (" symlink: %lu\n", tarinfo->skipped_symlinks); + if (tarinfo->skipped_hardlinks) + log_info (" hardlink: %lu\n", tarinfo->skipped_hardlinks); + if (tarinfo->skipped_other) + log_info (" other reason: %lu\n", tarinfo->skipped_other); + } + free_strlist (extheader); xfree (header); xfree (dirname); diff --git a/tools/gpgtar.h b/tools/gpgtar.h index 303db0045..9177fcfcb 100644 --- a/tools/gpgtar.h +++ b/tools/gpgtar.h @@ -54,8 +54,14 @@ struct /* An info structure to avoid global variables. */ struct tarinfo_s { - unsigned long long nblocks; /* Count of processed blocks. */ + unsigned long long nblocks; /* Count of processed blocks. */ unsigned long long headerblock; /* Number of current header block. */ + unsigned long long nextracted; /* Number of extracted files. */ + unsigned long skipped_badname; + unsigned long skipped_suspicious; + unsigned long skipped_symlinks; + unsigned long skipped_hardlinks; + unsigned long skipped_other; }; typedef struct tarinfo_s *tarinfo_t; From e5066f2d1c26124359da64fdc46360090910864c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 15 Mar 2023 12:05:45 +0100 Subject: [PATCH 38/60] gpgtar: Do not allow the use of stdout for --status-fd * tools/gpgtar.c (main): Don't allow logging via the Registry. Forbid using stdout for status-fd in crypt mode. -- Without that check a status output would be mixed up with the input to the internal call of gpg. Using the Registry key to enable logging is very annoying. --- tools/gpgtar.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/gpgtar.c b/tools/gpgtar.c index cfd760499..64e5306b2 100644 --- a/tools/gpgtar.c +++ b/tools/gpgtar.c @@ -459,7 +459,7 @@ main (int argc, char **argv) gnupg_reopen_std (GPGTAR_NAME); gpgrt_set_strusage (my_strusage); - log_set_prefix (GPGTAR_NAME, GPGRT_LOG_WITH_PREFIX); + log_set_prefix (GPGTAR_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY); /* Make sure that our subsystems are ready. */ i18n_init(); @@ -501,7 +501,11 @@ main (int argc, char **argv) log_fatal ("status-fd is invalid: %s\n", strerror (errno)); if (fd == 1) - opt.status_stream = es_stdout; + { + opt.status_stream = es_stdout; + if (!skip_crypto) + log_fatal ("using stdout for the status-fd is not possible\n"); + } else if (fd == 2) opt.status_stream = es_stderr; else From e4ac3e7dec92acce32398f571959c7a33534f0c4 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 16 Mar 2023 09:46:05 +0100 Subject: [PATCH 39/60] gpgsm: New option --no-pretty-dn * sm/gpgsm.c (oNoPrettyDN): New. (opts): Add --no-pretty-dn. (main): Implement. * sm/gpgsm.h (opt): Add no_pretty_dn. * sm/certdump.c (gpgsm_es_print_name): Act upon. --- doc/gpgsm.texi | 9 +++++++++ sm/certdump.c | 9 ++++++++- sm/gpgsm.c | 7 ++++++- sm/gpgsm.h | 2 ++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/doc/gpgsm.texi b/doc/gpgsm.texi index a117009bd..364345741 100644 --- a/doc/gpgsm.texi +++ b/doc/gpgsm.texi @@ -629,6 +629,15 @@ always listed in @option{--with-colons} mode. Include info about the presence of a secret key in public key listings done with @code{--with-colons}. +@item --no-pretty-dn +@opindex no-pretty-dn +By default gpgsm prints distinguished names (DNs) like the Issuer or +Subject in a more readable format (e.g. using a well defined order of +the parts). However, this format can't be used as input strings. +This option reverts printing to standard RFC-2253 format and thus +avoids the need to use --dump-cert or --with-colons to get the +``real'' name. + @end table @c ******************************************* diff --git a/sm/certdump.c b/sm/certdump.c index 3ad0edbe3..03bfd4106 100644 --- a/sm/certdump.c +++ b/sm/certdump.c @@ -728,7 +728,14 @@ gpgsm_es_print_name2 (estream_t fp, const char *name, int translate) void gpgsm_es_print_name (estream_t fp, const char *name) { - gpgsm_es_print_name2 (fp, name, 1); + if (opt.no_pretty_dn) + { + if (!name) + name = "[error]"; + es_write_sanitized (fp, name, strlen (name), NULL, NULL); + } + else + gpgsm_es_print_name2 (fp, name, 1); } diff --git a/sm/gpgsm.c b/sm/gpgsm.c index 8ca398360..aeb6ad7a9 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -170,6 +170,7 @@ enum cmd_and_opt_values { oWithKeyScreening, oAnswerYes, oAnswerNo, + oNoPrettyDN, oKeyring, oDefaultKey, oDefRecipient, @@ -385,7 +386,7 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_n (oWithKeygrip, "with-keygrip", "@"), ARGPARSE_s_n (oWithSecret, "with-secret", "@"), ARGPARSE_s_n (oWithKeyScreening,"with-key-screening", "@"), - + ARGPARSE_s_n (oNoPrettyDN, "no-pretty-dn", "@"), ARGPARSE_header ("Security", N_("Options controlling the security")), @@ -1318,6 +1319,10 @@ main ( int argc, char **argv) opt.with_key_screening = 1; break; + case oNoPrettyDN: + opt.no_pretty_dn = 1; + break; + case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oChUid: break; /* Command line only (see above). */ case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; diff --git a/sm/gpgsm.h b/sm/gpgsm.h index e09dd75e9..6149b8491 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -85,6 +85,8 @@ struct int with_key_screening; /* Option --with-key-screening active. */ + int no_pretty_dn; /* Option --no-pretty-dn */ + int pinentry_mode; int request_origin; From f5347fbc25aee7adce6244112aae639b0ff00ccd Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 16 Mar 2023 14:52:28 +0100 Subject: [PATCH 40/60] dirmngr: Add framework to implement a fake CRL feature. * dirmngr/fakecrl.c: New. * dirmngr/dirmngr.h (opt): Add fake_crl. * dirmngr/dirmngr.c (enum cmd_and_opt_values): Add oFakeCRL. (opts): Add "fake-crl" (parse_rereadable_options): Set opt.fake_crl. * dirmngr/server.c (cmd_isvalid): Take care of fakce CRLs. --- dirmngr/Makefile.am | 1 + dirmngr/crlcache.h | 8 ++++++ dirmngr/dirmngr.c | 10 ++++++- dirmngr/dirmngr.h | 1 + dirmngr/fakecrl.c | 63 +++++++++++++++++++++++++++++++++++++++++++++ dirmngr/server.c | 6 ++++- 6 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 dirmngr/fakecrl.c diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am index 1c8065dbb..feee2f5c8 100644 --- a/dirmngr/Makefile.am +++ b/dirmngr/Makefile.am @@ -58,6 +58,7 @@ endif noinst_HEADERS = dirmngr.h crlcache.h crlfetch.h misc.h dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c \ + fakecrl.c \ certcache.c certcache.h \ domaininfo.c \ workqueue.c \ diff --git a/dirmngr/crlcache.h b/dirmngr/crlcache.h index 7db8f01cc..375943462 100644 --- a/dirmngr/crlcache.h +++ b/dirmngr/crlcache.h @@ -45,6 +45,7 @@ crl_sig_result_t; struct crl_cache_entry_s; typedef struct crl_cache_entry_s *crl_cache_entry_t; +/*-- crlcache.c --*/ void crl_cache_init (void); void crl_cache_deinit (void); @@ -68,4 +69,11 @@ gpg_error_t crl_cache_load (ctrl_t ctrl, const char *filename); gpg_error_t crl_cache_reload_crl (ctrl_t ctrl, ksba_cert_t cert); +/*-- fakecrl.c --*/ +crl_cache_result_t fakecrl_isvalid (ctrl_t ctrl, + const char *issuer_hash, + const char *cert_id); + + + #endif /* CRLCACHE_H */ diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 66b7878e5..3c0818af9 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -158,6 +158,7 @@ enum cmd_and_opt_values { oConnectTimeout, oConnectQuickTimeout, oListenBacklog, + oFakeCRL, aTest }; @@ -274,7 +275,7 @@ static gpgrt_opt_t opts[] = { " points to serverlist")), ARGPARSE_s_i (oLDAPTimeout, "ldaptimeout", N_("|N|set LDAP timeout to N seconds")), - + ARGPARSE_s_s (oFakeCRL, "fake-crl", "@"), ARGPARSE_header ("OCSP", N_("Configuration for OCSP")), @@ -709,6 +710,8 @@ parse_rereadable_options (gpgrt_argparse_t *pargs, int reread) opt.ldaptimeout = DEFAULT_LDAP_TIMEOUT; ldapserver_list_needs_reset = 1; opt.debug_cache_expired_certs = 0; + xfree (opt.fake_crl); + opt.fake_crl = NULL; return 1; } @@ -871,6 +874,11 @@ parse_rereadable_options (gpgrt_argparse_t *pargs, int reread) opt.debug_cache_expired_certs = 0; break; + case oFakeCRL: + xfree (opt.fake_crl); + opt.fake_crl = *pargs->r.ret_str? xstrdup (pargs->r.ret_str) : NULL; + break; + default: return 0; /* Not handled. */ } diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index bd4660422..bcb364e8d 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -104,6 +104,7 @@ struct int force; /* Force loading outdated CRLs. */ + char *fake_crl; /* Name of a file with faked CRL entries. */ unsigned int connect_timeout; /* Timeout for connect. */ unsigned int connect_quick_timeout; /* Shorter timeout for connect. */ diff --git a/dirmngr/fakecrl.c b/dirmngr/fakecrl.c new file mode 100644 index 000000000..43b68a57a --- /dev/null +++ b/dirmngr/fakecrl.c @@ -0,0 +1,63 @@ +/* fakecrl.c - Debug code to test revocations. + * Copyright (C) 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. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/* + * For regression testing it is useful to have a way to claim that + * certain certificates are revoked. We achieve this with the + * --fake-crl option which takes a file name as argument. The format + * of the file is: empty lines and lines starting with a hash sign are + * ignored. A line with the issuer DN in brackets starts entries for + * this issuer. All following lines up to the next line with a + * bracket list revoked certificates. For each revoked certificate + * the hexadecimal encoded serial number is listed, followed by the + * revocation date in ISO 14 byte notation, optionally followed by a + * reason keyword. Example: + *--------------------- + * # Sample Fake CRL + * [CN=Bayern-Softtoken-Issuing-CA-2019,OU=IT-DLZ,O=Freistaat Bayern,C=DE] + * 7FD62B1A9EA5BBC84971183080717004 20221125T074346 + * 11223344556677 20230101T000000 key_compromise + * 0000000000000042 20221206T121200 certificate_hold + * + * [CN=CA IVBB Deutsche Telekom AG 18,OU=Bund,O=PKI-1-Verwaltung,C=DE] + * 735D1B97389F 20230210T083947 + *--------------------- + */ +#include + +#include +#include + +#include "dirmngr.h" +#include "crlcache.h" + + + +/* Returns 0 if the given certificate is not listed in the faked CRL + * or no fake CRL is configured. It is expected that the caller then + * consults the real CRL. */ +gpg_error_t +fakecrl_isvalid (ctrl_t ctrl, const char *issuer_hash, const char *cert_id) +{ + (void)ctrl; + (void)issuer_hash; + (void)cert_id; + return 0; +} diff --git a/dirmngr/server.c b/dirmngr/server.c index cd71592a4..cd0839aad 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -1339,6 +1339,10 @@ cmd_isvalid (assuan_context_t ctx, char *line) } else if (only_ocsp) err = gpg_error (GPG_ERR_NO_CRL_KNOWN); + else if (opt.fake_crl && (err = fakecrl_isvalid (ctrl, issuerhash, serialno))) + { + /* We already got the error code. */ + } else { switch (crl_cache_isvalid (ctrl, @@ -1377,7 +1381,7 @@ cmd_isvalid (assuan_context_t ctx, char *line) /* If the line contains a SHA-1 fingerprint as the first argument, - return the FPR vuffer on success. The function checks that the + return the FPR buffer on success. The function checks that the fingerprint consists of valid characters and prints and error message if it does not and returns NULL. Fingerprints are considered optional and thus no explicit error is returned. NULL is From 625aeb65b0e75192a414fdca5383cb67c996adee Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 20 Mar 2023 19:24:49 +0100 Subject: [PATCH 41/60] dirmngr: New command AD_QUERY. * dirmngr/dirmngr.h: Include name-value.h (struct server_control_s): Add rootdse and rootdse_tried. * dirmngr/dirmngr.c (dirmngr_deinit_default_ctrl): Release them. * dirmngr/ks-engine.h (KS_GET_FLAG_ROOTDSE): Add two new flags. * dirmngr/ks-engine-ldap.c: Include ks-action.h (SERVERINFO_GENERIC): New. (struct ks_engine_ldap_local_s): Add scope. (ks_ldap_new_state): Set a default scope. (ks_ldap_clear_state): Ditto. (my_ldap_connect): Add flag generic. (return_all_attributes): New. (fetch_rootdse): New. (basedn_from_rootdse): New. (ks_ldap_get): Move some code out to ... (ks_ldap_prepare_my_state): New. (ks_ldap_query): New. * dirmngr/ks-action.c (ks_action_parse_uri): Factored out from server.c (ks_action_query): New. * dirmngr/server.c (make_keyserver_item): Factored most code out to ks_action_parse_uri. (cmd_ad_query): New. -- This command allows to query the Windows Active directory. --- dirmngr/dirmngr.c | 2 + dirmngr/dirmngr.h | 6 + dirmngr/dirmngr_ldap.c | 2 +- dirmngr/ks-action.c | 145 +++++++++- dirmngr/ks-action.h | 4 + dirmngr/ks-engine-ldap.c | 567 +++++++++++++++++++++++++++++++++++---- dirmngr/ks-engine.h | 5 + dirmngr/server.c | 165 ++++++------ 8 files changed, 758 insertions(+), 138 deletions(-) diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 3c0818af9..bb54f4edd 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -1705,6 +1705,8 @@ dirmngr_deinit_default_ctrl (ctrl_t ctrl) xfree (ctrl->http_proxy); ctrl->http_proxy = NULL; + nvc_release (ctrl->rootdse); + ctrl->rootdse = NULL; } diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index bcb364e8d..1128e118b 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -36,6 +36,7 @@ #include "../common/sysutils.h" /* (gnupg_fd_t) */ #include "../common/asshelp.h" /* (assuan_context_t) */ #include "../common/i18n.h" +#include "../common/name-value.h" #include "dirmngr-status.h" #include "http.h" /* (parsed_uri_t) */ @@ -220,9 +221,12 @@ struct server_control_s int audit_events; /* Send audit events to client. */ char *http_proxy; /* The used http_proxy or NULL. */ + nvc_t rootdse; /* Container wit the rootDSE properties. */ + unsigned int timeout; /* Timeout for connect calls in ms. */ unsigned int http_no_crl:1; /* Do not check CRLs for https. */ + unsigned int rootdse_tried:1;/* Already tried to get the rootDSE. */ }; @@ -241,6 +245,8 @@ void ks_hkp_reload (void); void ks_hkp_init (void); /*-- server.c --*/ +void release_uri_item_list (uri_item_t list); + ldap_server_t get_ldapservers_from_ctrl (ctrl_t ctrl); ksba_cert_t get_cert_local (ctrl_t ctrl, const char *issuer); ksba_cert_t get_issuing_cert_local (ctrl_t ctrl, const char *issuer); diff --git a/dirmngr/dirmngr_ldap.c b/dirmngr/dirmngr_ldap.c index c6a4dd11f..412d0ad1f 100644 --- a/dirmngr/dirmngr_ldap.c +++ b/dirmngr/dirmngr_ldap.c @@ -823,7 +823,7 @@ fetch_ldap (LDAP *ld, const char *base, int scope, const char *filter) /* Main processing. Take the filter and run the LDAP query. The * result is printed to stdout, errors are logged to the log stream. * To allow searching with a different base it is possible to extend - * the filer. For example: + * the filter. For example: * * ^CN=foo, OU=My Users&(objectClasses=*) * diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c index 7de4a3b95..6b5f78f49 100644 --- a/dirmngr/ks-action.c +++ b/dirmngr/ks-action.c @@ -34,6 +34,100 @@ # include "ldap-parse-uri.h" #endif + +/* Parse an URI and store it in a new parsed URI item object which is + * returned at R_PARSEDURI (with its next set to NULL). On error an + * error code is returned an NULL stored at R_PARSEDITEM. */ +gpg_error_t +ks_action_parse_uri (const char *uri, uri_item_t *r_parseduri) +{ + gpg_error_t err; + uri_item_t item; + char *tmpstr = NULL; +#if USE_LDAP + const char *s; +#endif + + *r_parseduri = NULL; + + if (!uri) + return gpg_error (GPG_ERR_INV_URI); + + item = xtrymalloc (sizeof *item + strlen (uri)); + if (!item) + return gpg_error_from_syserror (); + + item->next = NULL; + item->parsed_uri = NULL; + strcpy (item->uri, uri); + +#if USE_LDAP + if (!strncmp (uri, "ldap:", 5) && !(uri[5] == '/' && uri[6] == '/')) + { + /* Special ldap scheme given. This differs from a valid ldap + * scheme in that no double slash follows. We use + * http_parse_uri to put it as opaque value into parsed_uri. */ + tmpstr = strconcat ("opaque:", uri+5, NULL); + if (!tmpstr) + err = gpg_error_from_syserror (); + else + err = http_parse_uri (&item->parsed_uri, tmpstr, 0); + } + else if ((s=strchr (uri, ':')) && !(s[1] == '/' && s[2] == '/')) + { + /* No valid scheme given. We use http_parse_uri to put the + * string as opaque value into parsed_uri. */ + tmpstr = strconcat ("opaque:", uri, NULL); + if (!tmpstr) + err = gpg_error_from_syserror (); + else + err = http_parse_uri (&item->parsed_uri, tmpstr, 0); + } + else if (ldap_uri_p (uri)) + { + int fixup = 0; + /* Fixme: We should get rid of that parser and replace it with + * our generic (http) URI parser. */ + + /* If no port has been specified and the scheme ist ldaps we use + * our idea of the default port because the standard LDAP URL + * parser would use 636 here. This is because we redefined + * ldaps to mean starttls. */ +#ifdef HAVE_W32_SYSTEM + if (!strcmp (uri, "ldap:///")) + fixup = 1; + else +#endif + if (!http_parse_uri (&item->parsed_uri,uri,HTTP_PARSE_NO_SCHEME_CHECK)) + { + if (!item->parsed_uri->port + && !strcmp (item->parsed_uri->scheme, "ldaps")) + fixup = 2; + http_release_parsed_uri (item->parsed_uri); + item->parsed_uri = NULL; + } + + err = ldap_parse_uri (&item->parsed_uri, uri); + if (!err && fixup == 1) + item->parsed_uri->ad_current = 1; + else if (!err && fixup == 2) + item->parsed_uri->port = 389; + } + else +#endif /* USE_LDAP */ + { + err = http_parse_uri (&item->parsed_uri, uri, HTTP_PARSE_NO_SCHEME_CHECK); + } + + xfree (tmpstr); + if (err) + xfree (item); + else + *r_parseduri = item; + return err; +} + + /* Called by the engine's help functions to print the actual help. */ gpg_error_t ks_print_help (ctrl_t ctrl, const char *text) @@ -270,7 +364,7 @@ ks_action_get (ctrl_t ctrl, uri_item_t keyservers, || strcmp (uri->parsed_uri->scheme, "https") == 0); int is_ldap = 0; - if ((ks_get_flags & KS_GET_FLAG_ONLY_LDAP)) + if ((ks_get_flags & (KS_GET_FLAG_ONLY_LDAP|KS_GET_FLAG_ONLY_AD))) is_hkp_s = is_http_s = 0; #if USE_LDAP @@ -448,3 +542,52 @@ ks_action_put (ctrl_t ctrl, uri_item_t keyservers, err = first_err; return err; } + + + +/* Query the default LDAP server or the one given by URL using + * the filter expression FILTER. Write the result to OUTFP. */ +gpg_error_t +ks_action_query (ctrl_t ctrl, const char *url, unsigned int ks_get_flags, + const char *filter, char **attrs, estream_t outfp) +{ +#if USE_LDAP + gpg_error_t err; + estream_t infp = NULL; + uri_item_t puri; /* The broken down URI (only one item used). */ + + if (!url && (ks_get_flags & KS_GET_FLAG_ROOTDSE)) + url = "ldap://"; + + err = ks_action_parse_uri (url, &puri); + if (err) + return err; + + if ((ks_get_flags & KS_GET_FLAG_ROOTDSE)) + { + /* Reset authentication for a serverless connection. */ + puri->parsed_uri->ad_current = 0; + puri->parsed_uri->auth = NULL; + } + + if (!strcmp (puri->parsed_uri->scheme, "ldap") + || !strcmp (puri->parsed_uri->scheme, "ldaps") + || !strcmp (puri->parsed_uri->scheme, "ldapi") + || puri->parsed_uri->opaque) + { + err = ks_ldap_query (ctrl, puri->parsed_uri, ks_get_flags, filter, + attrs, &infp); + if (!err) + err = copy_stream (infp, outfp); + } + else + err = gpg_error (GPG_ERR_CONFIGURATION); /* No LDAP server known. */ + + es_fclose (infp); + release_uri_item_list (puri); + return err; + +#else /* !USE_LDAP */ + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} diff --git a/dirmngr/ks-action.h b/dirmngr/ks-action.h index e780fc7db..f7d731d7a 100644 --- a/dirmngr/ks-action.h +++ b/dirmngr/ks-action.h @@ -21,6 +21,7 @@ #ifndef DIRMNGR_KS_ACTION_H #define DIRMNGR_KS_ACTION_H 1 +gpg_error_t ks_action_parse_uri (const char *uri, uri_item_t *r_parseduri); gpg_error_t ks_action_help (ctrl_t ctrl, const char *url); gpg_error_t ks_action_resolve (ctrl_t ctrl, uri_item_t keyservers); gpg_error_t ks_action_search (ctrl_t ctrl, uri_item_t keyservers, @@ -32,6 +33,9 @@ gpg_error_t ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp); gpg_error_t ks_action_put (ctrl_t ctrl, uri_item_t keyservers, void *data, size_t datalen, void *info, size_t infolen); +gpg_error_t ks_action_query (ctrl_t ctrl, const char *ldapserver, + unsigned int ks_get_flags, + const char *filter, char **attr, estream_t outfp); #endif /*DIRMNGR_KS_ACTION_H*/ diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c index 0912c6d02..a056b1163 100644 --- a/dirmngr/ks-engine-ldap.c +++ b/dirmngr/ks-engine-ldap.c @@ -1,7 +1,7 @@ /* ks-engine-ldap.c - talk to a LDAP keyserver * Copyright (C) 2001, 2002, 2004, 2005, 2006 * 2007 Free Software Foundation, Inc. - * Copyright (C) 2015, 2020 g10 Code GmbH + * Copyright (C) 2015, 2020, 2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -32,6 +32,7 @@ #include "misc.h" #include "../common/userids.h" #include "../common/mbox-util.h" +#include "ks-action.h" #include "ks-engine.h" #include "ldap-misc.h" #include "ldap-parse-uri.h" @@ -43,6 +44,7 @@ #define SERVERINFO_PGPKEYV2 2 /* Needs "pgpKeyV2" instead of "pgpKey"*/ #define SERVERINFO_SCHEMAV2 4 /* Version 2 of the Schema. */ #define SERVERINFO_NTDS 8 /* Server is an Active Directory. */ +#define SERVERINFO_GENERIC 16 /* Connected in genric mode. */ /* The page size requested from the server. */ @@ -61,6 +63,7 @@ struct ks_engine_ldap_local_s LDAPMessage *message; LDAPMessage *msg_iter; /* Iterator for message. */ unsigned int serverinfo; + int scope; char *basedn; char *keyspec; char *filter; @@ -192,7 +195,12 @@ ks_ldap_help (ctrl_t ctrl, parsed_uri_t uri) static struct ks_engine_ldap_local_s * ks_ldap_new_state (void) { - return xtrycalloc (1, sizeof(struct ks_engine_ldap_local_s)); + struct ks_engine_ldap_local_s *state; + + state = xtrycalloc (1, sizeof(struct ks_engine_ldap_local_s)); + if (state) + state->scope = LDAP_SCOPE_SUBTREE; + return state; } @@ -217,6 +225,7 @@ ks_ldap_clear_state (struct ks_engine_ldap_local_s *state) } state->serverinfo = 0; xfree (state->basedn); + state->scope = LDAP_SCOPE_SUBTREE; state->basedn = NULL; xfree (state->keyspec); state->keyspec = NULL; @@ -240,6 +249,45 @@ ks_ldap_free_state (struct ks_engine_ldap_local_s *state) } +/* Helper for ks_ldap_get and ks_ldap_query. On return first_mode and + * next_mode are set accordingly. */ +static gpg_error_t +ks_ldap_prepare_my_state (ctrl_t ctrl, unsigned int ks_get_flags, + int *first_mode, int *next_mode) +{ + *first_mode = *next_mode = 0; + + if ((ks_get_flags & KS_GET_FLAG_FIRST)) + { + if (ctrl->ks_get_state) + ks_ldap_clear_state (ctrl->ks_get_state); + else if (!(ctrl->ks_get_state = ks_ldap_new_state ())) + return gpg_error_from_syserror (); + *first_mode = 1; + } + + if ((ks_get_flags & KS_GET_FLAG_NEXT)) + { + if (!ctrl->ks_get_state || !ctrl->ks_get_state->ldap_conn + || !ctrl->ks_get_state->message) + { + log_error ("ks-ldap: --next requested but no state\n"); + return gpg_error (GPG_ERR_INV_STATE); + } + *next_mode = 1; + } + + /* Do not keep an old state around if not needed. */ + if (!(*first_mode || *next_mode)) + { + ks_ldap_free_state (ctrl->ks_get_state); + ctrl->ks_get_state = NULL; + } + + return 0; +} + + /* Convert a keyspec to a filter. Return an error if the keyspec is bad or is not supported. The filter is escaped and returned in @@ -437,7 +485,9 @@ interrogate_ldap_dn (LDAP *ldap_conn, const char *basedn_search, * * URI describes the server to connect to and various options * including whether to use TLS and the username and password (see - * ldap_parse_uri for a description of the various fields). + * ldap_parse_uri for a description of the various fields). Be + * default a PGP keyserver is assumed; if GENERIC is true a generic + * ldap conenction is instead established. * * Returns: The ldap connection handle in *LDAP_CONNP, R_BASEDN is set * to the base DN for the PGP key space, several flags will be stored @@ -450,7 +500,7 @@ interrogate_ldap_dn (LDAP *ldap_conn, const char *basedn_search, * If it is NULL, then the server does not appear to be an OpenPGP * keyserver. */ static gpg_error_t -my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, +my_ldap_connect (parsed_uri_t uri, unsigned int generic, LDAP **ldap_connp, char **r_basedn, char **r_host, int *r_use_tls, unsigned int *r_serverinfo) { @@ -519,15 +569,15 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, } if (opt.verbose) - log_info ("ldap connect to '%s:%d:%s:%s:%s:%s%s%s'\n", + log_info ("ldap connect to '%s:%d:%s:%s:%s:%s%s%s'%s\n", host, port, basedn_arg ? basedn_arg : "", bindname ? bindname : "", password ? "*****" : "", use_tls == 1? "starttls" : use_tls == 2? "ldaptls" : "plain", use_ntds ? ",ntds":"", - use_areconly? ",areconly":""); - + use_areconly? ",areconly":"", + generic? " (generic)":""); /* If the uri specifies a secure connection and we don't support TLS, then fail; don't silently revert to an insecure @@ -535,7 +585,7 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, if (use_tls) { #ifndef HAVE_LDAP_START_TLS_S - log_error ("ldap: can't connect to the server: no TLS support."); + log_error ("ks-ldap: can't connect to the server: no TLS support."); err = GPG_ERR_LDAP_NOT_SUPPORTED; goto out; #endif @@ -607,6 +657,8 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, { int ver = opt.ldaptimeout; + /* fixme: also use LDAP_OPT_SEND_TIMEOUT? */ + lerr = ldap_set_option (ldap_conn, LDAP_OPT_TIMELIMIT, &ver); if (lerr != LDAP_SUCCESS) { @@ -699,7 +751,21 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, /* By default we don't bind as there is usually no need to. */ } - if (basedn_arg && *basedn_arg) + if (generic) + { + /* Generic use of this function for arbitrary LDAP servers. */ + *r_serverinfo |= SERVERINFO_GENERIC; + if (basedn_arg && *basedn_arg) + { + basedn = xtrystrdup (basedn_arg); + if (!basedn) + { + err = gpg_error_from_syserror (); + goto out; + } + } + } + else if (basedn_arg && *basedn_arg) { /* User specified base DN. In this case we know the server is a * real LDAP server. */ @@ -819,11 +885,15 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, if (!err && opt.debug) { log_debug ("ldap_conn: %p\n", ldap_conn); - log_debug ("server_type: %s\n", ((*r_serverinfo & SERVERINFO_REALLDAP) - ? "LDAP" : "PGP.com keyserver") ); + log_debug ("server_type: %s\n", + ((*r_serverinfo & SERVERINFO_GENERIC) + ? "Generic" : + (*r_serverinfo & SERVERINFO_REALLDAP) + ? "LDAP" : "PGP.com keyserver") ); log_debug ("basedn: %s\n", basedn); - log_debug ("pgpkeyattr: %s\n", - (*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey"); + if (!(*r_serverinfo & SERVERINFO_GENERIC)) + log_debug ("pgpkeyattr: %s\n", + (*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey"); } ldapserver_list_free (server); @@ -1028,11 +1098,132 @@ return_one_keyblock (LDAP *ldap_conn, LDAPMessage *msg, unsigned int serverinfo, } -/* Helper for ks_ldap_get. Note that KEYSPEC is only used for - * diagnostics. */ +/* Helper for ks_ldap_query. Returns 0 if an attr was fetched and + * printed to FP. The error code GPG_ERR_NO_DATA is returned if no + * data was printed. Note that FP is updated by this function. */ +static gpg_error_t +return_all_attributes (LDAP *ld, LDAPMessage *msg, estream_t *fp) +{ + gpg_error_t err = 0; + BerElement *berctx = NULL; + char *attr = NULL; + const char *attrprefix; + struct berval **values = NULL; + int idx; + int any = 0; + const char *s; + const char *val; + size_t len; + char *mydn; + + mydn = ldap_get_dn (ld, msg); + if (!*fp) + { + *fp = es_fopenmem(0, "rw"); + if (!*fp) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + /* Always print the DN - note that by using only unbkown attributes + * it is pissible to list just the DNs with out addiional + * linefeeds. */ + es_fprintf (*fp, "Dn: %s\n", mydn? mydn : "[oops DN missing]"); + + for (npth_unprotect (), attr = ldap_first_attribute (ld, msg, &berctx), + npth_protect (); + attr; + npth_unprotect (), attr = ldap_next_attribute (ld, msg, berctx), + npth_protect ()) + { + npth_unprotect (); + values = ldap_get_values_len (ld, msg, attr); + npth_protect (); + + if (!values) + { + if (opt.verbose) + log_info ("attribute '%s' not found\n", attr); + ldap_memfree (attr); + attr = NULL; + continue; + } + + any = 1; + + if (opt.verbose > 1) + { + log_info ("found attribute '%s'\n", attr); + for (idx=0; values[idx]; idx++) + log_info (" length[%d]=%d\n", + idx, (int)values[0]->bv_len); + } + + if (!ascii_strcasecmp (attr, "Dn")) + attrprefix = "X-"; + else if (*attr == '#') + attrprefix = "X-hash-"; + else if (*attr == ' ') + attrprefix = "X-blank-"; + else + attrprefix = ""; + /* FIXME: We should remap all invalid chars in ATTR. */ + + for (idx=0; values[idx]; idx++) + { + es_fprintf (*fp, "%s%s: ", attrprefix, attr); + val = values[idx]->bv_val; + len = values[idx]->bv_len; + while (len && (s = memchr (val, '\n', len))) + { + s++; /* We als want to print the LF. */ + if (es_fwrite (val, s - val, 1, *fp) != 1) + goto fwrite_failed; + len -= (s-val); + val = s; + if (len && es_fwrite (" ", 1, 1, *fp) != 1) + goto fwrite_failed; + } + if (len && es_fwrite (val, len, 1, *fp) != 1) + goto fwrite_failed; + if (es_fwrite ("\n", 1, 1, *fp) != 1) /* Final LF. */ + goto fwrite_failed; + } + + ldap_value_free_len (values); + values = NULL; + ldap_memfree (attr); + attr = NULL; + } + + /* One final linefeed to prettify the output. */ + if (any && es_fwrite ("\n", 1, 1, *fp) != 1) + goto fwrite_failed; + + + leave: + if (values) + ldap_value_free_len (values); + ldap_memfree (attr); + if (mydn) + ldap_memfree (mydn); + ber_free (berctx, 0); + return err; + + fwrite_failed: + err = gpg_error_from_syserror (); + log_error ("error writing to stdout: %s\n", gpg_strerror (err)); + goto leave; +} + + +/* Helper for ks_ldap_get and ks_ldap_query. Note that KEYSPEC is + * only used for diagnostics. */ static gpg_error_t search_and_parse (ctrl_t ctrl, const char *keyspec, - LDAP *ldap_conn, char *basedn, char *filter, + LDAP *ldap_conn, char *basedn, int scope, char *filter, char **attrs, LDAPMessage **r_message) { gpg_error_t err = 0; @@ -1065,7 +1256,7 @@ search_and_parse (ctrl_t ctrl, const char *keyspec, } npth_unprotect (); - l_err = ldap_search_ext_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE, + l_err = ldap_search_ext_s (ldap_conn, basedn, scope, filter, attrs, 0, srvctrls[0]? srvctrls : NULL, NULL, NULL, 0, r_message); @@ -1130,7 +1321,7 @@ search_and_parse (ctrl_t ctrl, const char *keyspec, if (count < 1) { if (!ctrl->ks_get_state || ctrl->ks_get_state->pageno == 1) - log_info ("ks-ldap: key %s not found on keyserver\n", keyspec); + log_info ("ks-ldap: '%s' not found on LDAP server\n", keyspec); if (count == -1) err = ldap_to_gpg_err (ldap_conn); @@ -1150,6 +1341,77 @@ search_and_parse (ctrl_t ctrl, const char *keyspec, } +/* Fetch all entries from the RootDSE and return them as a name value + * object. */ +static nvc_t +fetch_rootdse (ctrl_t ctrl, parsed_uri_t uri) +{ + gpg_error_t err; + estream_t infp = NULL; + uri_item_t puri; /* The broken down URI (only one item used). */ + nvc_t nvc = NULL; + + /* FIXME: We need the unparsed URI here - use uri_item_t instead + * of fix the parser to fill in original */ + err = ks_action_parse_uri (uri && uri->original? uri->original : "ldap://", + &puri); + if (err) + return NULL; + + /* Reset authentication for a serverless. */ + puri->parsed_uri->ad_current = 0; + puri->parsed_uri->auth = NULL; + + if (!strcmp (puri->parsed_uri->scheme, "ldap") + || !strcmp (puri->parsed_uri->scheme, "ldaps") + || !strcmp (puri->parsed_uri->scheme, "ldapi") + || puri->parsed_uri->opaque) + { + err = ks_ldap_query (ctrl, puri->parsed_uri, KS_GET_FLAG_ROOTDSE, + "^&base&(objectclass=*)", NULL, &infp); + if (err) + log_error ("ldap: reading the rootDES failed: %s\n", + gpg_strerror (err)); + else if ((err = nvc_parse (&nvc, NULL, infp))) + log_error ("parsing the rootDES failed: %s\n", gpg_strerror (err)); + } + + es_fclose (infp); + release_uri_item_list (puri); + if (err) + { + nvc_release (nvc); + nvc = NULL; + } + return nvc; +} + + +/* Return the baseDN for URI which might have already been cached for + * this session. */ +static char * +basedn_from_rootdse (ctrl_t ctrl, parsed_uri_t uri) +{ + const char *s; + + if (!ctrl->rootdse && !ctrl->rootdse_tried) + { + ctrl->rootdse = fetch_rootdse (ctrl, uri); + ctrl->rootdse_tried = 1; + if (ctrl->rootdse) + { + log_debug ("Dump of all rootDSE attributes:\n"); + nvc_write (ctrl->rootdse, log_get_stream ()); + log_debug ("End of dump\n"); + } + } + s = nvc_get_string (ctrl->rootdse, "defaultNamingContext:"); + return s? xtrystrdup (s): NULL; +} + + + + /* Get the key described key the KEYSPEC string from the keyserver * identified by URI. On success R_FP has an open stream to read the * data. KS_GET_FLAGS conveys flags from the client. */ @@ -1157,13 +1419,14 @@ gpg_error_t ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, unsigned int ks_get_flags, estream_t *r_fp) { - gpg_error_t err = 0; + gpg_error_t err; unsigned int serverinfo; char *host = NULL; int use_tls; char *filter = NULL; LDAP *ldap_conn = NULL; char *basedn = NULL; + int scope = LDAP_SCOPE_SUBTREE; estream_t fp = NULL; LDAPMessage *message = NULL; LDAPMessage *msg; @@ -1184,41 +1447,14 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, NULL }; - (void) ctrl; - if (dirmngr_use_tor ()) { return no_ldap_due_to_tor (ctrl); } - /* Make sure we got a state. */ - if ((ks_get_flags & KS_GET_FLAG_FIRST)) - { - if (ctrl->ks_get_state) - ks_ldap_clear_state (ctrl->ks_get_state); - else if (!(ctrl->ks_get_state = ks_ldap_new_state ())) - return gpg_error_from_syserror (); - first_mode = 1; - } - - if ((ks_get_flags & KS_GET_FLAG_NEXT)) - { - if (!ctrl->ks_get_state || !ctrl->ks_get_state->ldap_conn - || !ctrl->ks_get_state->message) - { - log_error ("ks_ldap: --next requested but no state\n"); - return gpg_error (GPG_ERR_INV_STATE); - } - next_mode = 1; - } - - /* Do not keep an old state around if not needed. */ - if (!(first_mode || next_mode)) - { - ks_ldap_free_state (ctrl->ks_get_state); - ctrl->ks_get_state = NULL; - } - + err = ks_ldap_prepare_my_state (ctrl, ks_get_flags, &first_mode, &next_mode); + if (err) + return err; if (next_mode) { @@ -1236,6 +1472,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, err = search_and_parse (ctrl, ctrl->ks_get_state->keyspec, ctrl->ks_get_state->ldap_conn, ctrl->ks_get_state->basedn, + ctrl->ks_get_state->scope, ctrl->ks_get_state->filter, attrs, &ctrl->ks_get_state->message); @@ -1284,7 +1521,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, else /* Not in --next mode. */ { /* Make sure we are talking to an OpenPGP LDAP server. */ - err = my_ldap_connect (uri, &ldap_conn, + err = my_ldap_connect (uri, 0, &ldap_conn, &basedn, &host, &use_tls, &serverinfo); if (err || !basedn) { @@ -1311,8 +1548,8 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, /* Replace "dummy". */ attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey"; - err = search_and_parse (ctrl, keyspec, ldap_conn, basedn, filter, attrs, - &message); + err = search_and_parse (ctrl, keyspec, ldap_conn, basedn, scope, + filter, attrs, &message); if (err) goto leave; @@ -1363,6 +1600,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, ctrl->ks_get_state->message = message; message = NULL; ctrl->ks_get_state->serverinfo = serverinfo; + ctrl->ks_get_state->scope = scope; ctrl->ks_get_state->basedn = basedn; basedn = NULL; ctrl->ks_get_state->keyspec = keyspec? xtrystrdup (keyspec) : NULL; @@ -1423,7 +1661,7 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, } /* Make sure we are talking to an OpenPGP LDAP server. */ - err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL, &serverinfo); + err = my_ldap_connect (uri, 0, &ldap_conn, &basedn, NULL, NULL, &serverinfo); if (err || !basedn) { if (!err) @@ -2312,7 +2550,7 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri, return no_ldap_due_to_tor (ctrl); } - err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL, &serverinfo); + err = my_ldap_connect (uri, 0, &ldap_conn, &basedn, NULL, NULL, &serverinfo); if (err || !basedn) { if (!err) @@ -2538,3 +2776,224 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri, return err; } + + + +/* Get the data described by FILTER_ARG from URI. On success R_FP has + * an open stream to read the data. KS_GET_FLAGS conveys flags from + * the client. ATTRS is a NULL terminated list of attributes to + * return or NULL for all. */ +gpg_error_t +ks_ldap_query (ctrl_t ctrl, parsed_uri_t uri, unsigned int ks_get_flags, + const char *filter_arg, char **attrs, estream_t *r_fp) +{ + gpg_error_t err; + unsigned int serverinfo; + char *host = NULL; + int use_tls; + LDAP *ldap_conn = NULL; + char *basedn = NULL; + estream_t fp = NULL; + char *filter = NULL; + int scope = LDAP_SCOPE_SUBTREE; + LDAPMessage *message = NULL; + LDAPMessage *msg; + int anydata = 0; + int first_mode = 0; + int next_mode = 0; + int get_first; + + if (dirmngr_use_tor ()) + return no_ldap_due_to_tor (ctrl); + + if ((!filter_arg || !*filter_arg) && (ks_get_flags & KS_GET_FLAG_ROOTDSE)) + filter_arg = "^&base&(objectclass=*)"; + + err = ks_ldap_prepare_my_state (ctrl, ks_get_flags, &first_mode, &next_mode); + if (err) + goto leave; + + if (!next_mode) /* (In --next mode the filter is ignored.) */ + { + if (!filter_arg || !*filter_arg) + { + err = gpg_error (GPG_ERR_LDAP_FILTER); + goto leave; + } + err = ldap_parse_extfilter (filter_arg, 0, &basedn, &scope, &filter); + if (err) + goto leave; + } + + + if (next_mode) + { + next_again: + if (!ctrl->ks_get_state->msg_iter && ctrl->ks_get_state->more_pages) + { + /* Get the next page of results. */ + if (ctrl->ks_get_state->message) + { + ldap_msgfree (ctrl->ks_get_state->message); + ctrl->ks_get_state->message = NULL; + } + err = search_and_parse (ctrl, ctrl->ks_get_state->keyspec, + ctrl->ks_get_state->ldap_conn, + ctrl->ks_get_state->basedn, + ctrl->ks_get_state->scope, + ctrl->ks_get_state->filter, + attrs, + &ctrl->ks_get_state->message); + if (err) + goto leave; + ctrl->ks_get_state->msg_iter = ctrl->ks_get_state->message; + get_first = 1; + } + else + get_first = 0; + + while (ctrl->ks_get_state->msg_iter) + { + npth_unprotect (); + ctrl->ks_get_state->msg_iter + = get_first? ldap_first_entry (ctrl->ks_get_state->ldap_conn, + ctrl->ks_get_state->msg_iter) + /* */ : ldap_next_entry (ctrl->ks_get_state->ldap_conn, + ctrl->ks_get_state->msg_iter); + npth_protect (); + get_first = 0; + if (ctrl->ks_get_state->msg_iter) + { + err = return_all_attributes (ctrl->ks_get_state->ldap_conn, + ctrl->ks_get_state->msg_iter, + &fp); + if (!err) + break; /* Found. */ + else if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = 0; /* Skip empty attributes. */ + else + goto leave; + } + } + + if (!ctrl->ks_get_state->msg_iter || !fp) + { + ctrl->ks_get_state->msg_iter = NULL; + if (ctrl->ks_get_state->more_pages) + goto next_again; + err = gpg_error (GPG_ERR_NO_DATA); + } + + } + else /* Not in --next mode. */ + { + /* Connect to the LDAP server in generic mode. */ + char *tmpbasedn; + + err = my_ldap_connect (uri, 1 /*generic*/, &ldap_conn, + &tmpbasedn, &host, &use_tls, &serverinfo); + if (err) + goto leave; + if (basedn) + xfree (tmpbasedn); /* Extended syntax overrides. */ + else if (tmpbasedn) + basedn = tmpbasedn; + else if (!(ks_get_flags & KS_GET_FLAG_ROOTDSE)) + { + /* No BaseDN known - get one. */ + basedn = basedn_from_rootdse (ctrl, uri); + } + + if (opt.debug) + { + log_debug ("ks-ldap: using basedn: %s\n", basedn); + log_debug ("ks-ldap: using filter: %s\n", filter); + } + + err = search_and_parse (ctrl, filter, ldap_conn, basedn, scope, filter, + attrs, &message); + if (err) + goto leave; + + + for (npth_unprotect (), + msg = ldap_first_entry (ldap_conn, message), + npth_protect (); + msg; + npth_unprotect (), + msg = ldap_next_entry (ldap_conn, msg), + npth_protect ()) + { + err = return_all_attributes (ldap_conn, msg, &fp); + if (!err) + { + anydata = 1; + if (first_mode) + break; + } + else if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = 0; /* Skip empty/duplicate attributes. */ + else + goto leave; + } + + if (ctrl->ks_get_state) /* Save the iterator. */ + ctrl->ks_get_state->msg_iter = msg; + + if (!fp) /* Nothing was found. */ + err = gpg_error (GPG_ERR_NO_DATA); + + if (!err && anydata) + err = dirmngr_status_printf (ctrl, "SOURCE", "%s://%s", + use_tls? "ldaps" : "ldap", + host? host:""); + } + + + leave: + /* Store our state if needed. */ + if (!err && (ks_get_flags & KS_GET_FLAG_FIRST)) + { + log_assert (!ctrl->ks_get_state->ldap_conn); + ctrl->ks_get_state->ldap_conn = ldap_conn; + ldap_conn = NULL; + log_assert (!ctrl->ks_get_state->message); + ctrl->ks_get_state->message = message; + message = NULL; + ctrl->ks_get_state->serverinfo = serverinfo; + ctrl->ks_get_state->scope = scope; + ctrl->ks_get_state->basedn = basedn; + basedn = NULL; + ctrl->ks_get_state->keyspec = filter? xtrystrdup (filter) : NULL; + ctrl->ks_get_state->filter = filter; + filter = NULL; + } + if ((ks_get_flags & KS_GET_FLAG_NEXT)) + { + /* Keep the state in --next mode even with errors. */ + ldap_conn = NULL; + message = NULL; + } + + if (message) + ldap_msgfree (message); + + if (err) + es_fclose (fp); + else + { + if (fp) + es_fseek (fp, 0, SEEK_SET); + *r_fp = fp; + } + + xfree (basedn); + xfree (host); + + if (ldap_conn) + ldap_unbind (ldap_conn); + + xfree (filter); + + return err; +} diff --git a/dirmngr/ks-engine.h b/dirmngr/ks-engine.h index be4e27e6f..5fbe300c0 100644 --- a/dirmngr/ks-engine.h +++ b/dirmngr/ks-engine.h @@ -27,6 +27,8 @@ #define KS_GET_FLAG_ONLY_LDAP 1 #define KS_GET_FLAG_FIRST 2 #define KS_GET_FLAG_NEXT 4 +#define KS_GET_FLAG_ONLY_AD 8 /* Do this only if we have an AD. */ +#define KS_GET_FLAG_ROOTDSE 16 /* Get the rootDSE. */ /*-- ks-action.c --*/ @@ -78,6 +80,9 @@ gpg_error_t ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, gpg_error_t ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri, void *data, size_t datalen, void *info, size_t infolen); +gpg_error_t ks_ldap_query (ctrl_t ctrl, parsed_uri_t uri, + unsigned int ks_get_flags, + const char *filter, char **attrs, estream_t *r_fp); #endif /*DIRMNGR_KS_ENGINE_H*/ diff --git a/dirmngr/server.c b/dirmngr/server.c index cd0839aad..c93437247 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -146,7 +146,7 @@ get_ldapservers_from_ctrl (ctrl_t ctrl) } /* Release an uri_item_t list. */ -static void +void release_uri_item_list (uri_item_t list) { while (list) @@ -2147,15 +2147,6 @@ cmd_validate (assuan_context_t ctx, char *line) static gpg_error_t make_keyserver_item (const char *uri, uri_item_t *r_item) { - gpg_error_t err; - uri_item_t item; - char *tmpstr = NULL; -#if USE_LDAP - const char *s; -#endif - - *r_item = NULL; - /* We used to have DNS CNAME redirection from the URLs below to * sks-keyserver. pools. The idea was to allow for a quick way to * switch to a different set of pools. The problem with that @@ -2187,78 +2178,7 @@ make_keyserver_item (const char *uri, uri_item_t *r_item) else if (!strcmp (uri, "http://http-keys.gnupg.net")) uri = "hkp://keyserver.ubuntu.com:80"; - item = xtrymalloc (sizeof *item + strlen (uri)); - if (!item) - return gpg_error_from_syserror (); - - item->next = NULL; - item->parsed_uri = NULL; - strcpy (item->uri, uri); - -#if USE_LDAP - if (!strncmp (uri, "ldap:", 5) && !(uri[5] == '/' && uri[6] == '/')) - { - /* Special ldap scheme given. This differs from a valid ldap - * scheme in that no double slash follows.. Use http_parse_uri - * to put it as opaque value into parsed_uri. */ - tmpstr = strconcat ("opaque:", uri+5, NULL); - if (!tmpstr) - err = gpg_error_from_syserror (); - else - err = http_parse_uri (&item->parsed_uri, tmpstr, 0); - } - else if ((s=strchr (uri, ':')) && !(s[1] == '/' && s[2] == '/')) - { - /* No valid scheme given. Use http_parse_uri to put the string - * as opaque value into parsed_uri. */ - tmpstr = strconcat ("opaque:", uri, NULL); - if (!tmpstr) - err = gpg_error_from_syserror (); - else - err = http_parse_uri (&item->parsed_uri, tmpstr, 0); - } - else if (ldap_uri_p (uri)) - { - int fixup = 0; - /* Fixme: We should get rid of that parser and replace it with - * our generic (http) URI parser. */ - - /* If no port has been specified and the scheme ist ldaps we use - * our idea of the default port because the standard LDAP URL - * parser would use 636 here. This is because we redefined - * ldaps to mean starttls. */ -#ifdef HAVE_W32_SYSTEM - if (!strcmp (uri, "ldap:///")) - fixup = 1; - else -#endif - if (!http_parse_uri (&item->parsed_uri,uri,HTTP_PARSE_NO_SCHEME_CHECK)) - { - if (!item->parsed_uri->port - && !strcmp (item->parsed_uri->scheme, "ldaps")) - fixup = 2; - http_release_parsed_uri (item->parsed_uri); - item->parsed_uri = NULL; - } - - err = ldap_parse_uri (&item->parsed_uri, uri); - if (!err && fixup == 1) - item->parsed_uri->ad_current = 1; - else if (!err && fixup == 2) - item->parsed_uri->port = 389; - } - else -#endif /* USE_LDAP */ - { - err = http_parse_uri (&item->parsed_uri, uri, HTTP_PARSE_NO_SCHEME_CHECK); - } - - xfree (tmpstr); - if (err) - xfree (item); - else - *r_item = item; - return err; + return ks_action_parse_uri (uri, r_item); } @@ -2768,6 +2688,86 @@ cmd_ks_put (assuan_context_t ctx, char *line) } + +static const char hlp_ad_query[] = + "AD_QUERY [--first|--next] [--] \n" + "\n" + "Query properties from a Windows Active Directory.\n" + "Our extended filter syntax may be used for the filter\n" + "expression; see gnupg/dirmngr/ldap-misc.c. There are\n" + "a couple of other options available:\n\n" + " --rootdse - Query the root using serverless binding,\n" + " --attr= - Comma delimited list of attributes\n" + " to return.\n" + ; +static gpg_error_t +cmd_ad_query (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + unsigned int flags = 0; + const char *filter; + estream_t outfp = NULL; + char *p; + char **opt_attr = NULL; + + /* No options for now. */ + if (has_option (line, "--first")) + flags |= KS_GET_FLAG_FIRST; + if (has_option (line, "--next")) + flags |= KS_GET_FLAG_NEXT; + if (has_option (line, "--rootdse")) + flags |= KS_GET_FLAG_ROOTDSE; + err = get_option_value (line, "--attr", &p); + if (err) + goto leave; + if (p) + { + opt_attr = strtokenize (p, ","); + if (!opt_attr) + { + err = gpg_error_from_syserror (); + xfree (p); + goto leave; + } + xfree (p); + } + line = skip_options (line); + filter = line; + + if ((flags & KS_GET_FLAG_NEXT)) + { + if (*filter || (flags & ~KS_GET_FLAG_NEXT)) + { + err = PARM_ERROR ("No filter or other options allowed with --next"); + goto leave; + } + } + + /* Setup an output stream and perform the get. */ + outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); + if (!outfp) + { + err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); + goto leave; + } + + ctrl->server_local->inhibit_data_logging = 1; + ctrl->server_local->inhibit_data_logging_now = 0; + ctrl->server_local->inhibit_data_logging_count = 0; + + err = ks_action_query (ctrl, + (flags & KS_GET_FLAG_ROOTDSE)? NULL : "ldap:///", + flags, filter, opt_attr, outfp); + + leave: + es_fclose (outfp); + xfree (opt_attr); + ctrl->server_local->inhibit_data_logging = 0; + return leave_cmd (ctx, err); +} + + static const char hlp_loadswdb[] = "LOADSWDB [--force]\n" @@ -2973,6 +2973,7 @@ register_commands (assuan_context_t ctx) { "KS_GET", cmd_ks_get, hlp_ks_get }, { "KS_FETCH", cmd_ks_fetch, hlp_ks_fetch }, { "KS_PUT", cmd_ks_put, hlp_ks_put }, + { "AD_QUERY", cmd_ad_query, hlp_ad_query }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "LOADSWDB", cmd_loadswdb, hlp_loadswdb }, { "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr }, From 658daae34aa3b2b40e6473d44d41abcf175f1ab2 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 21 Mar 2023 09:15:20 +0100 Subject: [PATCH 42/60] doc: Suggest the use of out-of-source builds. -- GnuPG-bug-id: 6313 --- INSTALL | 28 ++++++++++++++++------------ README | 7 +++++-- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/INSTALL b/INSTALL index 5458714e1..9e9642898 100644 --- a/INSTALL +++ b/INSTALL @@ -42,10 +42,12 @@ may remove or edit it. you want to change it or regenerate `configure' using a newer version of `autoconf'. -The simplest way to compile this package is: +The suggested way to compile this package is: - 1. `cd' to the directory containing the package's source code and type - `./configure' to configure the package for your system. + 1. `cd' to the directory containing the package's source code and + create a new directory named `build'. Then `cd' to that + directory and type `../configure' to configure the package for + your system. Running `configure' might take a while. While running, it prints some messages telling which features it is checking for. @@ -58,14 +60,17 @@ The simplest way to compile this package is: 4. Type `make install' to install the programs and any data files and documentation. - 5. You can remove the program binaries and object files from the - source code directory by typing `make clean'. To also remove the - files that `configure' created (so you can compile the package for - a different kind of computer), type `make distclean'. There is - also a `make maintainer-clean' target, but that is intended mainly - for the package's developers. If you use it, you may have to get - all sorts of other programs in order to regenerate files that came - with the distribution. + 5. You can remove the program binaries and object files by deleting + all files from the `build' directory. In case you did not used a + dedicated build directory but build the software directly in the + source tree, you can remove the program binaries and object files + from the source code directory by typing `make clean'. To also + remove the files that `configure' created (so you can compile the + package for a different kind of computer), type `make distclean'. + There is also a `make maintainer-clean' target, but that is + intended mainly for the package's developers. If you use it, you + may have to get all sorts of other programs in order to + regenerate files that came with the distribution. Compilers and Options ===================== @@ -231,4 +236,3 @@ an Autoconf bug. Until the bug is fixed you can use this workaround: `configure' also accepts some other, not widely useful, options. Run `configure --help' for more details. - diff --git a/README b/README index 42eed238f..b9bf7805e 100644 --- a/README +++ b/README @@ -53,7 +53,9 @@ As with all packages, you just have to do - ./configure + mkdir build + cd build + ../configure make make check make install @@ -81,7 +83,8 @@ To quickly build all required software without installing it, the Speedo method may be used: - make -f build-aux/speedo.mk native + cd build + make -f ../build-aux/speedo.mk native This method downloads all required libraries and does a native build of GnuPG to PLAY/inst/. GNU make is required and you need to set From d09301a9e133eab988a7ea50d94dc754fd71f6a1 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 21 Mar 2023 09:33:05 +0100 Subject: [PATCH 43/60] po: Fix German translation regarding the caching of PINs. -- We do not actually cache PINs (but the card does this). Thus we now use a more clear message. --- po/de.po | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/po/de.po b/po/de.po index b0e3c43cd..0f8fe1d95 100644 --- a/po/de.po +++ b/po/de.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: gnupg-2.3.0\n" "Report-Msgid-Bugs-To: translations@gnupg.org\n" -"PO-Revision-Date: 2022-12-16 16:11+0100\n" +"PO-Revision-Date: 2023-03-21 09:31+0100\n" "Last-Translator: Werner Koch \n" "Language-Team: German \n" "Language: de\n" @@ -404,19 +404,19 @@ msgid "Options controlling the security" msgstr "Optionen zur Einstellung der Sicherheit" msgid "|N|expire cached PINs after N seconds" -msgstr "|N|Lasse PINs im Cache nach N Sekunden verfallen" +msgstr "|N|Lösche unbenutzte Passwörter nach N Sekunden aus dem Cache" msgid "|N|expire SSH keys after N seconds" -msgstr "|N|Lasse SSH Schlüssel im Cache nach N Sekunden verfallen" +msgstr "|N|Lösche unbenutzte SSH Passwörter nach N Sekunden aus dem Cache" msgid "|N|set maximum PIN cache lifetime to N seconds" -msgstr "|N|Setze die maximale Lebensdauer von PINs im Cache auf N Sekunden" +msgstr "|N|Lösche Passwörter nach N Sekunden aus dem Cache" msgid "|N|set maximum SSH key lifetime to N seconds" -msgstr "|N|Setze die maximale Lebenszeit von SSH Schlüsseln auf N Sekunden" +msgstr "|N|Lösche SSH Passwörter nach N Sekunden aus dem Cache" msgid "do not use the PIN cache when signing" -msgstr "Benutze PINs im Cache nicht beim Signieren" +msgstr "Benutze den Passwort Cache nicht beim Signieren" msgid "disallow the use of an external password cache" msgstr "Verbiete die Verwendung eines externen Passwordmanagers" From 9f27e448bf1f825906f3c53e3428087d34bbd8fc Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 21 Mar 2023 16:30:18 +0100 Subject: [PATCH 44/60] gpg: New command --quick-add-adsk * g10/gpg.c (enum cmd_and_opt_values): Add aQuickAddADSK. (opts): Add --quick-add-adsk. (main): Call the actual function. * g10/keyedit.c (keyedit_quick_addadsk): New. (menu_addadsk): Add arg adskfpr and change caller. -- GnuPG-bug-id: 6395 --- doc/gpg.texi | 9 +++++ g10/gpg.c | 15 +++++++ g10/keyedit.c | 109 +++++++++++++++++++++++++++++++++++++++++++------- g10/keyedit.h | 1 + 4 files changed, 120 insertions(+), 14 deletions(-) diff --git a/doc/gpg.texi b/doc/gpg.texi index a6ab4d57d..7a4935fc6 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -770,6 +770,15 @@ specifying a value, or using ``-'' results in a key expiring in a reasonable default interval. The values ``never'', ``none'' can be used for no expiration date. +@item --quick-add-adsk @var{fpr} @var{adskfpr} +@opindex quick-add-adsk +Directly add an Additional Decryption Subkey to the key identified by +the fingerprint @var{fpr}. @var{adskfpr} is the fingerprint of +another key's encryption subkey. A subkey is commonly used here +because by default a primary key has no encryption capability. Use +the option @option{--with-subkey-fingerprint} with a list command to +display the subkey fingerprints. + @item --generate-key @opindex generate-key @itemx --gen-key diff --git a/g10/gpg.c b/g10/gpg.c index 632fbb90f..84706ca6b 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -130,6 +130,7 @@ enum cmd_and_opt_values aQuickRevSig, aQuickAddUid, aQuickAddKey, + aQuickAddADSK, aQuickRevUid, aQuickSetExpire, aQuickSetPrimaryUid, @@ -486,6 +487,7 @@ static gpgrt_opt_t opts[] = { ARGPARSE_c (aQuickAddUid, "quick-adduid", "@"), ARGPARSE_c (aQuickAddKey, "quick-add-key", "@"), ARGPARSE_c (aQuickAddKey, "quick-addkey", "@"), + ARGPARSE_c (aQuickAddADSK, "quick-add-adsk", "@"), ARGPARSE_c (aQuickRevUid, "quick-revoke-uid", N_("quickly revoke a user-id")), ARGPARSE_c (aQuickRevUid, "quick-revuid", "@"), @@ -2691,6 +2693,7 @@ main (int argc, char **argv) case aQuickKeygen: case aQuickAddUid: case aQuickAddKey: + case aQuickAddADSK: case aQuickRevUid: case aQuickSetExpire: case aQuickSetPrimaryUid: @@ -4302,6 +4305,7 @@ main (int argc, char **argv) case aQuickKeygen: case aQuickAddUid: case aQuickAddKey: + case aQuickAddADSK: case aQuickRevUid: case aQuickSetPrimaryUid: case aQuickUpdatePref: @@ -4769,6 +4773,17 @@ main (int argc, char **argv) } break; + case aQuickAddADSK: + { + if (argc != 2) + wrong_args ("--quick-add-adsk FINGERPRINT ADSK-FINGERPRINT"); + if (mopt.forbid_gen_key) + gen_key_forbidden (); + else + keyedit_quick_addadsk (ctrl, argv[0], argv[1]); + } + break; + case aQuickRevUid: { const char *uid, *uidtorev; diff --git a/g10/keyedit.c b/g10/keyedit.c index d21064a21..a91cc4447 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -73,7 +73,8 @@ static int menu_delsig (ctrl_t ctrl, kbnode_t pub_keyblock); static int menu_clean (ctrl_t ctrl, kbnode_t keyblock, int self_only); static void menu_delkey (KBNODE pub_keyblock); static int menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive); -static int menu_addadsk (ctrl_t ctrl, kbnode_t pub_keyblock); +static int menu_addadsk (ctrl_t ctrl, kbnode_t pub_keyblock, + const char *adskfpr); static gpg_error_t menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock, int unattended, u32 newexpiration); static int menu_changeusage (ctrl_t ctrl, kbnode_t keyblock); @@ -1310,7 +1311,7 @@ static struct { "addrevoker", cmdADDREVOKER, KEYEDIT_NEED_SK, N_("add a revocation key")}, { "addadsk", cmdADDADSK, KEYEDIT_NEED_SK, - N_("add additional decryption subkeys")}, + N_("add an additional decryption subkey")}, { "delsig", cmdDELSIG, 0, N_("delete signatures from the selected user IDs")}, { "expire", cmdEXPIRE, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, @@ -2016,7 +2017,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, break; case cmdADDADSK: - if (menu_addadsk (ctrl, keyblock)) + if (menu_addadsk (ctrl, keyblock, NULL)) { redisplay = 1; modified = 1; @@ -3247,6 +3248,69 @@ keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr, } +/* Unattended ADSK setup function. + * + * FPR is the fingerprint of our key. ADSKFPR is the fingerprint of + * another subkey which we want to add as ADSK to our key. + */ +void +keyedit_quick_addadsk (ctrl_t ctrl, const char *fpr, const char *adskfpr) +{ + gpg_error_t err; + kbnode_t keyblock; + KEYDB_HANDLE kdbhd; + int modified = 0; + PKT_public_key *pk; + +#ifdef HAVE_W32_SYSTEM + /* See keyedit_menu for why we need this. */ + check_trustdb_stale (ctrl); +#endif + + /* We require a fingerprint because only this uniquely identifies a + * key and may thus be used to select a key for unattended adsk + * adding. */ + if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd)) + goto leave; + + if (fix_keyblock (ctrl, &keyblock)) + modified++; + + pk = keyblock->pkt->pkt.public_key; + if (pk->flags.revoked) + { + if (!opt.verbose) + show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1); + log_error ("%s%s", _("Key is revoked."), "\n"); + goto leave; + } + + /* Locate and add the ADSK. Note that the called function already + * prints error messages. */ + if (menu_addadsk (ctrl, keyblock, adskfpr)) + modified = 1; + else + log_inc_errorcount (); /* (We use log_info in menu_adsk) */ + + es_fflush (es_stdout); + + /* Store. */ + if (modified) + { + err = keydb_update_keyblock (ctrl, kdbhd, keyblock); + if (err) + { + log_error (_("update failed: %s\n"), gpg_strerror (err)); + goto leave; + } + } + + leave: + release_kbnode (keyblock); + keydb_release (kdbhd); +} + + /* Unattended expiration setting function for the main key. If * SUBKEYFPRS is not NULL and SUBKEYSFPRS[0] is neither NULL, it is * expected to be an array of fingerprints for subkeys to change. It @@ -4691,11 +4755,12 @@ fail: /* * Ask for a new additional decryption subkey and add it to the key - * block. Returns true if the keybloxk was changed and false - * otherwise. + * block. Returns true if the keyblock was changed and false + * otherwise. If ADSKFPR is not NULL, this fucntion has been called + * by quick_addadsk and gives the fingerprint of the to be added key. */ static int -menu_addadsk (ctrl_t ctrl, kbnode_t pub_keyblock) +menu_addadsk (ctrl_t ctrl, kbnode_t pub_keyblock, const char *adskfpr) { PKT_public_key *pk; PKT_public_key *sub_pk; @@ -4718,18 +4783,26 @@ menu_addadsk (ctrl_t ctrl, kbnode_t pub_keyblock) for (;;) { xfree (answer); - answer = cpr_get_utf8 - ("keyedit.addadsk", - _("Enter the fingerprint of the additional decryption subkey: ")); - if (answer[0] == '\0' || answer[0] == CONTROL_D) + if (adskfpr) + answer = xstrdup (adskfpr); + else { - err = gpg_error (GPG_ERR_CANCELED); - goto leave; + answer = cpr_get_utf8 + ("keyedit.addadsk", + _("Enter the fingerprint of the additional decryption subkey: ")); + if (answer[0] == '\0' || answer[0] == CONTROL_D) + { + err = gpg_error (GPG_ERR_CANCELED); + goto leave; + } } if (classify_user_id (answer, &desc, 1) || desc.mode != KEYDB_SEARCH_MODE_FPR) { log_info (_("\"%s\" is not a fingerprint\n"), answer); + err = gpg_error (GPG_ERR_INV_USER_ID); + if (adskfpr) + goto leave; continue; } @@ -4744,8 +4817,11 @@ menu_addadsk (ctrl_t ctrl, kbnode_t pub_keyblock) { log_info (_("key \"%s\" not found: %s\n"), answer, gpg_strerror (err)); - if (!opt.batch && gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY) + if ((!opt.batch || adskfpr) && !opt.quiet + && gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY) log_info (_("Did you specify the fingerprint of a subkey?\n")); + if (adskfpr) + goto leave; continue; } @@ -4767,8 +4843,10 @@ menu_addadsk (ctrl_t ctrl, kbnode_t pub_keyblock) err = gpg_error (GPG_ERR_WRONG_KEY_USAGE); log_info (_("key \"%s\" not found: %s\n"), answer, gpg_strerror (err)); - if (!opt.batch) + if ((!opt.batch || adskfpr) && !opt.quiet) log_info (_("Did you specify the fingerprint of a subkey?\n")); + if (adskfpr) + goto leave; continue; } @@ -4788,6 +4866,9 @@ menu_addadsk (ctrl_t ctrl, kbnode_t pub_keyblock) if (node2) { log_info (_("key \"%s\" is already on this keyblock\n"), answer); + err = gpg_error (GPG_ERR_DUP_KEY); + if (adskfpr) + goto leave; continue; } diff --git a/g10/keyedit.h b/g10/keyedit.h index ea4fd253c..3ed0d0fea 100644 --- a/g10/keyedit.h +++ b/g10/keyedit.h @@ -44,6 +44,7 @@ void keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid); void keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr, const char *usagestr, const char *expirestr); +void keyedit_quick_addadsk (ctrl_t ctrl, const char *fpr, const char *adskfpr); void keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev); void keyedit_quick_sign (ctrl_t ctrl, const char *fpr, From ab35d756d86438db124fa68aa633fe528ff8be50 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 27 Mar 2023 11:37:49 +0200 Subject: [PATCH 45/60] agent: New option --restricted for PRESET_PASSPHRASE. * agent/command.c (cmd_preset_passphrase): Add option. * agent/preset-passphrase.c (oRestricted): New. (opts): Add option --restricted. (main): Set option. (preset_passphrase): Use option. -- We use a different cache for connections from the extra-socket. However, with gpg-preset-passphrase is only able to preset a passphrase into the regular cache. Further, a restricted connection may not use PRESET_PASSPHRASE. To solve this we add an new option to preset the passphrase into the "restricted" cache. For the gpg-preset-passphrase tool we also add the option --restricted. Note that this does not yet work with gpg-preset-passphrase --forget. --- agent/command.c | 13 +++++++++++-- agent/preset-passphrase.c | 9 ++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/agent/command.c b/agent/command.c index 2e996d096..9481f47c3 100644 --- a/agent/command.c +++ b/agent/command.c @@ -2491,14 +2491,17 @@ cmd_passwd (assuan_context_t ctx, char *line) static const char hlp_preset_passphrase[] = - "PRESET_PASSPHRASE [--inquire] []\n" + "PRESET_PASSPHRASE [--inquire] [--restricted] \\\n" + " []\n" "\n" "Set the cached passphrase/PIN for the key identified by the keygrip\n" "to passwd for the given time, where -1 means infinite and 0 means\n" "the default (currently only a timeout of -1 is allowed, which means\n" "to never expire it). If passwd is not provided, ask for it via the\n" "pinentry module unless --inquire is passed in which case the passphrase\n" - "is retrieved from the client via a server inquire.\n"; + "is retrieved from the client via a server inquire. The option\n" + "--restricted can be used to put the passphrase into the cache used\n" + "by restricted connections."; static gpg_error_t cmd_preset_passphrase (assuan_context_t ctx, char *line) { @@ -2509,6 +2512,7 @@ cmd_preset_passphrase (assuan_context_t ctx, char *line) int ttl; size_t len; int opt_inquire; + int opt_restricted; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); @@ -2517,6 +2521,7 @@ cmd_preset_passphrase (assuan_context_t ctx, char *line) return set_error (GPG_ERR_NOT_SUPPORTED, "no --allow-preset-passphrase"); opt_inquire = has_option (line, "--inquire"); + opt_restricted = has_option (line, "--restricted"); line = skip_options (line); grip_clear = line; while (*line && (*line != ' ' && *line != '\t')) @@ -2579,7 +2584,11 @@ cmd_preset_passphrase (assuan_context_t ctx, char *line) if (!rc) { + int save_restricted = ctrl->restricted; + if (opt_restricted) + ctrl->restricted = 1; rc = agent_put_cache (ctrl, grip_clear, CACHE_MODE_ANY, passphrase, ttl); + ctrl->restricted = save_restricted; if (opt_inquire) { wipememory (passphrase, len); diff --git a/agent/preset-passphrase.c b/agent/preset-passphrase.c index df6da00e3..4cf624462 100644 --- a/agent/preset-passphrase.c +++ b/agent/preset-passphrase.c @@ -63,11 +63,13 @@ enum cmd_and_opt_values oNoVerbose = 500, oHomedir, + oRestricted, aTest }; static const char *opt_passphrase; +static int opt_restricted; static gpgrt_opt_t opts[] = { @@ -79,6 +81,7 @@ static gpgrt_opt_t opts[] = { { oForget, "forget", 256, "forget passphrase"}, { oHomedir, "homedir", 2, "@" }, + { oRestricted, "restricted", 0, "put into the restricted cache"}, ARGPARSE_end () }; @@ -156,7 +159,9 @@ preset_passphrase (const char *keygrip) return; } - rc = asprintf (&line, "PRESET_PASSPHRASE %s -1 %s\n", keygrip, + rc = asprintf (&line, "PRESET_PASSPHRASE %s%s -1 %s\n", + opt_restricted? "--restricted ":"", + keygrip, passphrase_esc); wipememory (passphrase_esc, strlen (passphrase_esc)); xfree (passphrase_esc); @@ -232,6 +237,8 @@ main (int argc, char **argv) case oForget: cmd = oForget; break; case oPassphrase: opt_passphrase = pargs.r.ret_str; break; + case oRestricted: opt_restricted = 1; break; + default : pargs.err = 2; break; } } From 4b9346492e3f66caf73c9128ad15c614ae74b1bf Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 31 Mar 2023 09:31:02 +0200 Subject: [PATCH 46/60] gpgsm: Remove conditional compilation for older libksba versions. * sm/sign.c (add_signed_attribute): Return an error because the option --attribute is not yet working. -- We require libskba 1.6 anyway. --- doc/DETAILS | 25 +++++++++++++++++++++++++ doc/gpg.texi | 2 +- sm/decrypt.c | 8 -------- sm/sign.c | 15 +++++---------- 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/doc/DETAILS b/doc/DETAILS index 4c1e9b67c..eee640a01 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -1695,6 +1695,7 @@ Description of some debug flags: - RFC-1750 :: Randomness Recommendations for Security - RFC-1991 :: PGP Message Exchange Formats (obsolete) - RFC-2144 :: The CAST-128 Encryption Algorithm + - RFC-2253 :: UTF-8 String Representation of Distinguished Names. - RFC-2279 :: UTF-8, a transformation format of ISO 10646 - RFC-2440 :: OpenPGP (obsolete). - RFC-3156 :: MIME Security with Pretty Good Privacy (PGP). @@ -1813,3 +1814,27 @@ Description of some debug flags: it is also possible to set them direct: Use a "=" character directly followed by a combination of "a" (for authentication), "s" (for signing), or "c" (for certification). + +** extendedKeyUsage and keyUsage in gpgsm + +This table describes how the extended KeyUsage masks the KeyUsage. + + | ExtKeyUsage | Valid KeyUsages | + |-----------------+------------------| + | serverAuth | digitalSignature | + | | keyEncipherment | + | | keyAgreement | + |-----------------+------------------| + | clientAuth | digitalSignature | + | | keyAgreement | + |-----------------+------------------| + | codeSigning | digitalSignature | + |-----------------+------------------| + | emailProtection | digitalSignature | + | | nonRepudiation | + | | keyEncipherment | + | | keyAgreement | + |-----------------+------------------| + | timeStamping | digitalSignature | + | | nonRepudiation | + |-----------------+------------------| diff --git a/doc/gpg.texi b/doc/gpg.texi index 7a4935fc6..393267858 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -622,7 +622,7 @@ outputs an endless stream of hex-encoded octets. The special level @item --gen-prime @var{mode} @var{bits} @opindex gen-prime Use the source, Luke :-). The output format is subject to change -with ant release. +with any release. @item --enarmor diff --git a/sm/decrypt.c b/sm/decrypt.c index 3702cd893..68b362b45 100644 --- a/sm/decrypt.c +++ b/sm/decrypt.c @@ -37,14 +37,6 @@ #include "../common/tlv.h" #include "../common/compliance.h" -/* We can provide an enum value which is only availabale with KSBA - * 1.6.0 so that we can compile even against older versions. Some - * calls will of course return an error in this case. This value is - * currently not used because the cipher mode is sufficient here. */ -/* #if KSBA_VERSION_NUMBER < 0x010600 /\* 1.6.0 *\/ */ -/* # define KSBA_CT_AUTHENVELOPED_DATA 10 */ -/* #endif */ - struct decrypt_filter_parm_s { diff --git a/sm/sign.c b/sm/sign.c index 9290fc17b..d584433d7 100644 --- a/sm/sign.c +++ b/sm/sign.c @@ -300,7 +300,6 @@ add_certificate_list (ctrl_t ctrl, ksba_cms_t cms, ksba_cert_t cert) } -#if KSBA_VERSION_NUMBER >= 0x010400 && 0 /* 1.4.0 */ static gpg_error_t add_signed_attribute (ksba_cms_t cms, const char *attrstr) { @@ -378,7 +377,12 @@ add_signed_attribute (ksba_cms_t cms, const char *attrstr) } /* Store the data in the CMS object for all signers. */ +#if 0 err = ksba_cms_add_attribute (cms, -1, fields[0], 0, der, derlen); +#else + (void)cms; + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif if (err) { log_error ("invalid attribute specification '%s': %s\n", @@ -391,7 +395,6 @@ add_signed_attribute (ksba_cms_t cms, const char *attrstr) xfree (fields); return err; } -#endif /*ksba >= 1.4.0 */ @@ -474,9 +477,7 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, if (!err) err = ksba_cms_set_content_type (cms, 1, -#if KSBA_VERSION_NUMBER >= 0x010400 && 0 opt.authenticode? KSBA_CT_SPC_IND_DATA_CTX : -#endif KSBA_CT_DATA ); if (err) @@ -758,8 +759,6 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, } } - /* We can add signed attributes only when build against libksba 1.4. */ -#if KSBA_VERSION_NUMBER >= 0x010400 && 0 /* 1.4.0 */ { strlist_t sl; @@ -767,10 +766,6 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, if ((err = add_signed_attribute (cms, sl->d))) goto leave; } -#else - if (opt.attributes) - log_info ("Note: option --attribute is ignored by this version\n"); -#endif /*ksba >= 1.4.0 */ /* We need to write at least a minimal list of our capabilities to From 8996b0b655952fa6b5bb678a92d3106f72f80f2a Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 3 Apr 2023 12:01:37 +0200 Subject: [PATCH 47/60] gpgsm: Create binary detached sigs with definite form length octets. * sm/sign.c: Include tlv.h. (write_detached_signature): New, (gpgsm_sign): Fixup binary detached signatures. -- This helps some other software to verify detached signatures. --- NEWS | 16 ++-- sm/sign.c | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 262 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index 340742c20..5eb609987 100644 --- a/NEWS +++ b/NEWS @@ -1,15 +1,21 @@ Noteworthy changes in version 2.4.1 (unreleased) ------------------------------------------------ - * gpg: New option --add-desig-revoker. + * gpg: New option --add-desig-revoker. [rG3d094e2bcf] - * gpg: New list-option "show-unusable-sigs". + * gpg: New list-option "show-unusable-sigs". Also show + "[self-signature]" instead of the user-id in key signature + listings. [rG103acfe9ca] - * gpg: Show "[self-signature]" instead of the user-id in key - signature listings. + * gpgsm: Verification of detached signatures does now strip trailing + zeroes from the input if --assume-binary is used. [rG2a13f7f9dc] + + * gpgsm: Non-armored detached signature are now created without + using indefinite form length octets. This improves compatibility + with some PDF signature verification software. * gpg: Make list-options "show-sig-subpackets" work again. - Fixes regression in 2.4.0. + Fixes regression in 2.4.0. [rG5a223303d7] diff --git a/sm/sign.c b/sm/sign.c index d584433d7..64bbf8d56 100644 --- a/sm/sign.c +++ b/sm/sign.c @@ -1,6 +1,8 @@ /* sign.c - Sign a message * Copyright (C) 2001, 2002, 2003, 2008, * 2010 Free Software Foundation, Inc. + * Copyright (C) 2003-2012, 2016-2017, 2019, + * 2020, 2022-2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -16,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 @@ -32,6 +35,7 @@ #include "keydb.h" #include "../common/i18n.h" +#include "../common/tlv.h" /* Hash the data and return if something was hashed. Return -1 on error. */ @@ -397,6 +401,216 @@ add_signed_attribute (ksba_cms_t cms, const char *attrstr) } + +/* This function takes a binary detached signature in (BLOB,BLOBLEN) + * and writes it to OUT_FP. The core of the function is to replace + * NDEF length sequences in the input to those with fixed inputs. + * This helps certain other implementations to properly verify + * detached signature. Moreover, it allows our own trailing zero + * stripping code - which we need for PDF signatures - to work + * correctly. + * + * Example start of a detached signature as created by us: + * 0 NDEF: SEQUENCE { -- 1st sequence + * 2 9: OBJECT IDENTIFIER signedData (1 2 840 113549 1 7 2) + * 13 NDEF: [0] { -- 2nd sequence + * 15 NDEF: SEQUENCE { -- 3rd sequence + * 17 1: INTEGER 1 -- version + * 20 15: SET { -- set of algorithms + * 22 13: SEQUENCE { + * 24 9: OBJECT IDENTIFIER sha-256 (2 16 840 1 101 3 4 2 1) + * 35 0: NULL + * : } + * : } + * 37 NDEF: SEQUENCE { -- 4th pretty short sequence + * 39 9: OBJECT IDENTIFIER data (1 2 840 113549 1 7 1) + * : } + * 52 869: [0] { + * Our goal is to replace the NDEF by fixed length tags. + */ +static gpg_error_t +write_detached_signature (ctrl_t ctrl, const void *blob, size_t bloblen, + estream_t out_fp) +{ + gpg_error_t err; + const unsigned char *p, *psave; + size_t n, nsave, objlen, objlensave, hdrlen; + int class, tag, cons, ndef; + const unsigned char *p_ctoid, *p_version, *p_algoset, *p_dataoid; + size_t n_ctoid, n_version, n_algoset, n_dataoid; + const unsigned char *p_certset, *p_signerinfos; + size_t n_certset, n_signerinfos; + int i; + ksba_der_t dbld; + unsigned char *finalder = NULL; + size_t finalderlen; + + p = blob; + n = bloblen; + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No 1st sequence. */ + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !cons)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No signedData OID. */ + if (objlen > n) + return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ + p_ctoid = p; + n_ctoid = objlen; + p += objlen; + n -= objlen; + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_CONTEXT && tag == 0 && cons)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No 2nd sequence. */ + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No 3rd sequence. */ + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_INTEGER)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No version. */ + if (objlen > n) + return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ + p_version = p; + n_version = objlen; + p += objlen; + n -= objlen; + + p_algoset = p; + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_SET && cons && !ndef)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No set of algorithms. */ + if (objlen > n) + return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ + n_algoset = hdrlen + objlen; + p += objlen; + n -= objlen; + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No 4th sequence. */ + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !cons)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No data OID. */ + if (objlen > n) + return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ + p_dataoid = p; + n_dataoid = objlen; + p += objlen; + n -= objlen; + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_NONE && !cons && !objlen)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No End tag. */ + + /* certificates [0] IMPLICIT CertificateSet OPTIONAL, + * Note: We ignore the following + * crls [1] IMPLICIT CertificateRevocationLists OPTIONAL + * because gpgsm does not create them. */ + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (class == CLASS_CONTEXT && tag == 0 && cons) + { + if (objlen > n) + return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ + p_certset = p; + n_certset = objlen; + p += objlen; + n -= objlen; + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef, + &objlen,&hdrlen))) + return err; + } + else + { + p_certset = NULL; + n_certset = 0; + } + + /* SignerInfos ::= SET OF SignerInfo */ + if (!(class == CLASS_UNIVERSAL && tag == TAG_SET && cons && !ndef)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No set of signerInfos. */ + if (objlen > n) + return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ + p_signerinfos = p; + n_signerinfos = objlen; + p += objlen; + n -= objlen; + + /* For the fun of it check the 3 end tags. */ + for (i=0; i < 3; i++) + { + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef, + &objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_NONE && !cons && !objlen)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No End tag. */ + } + if (n) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* Garbage */ + + /*---- From here on we jump to leave on error. ----*/ + + /* Now create a new object from the collected data. */ + dbld = ksba_der_builder_new (16); /* (pre-allocate 16 items) */ + if (!dbld) + { + err = gpg_error_from_syserror (); + goto leave; + } + ksba_der_add_tag (dbld, 0, KSBA_TYPE_SEQUENCE); + ksba_der_add_val ( dbld, 0, KSBA_TYPE_OBJECT_ID, p_ctoid, n_ctoid); + ksba_der_add_tag ( dbld, KSBA_CLASS_CONTEXT, 0); + ksba_der_add_tag ( dbld, 0, KSBA_TYPE_SEQUENCE); + ksba_der_add_val ( dbld, 0, KSBA_TYPE_INTEGER, p_version, n_version); + ksba_der_add_der ( dbld, p_algoset, n_algoset); + ksba_der_add_tag ( dbld, 0, KSBA_TYPE_SEQUENCE); + ksba_der_add_val ( dbld, 0, KSBA_TYPE_OBJECT_ID, p_dataoid, n_dataoid); + ksba_der_add_end ( dbld); + if (p_certset) + { + ksba_der_add_tag ( dbld, KSBA_CLASS_CONTEXT, 0); + ksba_der_add_der ( dbld, p_certset, n_certset); + ksba_der_add_end ( dbld); + } + ksba_der_add_tag ( dbld, 0, KSBA_TYPE_SET); + ksba_der_add_der ( dbld, p_signerinfos, n_signerinfos); + ksba_der_add_end ( dbld); + ksba_der_add_end ( dbld); + ksba_der_add_end ( dbld); + ksba_der_add_end (dbld); + + err = ksba_der_builder_get (dbld, &finalder, &finalderlen); + if (err) + goto leave; + + if (es_fwrite (finalder, finalderlen, 1, out_fp) != 1) + { + err = gpg_error_from_syserror (); + goto leave; + } + + + leave: + ksba_der_release (dbld); + ksba_free (finalder); + return err; +} + + /* Perform a sign operation. @@ -412,6 +626,7 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, gpg_error_t err; gnupg_ksba_io_t b64writer = NULL; ksba_writer_t writer; + estream_t sig_fp = NULL; /* Used for detached signatures. */ ksba_cms_t cms = NULL; ksba_stop_reason_t stopreason; KEYDB_HANDLE kh = NULL; @@ -422,6 +637,7 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, ksba_isotime_t signed_at; certlist_t cl; int release_signerlist = 0; + int binary_detached = detached && !ctrl->create_pem && !ctrl->create_base64; audit_set_type (ctrl->audit, AUDIT_TYPE_SIGN); @@ -444,11 +660,25 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, goto leave; } + /* Note that in detached mode the b64 write is actually a binary + * writer because we need to fixup the created signature later. + * Note that we do this only for binary output because we have no + * PEM writer interface outside of the ksba create writer code. */ ctrl->pem_name = "SIGNED MESSAGE"; - rc = gnupg_ksba_create_writer - (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) - | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), - ctrl->pem_name, out_fp, &writer); + if (binary_detached) + { + sig_fp = es_fopenmem (0, "w+"); + rc = sig_fp? 0 : gpg_error_from_syserror (); + if (!rc) + rc = gnupg_ksba_create_writer (&b64writer, 0, NULL, sig_fp, &writer); + } + else + { + rc = gnupg_ksba_create_writer + (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) + | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), + ctrl->pem_name, out_fp, &writer); + } if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); @@ -944,6 +1174,22 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, goto leave; } + if (binary_detached) + { + void *blob = NULL; + size_t bloblen; + + rc = es_fclose_snatch (sig_fp, &blob, &bloblen); + sig_fp = NULL; + if (rc) + goto leave; + rc = write_detached_signature (ctrl, blob, bloblen, out_fp); + xfree (blob); + if (rc) + goto leave; + } + + audit_log (ctrl->audit, AUDIT_SIGNING_DONE); log_info ("signature created\n"); @@ -957,5 +1203,6 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, gnupg_ksba_destroy_writer (b64writer); keydb_release (kh); gcry_md_close (data_md); + es_fclose (sig_fp); return rc; } From a5360ae4c7bfe6df6754409d5bd5c5a521ae5e6f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 3 Apr 2023 14:06:36 +0200 Subject: [PATCH 48/60] agent: Add trustlist flag "de-vs". * agent/trustlist.c (struct trustitem_s): Add field de_vs. (read_one_trustfile): Parse it. (istrusted_internal): Emit TRUSTLISTFLAG status line. * sm/gpgsm.h (struct rootca_flags_s): Add field de_vs. * sm/call-agent.c (istrusted_status_cb): Detect the flags. * sm/sign.c (write_detached_signature): Remove unused vars. -- Right now this flag has no effect; we first need to specify the exact behaviour. GnuPG-bug-id: 5079 --- agent/trustlist.c | 8 +++++++- doc/gpg-agent.texi | 5 +++++ sm/call-agent.c | 2 ++ sm/gpgsm.h | 1 + sm/sign.c | 6 ++++-- 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/agent/trustlist.c b/agent/trustlist.c index 4d23eb1b0..330f233b8 100644 --- a/agent/trustlist.c +++ b/agent/trustlist.c @@ -45,6 +45,7 @@ struct trustitem_s constraints. */ int cm:1; /* Use chain model for validation. */ int qual:1; /* Root CA for qualified signatures. */ + int de_vs:1; /* Root CA for de-vs compliant PKI. */ } flags; unsigned char fpr[20]; /* The binary fingerprint. */ }; @@ -324,6 +325,8 @@ read_one_trustfile (const char *fname, int systrust, ti->flags.cm = 1; else if (n == 4 && !memcmp (p, "qual", 4) && systrust) ti->flags.qual = 1; + else if (n == 4 && !memcmp (p, "de-vs", 4) && systrust) + ti->flags.de_vs = 1; else log_error ("flag '%.*s' in '%s', line %d ignored\n", n, p, fname, lnr); @@ -476,7 +479,8 @@ istrusted_internal (ctrl_t ctrl, const char *fpr, int *r_disabled, in a locked state. */ if (already_locked) ; - else if (ti->flags.relax || ti->flags.cm || ti->flags.qual) + else if (ti->flags.relax || ti->flags.cm || ti->flags.qual + || ti->flags.de_vs) { unlock_trusttable (); locked = 0; @@ -487,6 +491,8 @@ istrusted_internal (ctrl_t ctrl, const char *fpr, int *r_disabled, err = agent_write_status (ctrl,"TRUSTLISTFLAG", "cm", NULL); if (!err && ti->flags.qual) err = agent_write_status (ctrl,"TRUSTLISTFLAG", "qual",NULL); + if (!err && ti->flags.de_vs) + err = agent_write_status (ctrl,"TRUSTLISTFLAG", "de-vs",NULL); } if (!err) diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index 1a03de010..c8080c7c2 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -813,6 +813,11 @@ This flag has an effect only if used in the global list. This is now the preferred way to mark such CA; the old way of having a separate file @file{qualified.txt} is still supported. +@item de-vs +The CA is part of an approved PKI for the German classification level +VS-NfD. It is only valid in the global trustlist. As of now this is +used only for documentation purpose. + @end table diff --git a/sm/call-agent.c b/sm/call-agent.c index 06319cf62..698039504 100644 --- a/sm/call-agent.c +++ b/sm/call-agent.c @@ -890,6 +890,8 @@ istrusted_status_cb (void *opaque, const char *line) flags->chain_model = 1; else if (has_leading_keyword (line, "qual")) flags->qualified = 1; + else if (has_leading_keyword (line, "de-vs")) + flags->de_vs = 1; } return 0; } diff --git a/sm/gpgsm.h b/sm/gpgsm.h index 6149b8491..cef39ff2a 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -295,6 +295,7 @@ struct rootca_flags_s unsigned int relax:1; /* Relax checking of root certificates. */ unsigned int chain_model:1; /* Root requires the use of the chain model. */ unsigned int qualified:1; /* Root CA used for qualfied signatures. */ + unsigned int de_vs:1; /* Root CA is de-vs compliant. */ }; diff --git a/sm/sign.c b/sm/sign.c index 64bbf8d56..b3b7e1883 100644 --- a/sm/sign.c +++ b/sm/sign.c @@ -433,8 +433,8 @@ write_detached_signature (ctrl_t ctrl, const void *blob, size_t bloblen, estream_t out_fp) { gpg_error_t err; - const unsigned char *p, *psave; - size_t n, nsave, objlen, objlensave, hdrlen; + const unsigned char *p; + size_t n, objlen, hdrlen; int class, tag, cons, ndef; const unsigned char *p_ctoid, *p_version, *p_algoset, *p_dataoid; size_t n_ctoid, n_version, n_algoset, n_dataoid; @@ -445,6 +445,8 @@ write_detached_signature (ctrl_t ctrl, const void *blob, size_t bloblen, unsigned char *finalder = NULL; size_t finalderlen; + (void)ctrl; + p = blob; n = bloblen; if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) From 56d309133f0e54ac5e2f95871fb74f8cb97e2636 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 4 Apr 2023 08:49:55 +0200 Subject: [PATCH 49/60] dirmngr: Return modifyTimestamp and add server option --newer. * dirmngr/server.c (cmd_ks_get): Add option --newer. (cmd_ad_query): Ditto. * dirmngr/ldap-misc.c (isotime2rfc4517): New. (rfc4517toisotime): New. * dirmngr/ks-action.c (ks_action_get): Add arg newer and pass on. (ks_action_query): Ditto. * dirmngr/ks-engine-ldap.c (extract_keys): Print new "chg" record. (ks_ldap_get): Add arg newer. Modify filter with newer arg. (ks_ldap_search): Print the modifyTimestamp. (ks_ldap_query): Add arg newer. Modify filter with newer arg. -- Note that the modifyTimestamp is also available on Windows, where its value is more commonly known as whenChanged. Both are constructed attributes. Note that the --newer option is a bit of a misnomer because LDAP has only a greater-or-equal and no greater-than operator. --- dirmngr/ks-action.c | 10 +++-- dirmngr/ks-action.h | 5 ++- dirmngr/ks-engine-ldap.c | 80 ++++++++++++++++++++++++++++++------ dirmngr/ks-engine.h | 5 ++- dirmngr/ldap-misc.c | 87 ++++++++++++++++++++++++++++++++++++++++ dirmngr/ldap-misc.h | 2 + dirmngr/server.c | 33 ++++++++++++--- 7 files changed, 195 insertions(+), 27 deletions(-) diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c index 6b5f78f49..002f1a7a5 100644 --- a/dirmngr/ks-action.c +++ b/dirmngr/ks-action.c @@ -337,7 +337,8 @@ ks_action_search (ctrl_t ctrl, uri_item_t keyservers, keyservers and write the result to the provided output stream. */ gpg_error_t ks_action_get (ctrl_t ctrl, uri_item_t keyservers, - strlist_t patterns, unsigned int ks_get_flags, estream_t outfp) + strlist_t patterns, unsigned int ks_get_flags, + gnupg_isotime_t newer, estream_t outfp) { gpg_error_t err = 0; gpg_error_t first_err = 0; @@ -382,7 +383,7 @@ ks_action_get (ctrl_t ctrl, uri_item_t keyservers, #if USE_LDAP if (is_ldap) err = ks_ldap_get (ctrl, uri->parsed_uri, sl->d, ks_get_flags, - &infp); + newer, &infp); else #endif if (is_hkp_s) @@ -549,7 +550,8 @@ ks_action_put (ctrl_t ctrl, uri_item_t keyservers, * the filter expression FILTER. Write the result to OUTFP. */ gpg_error_t ks_action_query (ctrl_t ctrl, const char *url, unsigned int ks_get_flags, - const char *filter, char **attrs, estream_t outfp) + const char *filter, char **attrs, + gnupg_isotime_t newer, estream_t outfp) { #if USE_LDAP gpg_error_t err; @@ -576,7 +578,7 @@ ks_action_query (ctrl_t ctrl, const char *url, unsigned int ks_get_flags, || puri->parsed_uri->opaque) { err = ks_ldap_query (ctrl, puri->parsed_uri, ks_get_flags, filter, - attrs, &infp); + attrs, newer, &infp); if (!err) err = copy_stream (infp, outfp); } diff --git a/dirmngr/ks-action.h b/dirmngr/ks-action.h index f7d731d7a..223aae2da 100644 --- a/dirmngr/ks-action.h +++ b/dirmngr/ks-action.h @@ -28,14 +28,15 @@ gpg_error_t ks_action_search (ctrl_t ctrl, uri_item_t keyservers, strlist_t patterns, estream_t outfp); gpg_error_t ks_action_get (ctrl_t ctrl, uri_item_t keyservers, strlist_t patterns, unsigned int ks_get_flags, - estream_t outfp); + gnupg_isotime_t newer, estream_t outfp); gpg_error_t ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp); gpg_error_t ks_action_put (ctrl_t ctrl, uri_item_t keyservers, void *data, size_t datalen, void *info, size_t infolen); gpg_error_t ks_action_query (ctrl_t ctrl, const char *ldapserver, unsigned int ks_get_flags, - const char *filter, char **attr, estream_t outfp); + const char *filter, char **attr, + gnupg_isotime_t newer, estream_t outfp); #endif /*DIRMNGR_KS_ACTION_H*/ diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c index a056b1163..1ffd30ecb 100644 --- a/dirmngr/ks-engine-ldap.c +++ b/dirmngr/ks-engine-ldap.c @@ -1004,6 +1004,15 @@ extract_keys (estream_t output, } my_ldap_value_free (vals); + vals = ldap_get_values (ldap_conn, message, "modifyTimestamp"); + if (vals && vals[0]) + { + gnupg_isotime_t atime; + if (!rfc4517toisotime (atime, vals[0])) + es_fprintf (output, "chg:%s:\n", atime); + } + my_ldap_value_free (vals); + es_fprintf (output, "INFO %s END\n", certid); } @@ -1368,7 +1377,7 @@ fetch_rootdse (ctrl_t ctrl, parsed_uri_t uri) || puri->parsed_uri->opaque) { err = ks_ldap_query (ctrl, puri->parsed_uri, KS_GET_FLAG_ROOTDSE, - "^&base&(objectclass=*)", NULL, &infp); + "^&base&(objectclass=*)", NULL, NULL, &infp); if (err) log_error ("ldap: reading the rootDES failed: %s\n", gpg_strerror (err)); @@ -1417,7 +1426,7 @@ basedn_from_rootdse (ctrl_t ctrl, parsed_uri_t uri) * data. KS_GET_FLAGS conveys flags from the client. */ gpg_error_t ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, - unsigned int ks_get_flags, estream_t *r_fp) + unsigned int ks_get_flags, gnupg_isotime_t newer, estream_t *r_fp) { gpg_error_t err; unsigned int serverinfo; @@ -1442,7 +1451,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, { "dummy", /* (to be be replaced.) */ "pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled", - "pgpkeycreatetime", "modifytimestamp", "pgpkeysize", "pgpkeytype", + "pgpkeycreatetime", "modifyTimestamp", "pgpkeysize", "pgpkeytype", "gpgfingerprint", NULL }; @@ -1542,6 +1551,28 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, if (err) goto leave; + if (*newer) + { + char *tstr, *fstr; + + tstr = isotime2rfc4517 (newer); + if (!tstr) + { + err = gpg_error_from_syserror (); + goto leave; + } + fstr = strconcat ("(&", filter, + "(modifyTimestamp>=", tstr, "))", NULL); + xfree (tstr); + if (!fstr) + { + err = gpg_error_from_syserror (); + goto leave; + } + xfree (filter); + filter = fstr; + } + if (opt.debug) log_debug ("ks-ldap: using filter: %s\n", filter); @@ -1697,7 +1728,7 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, char *attrs[] = { "pgpcertid", "pgpuserid", "pgprevoked", "pgpdisabled", - "pgpkeycreatetime", "pgpkeyexpiretime", "modifytimestamp", + "pgpkeycreatetime", "pgpkeyexpiretime", "modifyTimestamp", "pgpkeysize", "pgpkeytype", "gpgfingerprint", NULL }; @@ -1851,19 +1882,17 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, } my_ldap_value_free (vals); -#if 0 - /* This is not yet specified in the keyserver - protocol, but may be someday. */ es_fputc (':', fp); - vals = ldap_get_values (ldap_conn, each, "modifytimestamp"); - if(vals && vals[0] strlen (vals[0]) == 15) + vals = ldap_get_values (ldap_conn, each, "modifyTimestamp"); + if(vals && vals[0]) { - es_fprintf (fp, "%u", - (unsigned int) ldap2epochtime (vals[0])); + gnupg_isotime_t atime; + if (rfc4517toisotime (atime, vals[0])) + *atime = 0; + es_fprintf (fp, "%s", atime); } my_ldap_value_free (vals); -#endif es_fprintf (fp, "\n"); @@ -2785,7 +2814,8 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri, * return or NULL for all. */ gpg_error_t ks_ldap_query (ctrl_t ctrl, parsed_uri_t uri, unsigned int ks_get_flags, - const char *filter_arg, char **attrs, estream_t *r_fp) + const char *filter_arg, char **attrs, + gnupg_isotime_t newer, estream_t *r_fp) { gpg_error_t err; unsigned int serverinfo; @@ -2823,6 +2853,30 @@ ks_ldap_query (ctrl_t ctrl, parsed_uri_t uri, unsigned int ks_get_flags, err = ldap_parse_extfilter (filter_arg, 0, &basedn, &scope, &filter); if (err) goto leave; + if (newer && *newer) + { + char *tstr, *fstr; + + tstr = isotime2rfc4517 (newer); + if (!tstr) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (filter && *filter) + fstr = strconcat ("(&", filter, + "(modifyTimestamp>=", tstr, "))", NULL); + else + fstr = strconcat ("(modifyTimestamp>=", tstr, ")", NULL); + xfree (tstr); + if (!fstr) + { + err = gpg_error_from_syserror (); + goto leave; + } + xfree (filter); + filter = fstr; + } } diff --git a/dirmngr/ks-engine.h b/dirmngr/ks-engine.h index 5fbe300c0..03588a4d3 100644 --- a/dirmngr/ks-engine.h +++ b/dirmngr/ks-engine.h @@ -76,13 +76,14 @@ gpg_error_t ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, estream_t *r_fp); gpg_error_t ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, unsigned int ks_get_flags, - estream_t *r_fp); + gnupg_isotime_t newer, estream_t *r_fp); gpg_error_t ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri, void *data, size_t datalen, void *info, size_t infolen); gpg_error_t ks_ldap_query (ctrl_t ctrl, parsed_uri_t uri, unsigned int ks_get_flags, - const char *filter, char **attrs, estream_t *r_fp); + const char *filter, char **attrs, + gnupg_isotime_t newer, estream_t *r_fp); #endif /*DIRMNGR_KS_ENGINE_H*/ diff --git a/dirmngr/ldap-misc.c b/dirmngr/ldap-misc.c index 90f1d1f3c..6b0939a3b 100644 --- a/dirmngr/ldap-misc.c +++ b/dirmngr/ldap-misc.c @@ -332,3 +332,90 @@ ldap_parse_extfilter (const char *string, int silent, } return err; } + + + +/* Scan an ISO timestamp and return a Generalized Time according to + * RFC-4517. The only supported format is "yyyymmddThhmmss[Z]" + * delimited by white space, nul, a colon or a comma. Returns a + * malloced string or NULL for an invalid string or on memory + * error. */ +char * +isotime2rfc4517 (const char *string) +{ + int year, month, day, hour, minu, sec; + + if (!isotime_p (string)) + { + errno = 0; + return NULL; + } + + year = atoi_4 (string); + month = atoi_2 (string + 4); + day = atoi_2 (string + 6); + hour = atoi_2 (string + 9); + minu = atoi_2 (string + 11); + sec = atoi_2 (string + 13); + + /* Basic checks (1600 due to the LDAP time format base) */ + if (year < 1600 || month < 1 || month > 12 || day < 1 || day > 31 + || hour > 23 || minu > 59 || sec > 61 ) + { + errno = 0; + return NULL; + } + + return gpgrt_bsprintf ("%04d%02d%02d%02d%02d%02d.0Z", + year, month, day, hour, minu, sec); +} + + +/* Parse an LDAP Generalized Time string and update the provided + * isotime buffer. On error return and error code. */ +gpg_error_t +rfc4517toisotime (gnupg_isotime_t timebuf, const char *string) +{ + int i; + int year, month, day, hour, minu, sec; + const char *s; + + 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); + minu = 0; + sec = 0; + if (digitp (s) && digitp (s+1)) + { + minu = atoi_2 (s); + s += 2; + if (digitp (s) && digitp (s+1)) + { + sec = atoi_2 (s); + s += 2; + } + } + if (*s == '.' || *s == ',') + { + s++; + if (!digitp (s)) /* At least one digit of the fraction required. */ + return gpg_error (GPG_ERR_INV_TIME); + s++; + while (digitp (s)) + s++; + } + if (*s == 'Z' && (!s[1] || spacep (s+1))) + ; /* stop here. */ + else if (*s == '-' || *s == '+') + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ + else + return gpg_error (GPG_ERR_INV_TIME); + + snprintf (timebuf, sizeof (gnupg_isotime_t), "%04d%02d%02dT%02d%02d%02d", + year, month, day, hour, minu, sec); + return 0; +} diff --git a/dirmngr/ldap-misc.h b/dirmngr/ldap-misc.h index d555caf49..03efe5fa7 100644 --- a/dirmngr/ldap-misc.h +++ b/dirmngr/ldap-misc.h @@ -38,6 +38,8 @@ gpg_err_code_t ldap_err_to_gpg_err (int code); gpg_err_code_t ldap_to_gpg_err (LDAP *ld); gpg_error_t ldap_parse_extfilter (const char *string, int silent, char **r_base, int *r_scope, char **r_filter); +char *isotime2rfc4517 (const char *string); +gpg_error_t rfc4517toisotime (gnupg_isotime_t timebuf, const char *string); #endif /*DIRMNGR_LDAP_MISC_H*/ diff --git a/dirmngr/server.c b/dirmngr/server.c index c93437247..2c5a41b07 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -2461,22 +2461,28 @@ cmd_ks_search (assuan_context_t ctx, char *line) static const char hlp_ks_get[] = - "KS_GET [--quick] [--ldap] [--first|--next] {}\n" + "KS_GET [--quick] [--newer=TIME] [--ldap] [--first|--next] {}\n" "\n" "Get the keys matching PATTERN from the configured OpenPGP keyservers\n" "(see command KEYSERVER). Each pattern should be a keyid, a fingerprint,\n" "or an exact name indicated by the '=' prefix. Option --quick uses a\n" "shorter timeout; --ldap will use only ldap servers. With --first only\n" - "the first item is returned; --next is used to return the next item"; + "the first item is returned; --next is used to return the next item\n" + "Option --newer works only with certain LDAP servers."; static gpg_error_t cmd_ks_get (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; - strlist_t list, sl; + strlist_t list = NULL; + strlist_t sl; + const char *s; char *p; estream_t outfp; unsigned int flags = 0; + gnupg_isotime_t opt_newer; + + *opt_newer = 0; if (has_option (line, "--quick")) ctrl->timeout = opt.connect_quick_timeout; @@ -2486,13 +2492,18 @@ cmd_ks_get (assuan_context_t ctx, char *line) flags |= KS_GET_FLAG_FIRST; if (has_option (line, "--next")) flags |= KS_GET_FLAG_NEXT; + if ((s = option_value (line, "--newer")) + && !string2isotime (opt_newer, s)) + { + err = set_error (GPG_ERR_SYNTAX, "invalid time format"); + goto leave; + } line = skip_options (line); /* Break the line into a strlist. Each pattern is by definition percent-plus escaped. However we only support keyids and fingerprints and thus the client has no need to apply the escaping. */ - list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') @@ -2569,7 +2580,7 @@ cmd_ks_get (assuan_context_t ctx, char *line) ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; err = ks_action_get (ctrl, ctrl->server_local->keyservers, - list, flags, outfp); + list, flags, opt_newer, outfp); es_fclose (outfp); ctrl->server_local->inhibit_data_logging = 0; } @@ -2710,6 +2721,10 @@ cmd_ad_query (assuan_context_t ctx, char *line) estream_t outfp = NULL; char *p; char **opt_attr = NULL; + const char *s; + gnupg_isotime_t opt_newer; + + *opt_newer = 0; /* No options for now. */ if (has_option (line, "--first")) @@ -2718,6 +2733,12 @@ cmd_ad_query (assuan_context_t ctx, char *line) flags |= KS_GET_FLAG_NEXT; if (has_option (line, "--rootdse")) flags |= KS_GET_FLAG_ROOTDSE; + if ((s = option_value (line, "--newer")) + && !string2isotime (opt_newer, s)) + { + err = set_error (GPG_ERR_SYNTAX, "invalid time format"); + goto leave; + } err = get_option_value (line, "--attr", &p); if (err) goto leave; @@ -2758,7 +2779,7 @@ cmd_ad_query (assuan_context_t ctx, char *line) err = ks_action_query (ctrl, (flags & KS_GET_FLAG_ROOTDSE)? NULL : "ldap:///", - flags, filter, opt_attr, outfp); + flags, filter, opt_attr, opt_newer, outfp); leave: es_fclose (outfp); From 7bf57a794b77c5002c337b892bf90f59d5c82fe7 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 4 Apr 2023 09:20:26 +0200 Subject: [PATCH 50/60] gpg: Set the default digest algo for S2K to SHA256. * g10/main.h (DEFAULT_S2K_DIGEST_ALGO): Alias to DEFAULT_DIGEST_ALGO. -- GnuPG-bug-id: 6367 --- NEWS | 8 +++++++- g10/main.h | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 5eb609987..6d46e1d34 100644 --- a/NEWS +++ b/NEWS @@ -7,12 +7,18 @@ Noteworthy changes in version 2.4.1 (unreleased) "[self-signature]" instead of the user-id in key signature listings. [rG103acfe9ca] + * gpg: For symmetric encryption the default S2K hash is now SHA256. + [T6367] + * gpgsm: Verification of detached signatures does now strip trailing zeroes from the input if --assume-binary is used. [rG2a13f7f9dc] * gpgsm: Non-armored detached signature are now created without using indefinite form length octets. This improves compatibility - with some PDF signature verification software. + with some PDF signature verification software. [rG8996b0b655] + + * dirmngr: The LDAP modifyTimestamp is now returned by some + keyserver commands. [rG56d309133f] * gpg: Make list-options "show-sig-subpackets" work again. Fixes regression in 2.4.0. [rG5a223303d7] diff --git a/g10/main.h b/g10/main.h index dbaa0c6f3..3d71d0c09 100644 --- a/g10/main.h +++ b/g10/main.h @@ -42,7 +42,7 @@ #endif #define DEFAULT_DIGEST_ALGO ((GNUPG)? DIGEST_ALGO_SHA256:DIGEST_ALGO_SHA1) -#define DEFAULT_S2K_DIGEST_ALGO DIGEST_ALGO_SHA1 +#define DEFAULT_S2K_DIGEST_ALGO DEFAULT_DIGEST_ALGO #ifdef HAVE_ZIP # define DEFAULT_COMPRESS_ALGO COMPRESS_ALGO_ZIP #else @@ -235,7 +235,7 @@ int cpr_get_answer_okay_cancel (const char *keyword, /*-- helptext.c --*/ void display_online_help( const char *keyword ); -/*-- encode.c --*/ +/*-- encrypt.c --*/ gpg_error_t setup_symkey (STRING2KEY **symkey_s2k,DEK **symkey_dek); aead_algo_t use_aead (pk_list_t pk_list, int algo); int use_mdc (pk_list_t pk_list,int algo); From fcbb849c26e999db4922237492597f56edbcd39a Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 28 Mar 2023 10:39:01 +0200 Subject: [PATCH 51/60] speedo: Fix regression due to switching from gcc 8.3 to 10.2 * build-aux/speedo.mk (speedo_pkg_zlib_make_args): Use -static-libgcc. (cherry picked from commit 04f1d9649cfb9163907fe97d20821ddd1be44f82) --- build-aux/speedo.mk | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build-aux/speedo.mk b/build-aux/speedo.mk index 56e317275..f21f43731 100644 --- a/build-aux/speedo.mk +++ b/build-aux/speedo.mk @@ -699,9 +699,13 @@ speedo_pkg_w64_gpgex_configure = \ # External packages # +# gcc 10.2 takes __udivdi3 from the exception handler DLL and thus +# requires it. This is a regression from gcc 8.3 and earlier. To fix +# this we need to pass -static-libgcc. ifeq ($(TARGETOS),w32) speedo_pkg_zlib_make_args = \ - -fwin32/Makefile.gcc PREFIX=$(host)- IMPLIB=libz.dll.a + -fwin32/Makefile.gcc PREFIX=$(host)- IMPLIB=libz.dll.a \ + LDFLAGS=-static-libgcc speedo_pkg_zlib_make_args_inst = \ -fwin32/Makefile.gcc \ From db6ae6f6f851666064b90af57add13b470b0271b Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 4 Apr 2023 13:10:48 +0200 Subject: [PATCH 52/60] speedo: Remove deleted prf files from the Windows installer -- --- build-aux/speedo/w32/inst.nsi | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build-aux/speedo/w32/inst.nsi b/build-aux/speedo/w32/inst.nsi index bac898ccf..665c65b02 100644 --- a/build-aux/speedo/w32/inst.nsi +++ b/build-aux/speedo/w32/inst.nsi @@ -669,8 +669,6 @@ Section "GnuPG" SEC_gnupg File "share/gnupg/sks-keyservers.netCA.pem" SetOutPath "$INSTDIR\share\doc\gnupg\examples" - File "share/doc/gnupg/examples/VS-NfD.prf" - File "share/doc/gnupg/examples/Automatic.prf" File "share/doc/gnupg/examples/pwpattern.list" SetOutPath "$INSTDIR\share\locale\ca\LC_MESSAGES" @@ -1337,8 +1335,6 @@ Section "-un.gnupg" Delete "$INSTDIR\bin\gpg-check-pattern.exe" Delete "$INSTDIR\bin\gpg-wks-client.exe" - Delete "$INSTDIR\share\doc\gnupg\examples\VS-NfD.prf" - Delete "$INSTDIR\share\doc\gnupg\examples\Automatic.prf" Delete "$INSTDIR\share\doc\gnupg\examples\pwpattern.list" RMDir "$INSTDIR\share\doc\gnupg\examples" From d9e7488b17fdc617eec735e2c0485b69285ba511 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 4 Apr 2023 16:39:59 +0200 Subject: [PATCH 53/60] Use the keyboxd for a fresh install * common/homedir.c (gnupg_maybe_make_homedir): Also create a common.conf. * g10/keydb.c: Include comopt.h. (maybe_create_keyring_or_box): Detect the creation of a common.conf. * g10/gpg.c (main): Avoid adding more resources in this case. * sm/keydb.c: Include comopt.h. (maybe_create_keybox): Detect the creation of a common.conf. * common/comopt.h (comopt): Remove the conditional "extern". --- NEWS | 3 +++ README | 3 +++ common/comopt.h | 1 - common/homedir.c | 38 ++++++++++++++++++++++++++++++++++++-- doc/gpg.texi | 4 +++- g10/gpg.c | 20 +++++++++++++++----- g10/keydb.c | 31 ++++++++++++++++++++++++++----- sm/keydb.c | 28 ++++++++++++++++++++++++---- 8 files changed, 110 insertions(+), 18 deletions(-) diff --git a/NEWS b/NEWS index 6d46e1d34..7ca5b1335 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ Noteworthy changes in version 2.4.1 (unreleased) ------------------------------------------------ + * If the ~/.gnupg home directory does not exist, the keyboxd is now + automagically enabled. + * gpg: New option --add-desig-revoker. [rG3d094e2bcf] * gpg: New list-option "show-unusable-sigs". Also show diff --git a/README b/README index b9bf7805e..84a8bacfd 100644 --- a/README +++ b/README @@ -128,6 +128,9 @@ Only public keys and X.509 certificates are managed by the keyboxd; private keys are still stored as separate files. + Since version 2.4.1 the keyboxd will be used by default for a fresh + install; i.e. if a ~/.gnupg directory did not yet exist. + Note that there is no automatic migration; if the use-keyboxd option is enabled keys are not taken from pubring.kbx. To migrate existing keys to the keyboxd do this: diff --git a/common/comopt.h b/common/comopt.h index 7947f35b3..2a27fddac 100644 --- a/common/comopt.h +++ b/common/comopt.h @@ -35,7 +35,6 @@ /* Common options for all GnuPG components. */ -EXTERN_UNLESS_MAIN_MODULE struct { char *logfile; /* Socket used by daemons for logging. */ diff --git a/common/homedir.c b/common/homedir.c index 67bbde8f1..091964fc1 100644 --- a/common/homedir.c +++ b/common/homedir.c @@ -789,8 +789,42 @@ gnupg_maybe_make_homedir (const char *fname, int quiet) if (gnupg_mkdir (fname, "-rwx")) log_fatal ( _("can't create directory '%s': %s\n"), fname, strerror(errno) ); - else if (!quiet ) - log_info ( _("directory '%s' created\n"), fname ); + else + { + estream_t fp; + char *fcommon; + + if (!quiet ) + log_info ( _("directory '%s' created\n"), fname ); + +#ifdef BUILD_WITH_KEYBOXD + /* A new default homedir has been created. Now create a + * common.conf. */ + fcommon = make_filename (fname, "common.conf", NULL); + fp = es_fopen (fcommon, "wx,mode=-rw-r"); + if (!fp) + { + log_info (_("error creating '%s': %s\n"), fcommon, + gpg_strerror (gpg_error_from_syserror ())); + } + else + { + if (es_fputs ("use-keyboxd\n", fp) == EOF) + { + log_info (_("error writing to '%s': %s\n"), fcommon, + gpg_strerror (es_ferror (fp) + ? gpg_error_from_syserror () + : gpg_error (GPG_ERR_EOF))); + es_fclose (fp); + } + else if (es_fclose (fp)) + { + log_info (_("error closing '%s': %s\n"), fcommon, + gpg_strerror (gpg_error_from_syserror ())); + } + } +#endif /* BUILD_WITH_KEYBOXD */ + } } } diff --git a/doc/gpg.texi b/doc/gpg.texi index 393267858..b526deeca 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -3915,7 +3915,9 @@ current home directory (@pxref{option --homedir}). @efindex common.conf This is an optional configuration file read by @command{@gpgname} on startup. It may contain options pertaining to all components of - GnuPG. Its current main use is for the "use-keyboxd" option. + GnuPG. Its current main use is for the "use-keyboxd" option. If + the default home directory @file{~/.gnupg} does not exist, GnuPG creates + this directory and a @file{common.conf} file with "use_keyboxd". @end table diff --git a/g10/gpg.c b/g10/gpg.c index 84706ca6b..f52d13a76 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -4187,17 +4187,27 @@ main (int argc, char **argv) * need to add the keyrings if we are running under SELinux, this * is so that the rings are added to the list of secured files. * We do not add any keyring if --no-keyring or --use-keyboxd has - * been used. */ + * been used. Note that keydb_add_resource may create a new + * homedir and also tries to write a common.conf to enable the use + * of the keyboxd - in this case a special error code is returned + * and use_keyboxd is then also set. */ if (!opt.use_keyboxd && default_keyring >= 0 && (ALWAYS_ADD_KEYRINGS || (cmd != aDeArmor && cmd != aEnArmor && cmd != aGPGConfTest))) { + gpg_error_t tmperr = 0; + if (!nrings || default_keyring > 0) /* Add default ring. */ - keydb_add_resource ("pubring" EXTSEP_S GPGEXT_GPG, - KEYDB_RESOURCE_FLAG_DEFAULT); - for (sl = nrings; sl; sl = sl->next ) - keydb_add_resource (sl->d, sl->flags); + tmperr = keydb_add_resource ("pubring" EXTSEP_S GPGEXT_GPG, + KEYDB_RESOURCE_FLAG_DEFAULT); + if (gpg_err_code (tmperr) == GPG_ERR_TRUE && opt.use_keyboxd) + ; /* The keyboxd has been enabled. */ + else + { + for (sl = nrings; sl; sl = sl->next ) + keydb_add_resource (sl->d, sl->flags); + } } FREE_STRLIST(nrings); diff --git a/g10/keydb.c b/g10/keydb.c index 3938d7e16..d2d085291 100644 --- a/g10/keydb.c +++ b/g10/keydb.c @@ -37,6 +37,7 @@ #include "../kbx/keybox.h" #include "keydb.h" #include "../common/i18n.h" +#include "../common/comopt.h" #include "keydb-private.h" /* For struct keydb_handle_s */ @@ -265,8 +266,24 @@ maybe_create_keyring_or_box (char *filename, int is_box, int force_create) *last_slash_in_filename = save_slash; goto leave; } + + *last_slash_in_filename = save_slash; + + if (!opt.use_keyboxd + && !parse_comopt (GNUPG_MODULE_NAME_GPG, 0) + && comopt.use_keyboxd) + { + /* The above try_make_homedir created a new default hoemdir + * and also wrote a new common.conf. Thus we now see that + * use-keyboxd has been set. Let's set this option and + * return a dedicated error code. */ + opt.use_keyboxd = comopt.use_keyboxd; + rc = gpg_error (GPG_ERR_TRUE); + goto leave; + } } - *last_slash_in_filename = save_slash; + else + *last_slash_in_filename = save_slash; /* To avoid races with other instances of gpg trying to create or update the keyring (it is removed during an update for a short @@ -555,7 +572,8 @@ keydb_search_desc_dump (struct keydb_search_desc *desc) * If KEYDB_RESOURCE_FLAG_READONLY is set and the resource is a * keyring (not a keybox), then the keyring is marked as read only and * operations just as keyring_insert_keyblock will return - * GPG_ERR_ACCESS. */ + * GPG_ERR_ACCESS. + */ gpg_error_t keydb_add_resource (const char *url, unsigned int flags) { @@ -774,9 +792,12 @@ keydb_add_resource (const char *url, unsigned int flags) leave: if (err) { - log_error (_("keyblock resource '%s': %s\n"), - filename, gpg_strerror (err)); - write_status_error ("add_keyblock_resource", err); + if (gpg_err_code (err) != GPG_ERR_TRUE) + { + log_error (_("keyblock resource '%s': %s\n"), + filename, gpg_strerror (err)); + write_status_error ("add_keyblock_resource", err); + } } else any_registered = 1; diff --git a/sm/keydb.c b/sm/keydb.c index fbe28f2b9..a12dba19f 100644 --- a/sm/keydb.c +++ b/sm/keydb.c @@ -33,6 +33,7 @@ #include "keydb.h" #include "../common/i18n.h" #include "../common/asshelp.h" +#include "../common/comopt.h" #include "../kbx/kbx-client-util.h" @@ -242,8 +243,23 @@ maybe_create_keybox (char *filename, int force, int *r_created) *last_slash_in_filename = save_slash; goto leave; } + *last_slash_in_filename = save_slash; + + if (!opt.use_keyboxd + && !parse_comopt (GNUPG_MODULE_NAME_GPG, 0) + && comopt.use_keyboxd) + { + /* The above try_make_homedir created a new default hoemdir + * and also wrote a new common.conf. Thus we now see that + * use-keyboxd has been set. Let's set this option and + * return a dedicated error code. */ + opt.use_keyboxd = comopt.use_keyboxd; + rc = gpg_error (GPG_ERR_TRUE); + goto leave; + } } - *last_slash_in_filename = save_slash; + else + *last_slash_in_filename = save_slash; /* To avoid races with other instances of gpg trying to create or update the keybox (it is removed during an update for a short @@ -459,9 +475,13 @@ keydb_add_resource (ctrl_t ctrl, const char *url, int force, int *auto_created) leave: if (err) { - log_error ("keyblock resource '%s': %s\n", filename, gpg_strerror (err)); - gpgsm_status_with_error (ctrl, STATUS_ERROR, - "add_keyblock_resource", err); + if (gpg_err_code (err) != GPG_ERR_TRUE) + { + log_error ("keyblock resource '%s': %s\n", + filename, gpg_strerror (err)); + gpgsm_status_with_error (ctrl, STATUS_ERROR, + "add_keyblock_resource", err); + } } else any_registered = 1; From d261f5e5d0d56dcb3fbf323e94625ac18e7848d6 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 4 Apr 2023 16:51:58 +0200 Subject: [PATCH 54/60] common: Change allocation of the comopt symbol. * common/comopt.h (struct gnupg_comopt_s): New. * common/comopt.c (struct gnupg_comopt_s): Define here in the data segment. --- common/comopt.c | 2 ++ common/comopt.h | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/common/comopt.c b/common/comopt.c index 470cdac61..1cfd581b1 100644 --- a/common/comopt.c +++ b/common/comopt.c @@ -60,6 +60,8 @@ static gpgrt_opt_t opts[] = { }; +struct gnupg_comopt_s comopt = {NULL}; + /* Parse the common options in the homedir and etc. This needs to be * called after the gpgrt config directories are set. MODULE_ID is one of diff --git a/common/comopt.h b/common/comopt.h index 2a27fddac..9236afc6b 100644 --- a/common/comopt.h +++ b/common/comopt.h @@ -35,13 +35,16 @@ /* Common options for all GnuPG components. */ -struct +struct gnupg_comopt_s { char *logfile; /* Socket used by daemons for logging. */ int use_keyboxd; /* Use the keyboxd as storage backend. */ int no_autostart; /* Do not start gpg-agent. */ char *keyboxd_program; /* Use this as keyboxd program. */ -} comopt; +}; + + +extern struct gnupg_comopt_s comopt; gpg_error_t parse_comopt (int module_id, int verbose); From 946a851d17b0a4a7c8e2d594023d822fa4212466 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Wed, 5 Apr 2023 15:56:05 +0900 Subject: [PATCH 55/60] build: Update gpg-error.m4. * m4/gpg-error.m4: Update from libgpg-error master. -- Signed-off-by: NIIBE Yutaka --- m4/gpg-error.m4 | 59 ++++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/m4/gpg-error.m4 b/m4/gpg-error.m4 index a975e53d0..7fa52b127 100644 --- a/m4/gpg-error.m4 +++ b/m4/gpg-error.m4 @@ -1,5 +1,5 @@ # gpg-error.m4 - autoconf macro to detect libgpg-error. -# Copyright (C) 2002, 2003, 2004, 2011, 2014, 2018, 2020, 2021 +# Copyright (C) 2002, 2003, 2004, 2011, 2014, 2018, 2020, 2021, 2022 # g10 Code GmbH # # This file is free software; as a special exception the author gives @@ -10,23 +10,13 @@ # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # -# Last-changed: 2022-09-21 +# Last-changed: 2023-04-01 - -dnl AM_PATH_GPG_ERROR([MINIMUM-VERSION, -dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) dnl -dnl Test for libgpg-error and define GPG_ERROR_CFLAGS, GPG_ERROR_LIBS, -dnl GPG_ERROR_MT_CFLAGS, and GPG_ERROR_MT_LIBS. The _MT_ variants are -dnl used for programs requireing real multi thread support. +dnl Find gpg-error-config, for backward compatibility dnl -dnl If a prefix option is not used, the config script is first -dnl searched in $SYSROOT/bin and then along $PATH. If the used -dnl config script does not match the host specification the script -dnl is added to the gpg_config_script_warn variable. -dnl -AC_DEFUN([AM_PATH_GPG_ERROR], -[ AC_REQUIRE([AC_CANONICAL_HOST]) +dnl _AM_PATH_POSSIBLE_GPG_ERROR_CONFIG +AC_DEFUN([_AM_PATH_POSSIBLE_GPG_ERROR_CONFIG],[dnl gpg_error_config_prefix="" dnl --with-libgpg-error-prefix=PFX is the preferred name for this option, dnl since that is consistent with how our three siblings use the directory/ @@ -62,9 +52,14 @@ AC_DEFUN([AM_PATH_GPG_ERROR], fi AC_PATH_PROG(GPG_ERROR_CONFIG, gpg-error-config, no) - min_gpg_error_version=ifelse([$1], ,1.33,$1) - ok=no +]) +dnl +dnl Find gpgrt-config, which uses .pc file +dnl (minimum pkg-config functionality, supporting cross build) +dnl +dnl _AM_PATH_GPGRT_CONFIG +AC_DEFUN([_AM_PATH_GPGRT_CONFIG],[dnl AC_PATH_PROG(GPGRT_CONFIG, gpgrt-config, no, [$prefix/bin:$PATH]) if test "$GPGRT_CONFIG" != "no"; then # Determine gpgrt_libdir @@ -120,12 +115,9 @@ AC_DEFUN([AM_PATH_GPG_ERROR], fi if test -n "$gpgrt_libdir"; then break; fi done - if test -z "$libdir_candidates"; then - # No valid pkgconfig dir in any of the system directories, fallback - gpgrt_libdir=${possible_libdir1} - fi - else - # When we cannot determine system libdir-format, use this: + fi + if test -z "$gpgrt_libdir"; then + # No valid pkgconfig dir in any of the system directories, fallback gpgrt_libdir=${possible_libdir1} fi else @@ -139,12 +131,33 @@ AC_DEFUN([AM_PATH_GPG_ERROR], AC_MSG_NOTICE([Use gpgrt-config with $gpgrt_libdir as gpg-error-config]) gpg_error_config_version=`$GPG_ERROR_CONFIG --modversion` else + gpg_error_config_version=`$GPG_ERROR_CONFIG --version` unset GPGRT_CONFIG fi elif test "$GPG_ERROR_CONFIG" != "no"; then gpg_error_config_version=`$GPG_ERROR_CONFIG --version` unset GPGRT_CONFIG fi +]) + +dnl AM_PATH_GPG_ERROR([MINIMUM-VERSION, +dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) +dnl +dnl Test for libgpg-error and define GPG_ERROR_CFLAGS, GPG_ERROR_LIBS, +dnl GPG_ERROR_MT_CFLAGS, and GPG_ERROR_MT_LIBS. The _MT_ variants are +dnl used for programs requireing real multi thread support. +dnl +dnl If a prefix option is not used, the config script is first +dnl searched in $SYSROOT/bin and then along $PATH. If the used +dnl config script does not match the host specification the script +dnl is added to the gpg_config_script_warn variable. +dnl +AC_DEFUN([AM_PATH_GPG_ERROR],[dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([_AM_PATH_POSSIBLE_GPG_ERROR_CONFIG])dnl +AC_REQUIRE([_AM_PATH_GPGRT_CONFIG])dnl + min_gpg_error_version=ifelse([$1], ,1.33,$1) + ok=no if test "$GPG_ERROR_CONFIG" != "no"; then req_major=`echo $min_gpg_error_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)/\1/'` From 42ccbd6c78e61cdc9de5a87f3a771e0dd21852b8 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 5 Apr 2023 14:57:40 +0200 Subject: [PATCH 56/60] speedo,w32: Remove removed profiles and temporary disable runonce. -- --- build-aux/speedo/w32/inst.nsi | 3 ++- build-aux/speedo/w32/wixlib.wxs | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/build-aux/speedo/w32/inst.nsi b/build-aux/speedo/w32/inst.nsi index 665c65b02..42fa2fb58 100644 --- a/build-aux/speedo/w32/inst.nsi +++ b/build-aux/speedo/w32/inst.nsi @@ -1462,7 +1462,8 @@ Function .onInit ;;!define MUI_LANGDLL_ALWAYSSHOW !insertmacro MUI_LANGDLL_DISPLAY - Call G4wRunOnce + # Temporay disabled until we have fixed the DLL issue (wk 2023-04-05) + # Call G4wRunOnce SetOutPath $TEMP #!ifdef SOURCES diff --git a/build-aux/speedo/w32/wixlib.wxs b/build-aux/speedo/w32/wixlib.wxs index 66e6fee62..02568fe2f 100644 --- a/build-aux/speedo/w32/wixlib.wxs +++ b/build-aux/speedo/w32/wixlib.wxs @@ -160,12 +160,6 @@ and then manually edited: - - - - - - From c9e95b8dee05b9a837419fdef9a98f0b3e9671ed Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 5 Apr 2023 21:32:23 +0200 Subject: [PATCH 57/60] gpg: New option --assert-signer. * g10/gpg.c (enum cmd_and_opt_values): Add oAssertSigner. (opts): Add "assert-signer". (main): Set option. (assert_signer_true): New var. (g10_exit): Evaluate new var. * g10/main.h (assert_signer_true): Declare new var. * common/status.h (STATUS_ASSERT_SIGNER): New. * g10/options.h (opt): Add field assert_signer_list. * g10/verify.c (is_fingerprint): New. (check_assert_signer_list): New. * g10/mainproc.c (check_sig_and_print): Call that function. Clear assert_signer_true on a warning. * g10/gpgv.c: Add dummy function and vars. * g10/t-keydb-get-keyblock.c: Ditto. * g10/t-keydb.c: Ditto. * g10/t-stutter.c: Ditto. -- --- NEWS | 4 +- common/status.h | 1 + doc/DETAILS | 5 ++ doc/gpg.texi | 32 +++++++--- g10/gpg.c | 24 ++++++- g10/gpgv.c | 2 +- g10/main.h | 2 + g10/mainproc.c | 5 +- g10/options.h | 4 ++ g10/t-keydb-get-keyblock.c | 9 +++ g10/t-keydb.c | 10 +++ g10/t-stutter.c | 9 +++ g10/verify.c | 124 +++++++++++++++++++++++++++++++++++++ 13 files changed, 216 insertions(+), 15 deletions(-) diff --git a/NEWS b/NEWS index 7ca5b1335..894743db5 100644 --- a/NEWS +++ b/NEWS @@ -1,11 +1,13 @@ Noteworthy changes in version 2.4.1 (unreleased) ------------------------------------------------ - * If the ~/.gnupg home directory does not exist, the keyboxd is now + * If the ~/.gnupg directory does not exist, the keyboxd is now automagically enabled. * gpg: New option --add-desig-revoker. [rG3d094e2bcf] + * gpg: New option --assert-signer. + * gpg: New list-option "show-unusable-sigs". Also show "[self-signature]" instead of the user-id in key signature listings. [rG103acfe9ca] diff --git a/common/status.h b/common/status.h index 0c481d247..e4cf23ee1 100644 --- a/common/status.h +++ b/common/status.h @@ -53,6 +53,7 @@ enum STATUS_NEED_PASSPHRASE, STATUS_VALIDSIG, + STATUS_ASSERT_SIGNER, STATUS_SIG_ID, STATUS_ENC_TO, STATUS_NODATA, diff --git a/doc/DETAILS b/doc/DETAILS index eee640a01..fd95e511c 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -522,6 +522,11 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: Epoch or an ISO 8601 string which can be detected by the presence of the letter 'T'. +*** ASSERT_SIGNER + This is emitted for the matching when option + --assert-signer is used. The fingerprint is printed with + uppercase hex digits. + *** SIG_ID This is emitted only for signatures of class 0 or 1 which have been verified okay. The string is a signature id and may be used diff --git a/doc/gpg.texi b/doc/gpg.texi index b526deeca..eb7c35cac 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -264,11 +264,11 @@ out the actual signed data, but there are other pitfalls with this format as well. It is suggested to avoid cleartext signatures in favor of detached signatures. -Note: Sometimes the use of the @command{gpgv} tool is easier than -using the full-fledged @command{gpg} with this option. @command{gpgv} -is designed to compare signed data against a list of trusted keys and -returns with success only for a good signature. It has its own manual -page. +Note: To check whether a file was signed by a certain key the option +@option{--assert-signer} can be used. As an alternative the +@command{gpgv} tool can be used. @command{gpgv} is designed to +compare signed data against a list of trusted keys and returns with +success only for a good signature. It has its own manual page. @item --multifile @@ -1889,6 +1889,24 @@ Set what trust model GnuPG should follow. The models are: must be enabled explicitly. @end table +@item --always-trust +@opindex always-trust +Identical to @option{--trust-model always}. + +@item --assert-signer @var{fpr_or_file} +@opindex assert-signer +This option checks whether at least one valid signature on a file has +been made with the specified key. The key is either specified as a +fingerprint or a file listing fingerprints. The fingerprint must be +given or listed in compact format (no colons or spaces in between). +This option can be given multiple times and each fingerprint is +checked against the signing key as well as the corresponding primary +key. If @var{fpr_or_file} specifies a file, empty lines are ignored +as well as all lines starting with a hash sign. With this option gpg +is guaranteed to return with an exit code of 0 if and only if a +signature has been encountered, is valid, and the key matches one of +the fingerprints given by this option. + @item --auto-key-locate @var{mechanisms} @itemx --no-auto-key-locate @@ -3856,10 +3874,6 @@ Display the keyring name at the head of key listings to show which keyring a given key resides on. This option is deprecated: use @option{--list-options [no-]show-keyring} instead. -@item --always-trust -@opindex always-trust -Identical to @option{--trust-model always}. This option is deprecated. - @item --show-notation @itemx --no-show-notation @opindex show-notation diff --git a/g10/gpg.c b/g10/gpg.c index f52d13a76..b759cc1cf 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -446,6 +446,7 @@ enum cmd_and_opt_values oRequireCompliance, oCompatibilityFlags, oAddDesigRevoker, + oAssertSigner, oNoop }; @@ -708,7 +709,7 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_n (oNoAutoTrustNewKey, "no-auto-trust-new-key", "@"), #endif ARGPARSE_s_s (oAddDesigRevoker, "add-desig-revoker", "@"), - + ARGPARSE_s_s (oAssertSigner, "assert-signer", "@"), ARGPARSE_header ("Input", N_("Options controlling the input")), @@ -1032,8 +1033,12 @@ static struct compatibility_flags_s compatibility_flags [] = /* The list of the default AKL methods. */ #define DEFAULT_AKL_LIST "local,wkd" - +/* Can be set to true to force gpg to return with EXIT_FAILURE. */ int g10_errors_seen = 0; +/* If opt.assert_signer_list is used and this variabale is not true + * gpg will be forced to return EXIT_FAILURE. */ +int assert_signer_true = 0; + static int utf8_strings = #ifdef HAVE_W32_SYSTEM @@ -3734,6 +3739,11 @@ main (int argc, char **argv) append_to_strlist (&opt.desig_revokers, pargs.r.ret_str); break; + case oAssertSigner: + add_to_strlist (&opt.assert_signer_list, pargs.r.ret_str); + break; + + case oNoop: break; default: @@ -5448,7 +5458,15 @@ g10_exit( int rc ) gnupg_block_all_signals (); emergency_cleanup (); - rc = rc? rc : log_get_errorcount(0)? 2 : g10_errors_seen? 1 : 0; + if (rc) + ; + else if (log_get_errorcount(0)) + rc = 2; + else if (g10_errors_seen) + rc = 1; + else if (opt.assert_signer_list && !assert_signer_true) + rc = 1; + exit (rc); } diff --git a/g10/gpgv.c b/g10/gpgv.c index ceded4af9..f2895563e 100644 --- a/g10/gpgv.c +++ b/g10/gpgv.c @@ -118,7 +118,7 @@ static struct debug_flags_s debug_flags [] = int g10_errors_seen = 0; - +int assert_signer_true = 0; static char * make_libversion (const char *libname, const char *(*getfnc)(const char*)) diff --git a/g10/main.h b/g10/main.h index 3d71d0c09..b29e23e51 100644 --- a/g10/main.h +++ b/g10/main.h @@ -83,6 +83,7 @@ struct weakhash /*-- gpg.c --*/ extern int g10_errors_seen; +extern int assert_signer_true; #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ) void g10_exit(int rc) __attribute__ ((__noreturn__)); @@ -492,6 +493,7 @@ void print_file_status( int status, const char *name, int what ); int verify_signatures (ctrl_t ctrl, int nfiles, char **files ); int verify_files (ctrl_t ctrl, int nfiles, char **files ); int gpg_verify (ctrl_t ctrl, int sig_fd, int data_fd, estream_t out_fp); +void check_assert_signer_list (const char *mainpkhex, const char *pkhex); /*-- decrypt.c --*/ int decrypt_message (ctrl_t ctrl, const char *filename ); diff --git a/g10/mainproc.c b/g10/mainproc.c index 4710386ea..ce0fdaaac 100644 --- a/g10/mainproc.c +++ b/g10/mainproc.c @@ -2410,7 +2410,7 @@ check_sig_and_print (CTX c, kbnode_t node) } /* For good signatures print the VALIDSIG status line. */ - if (!rc && is_status_enabled () && pk) + if (!rc && (is_status_enabled () || opt.assert_signer_list) && pk) { char pkhex[MAX_FINGERPRINT_LEN*2+1]; char mainpkhex[MAX_FINGERPRINT_LEN*2+1]; @@ -2430,6 +2430,8 @@ check_sig_and_print (CTX c, kbnode_t node) sig->digest_algo, sig->sig_class, mainpkhex); + /* Handle the --assert-signer option. */ + check_assert_signer_list (mainpkhex, pkhex); } /* Print compliance warning for Good signatures. */ @@ -2510,6 +2512,7 @@ check_sig_and_print (CTX c, kbnode_t node) is not a detached signature. */ log_info (_("WARNING: not a detached signature; " "file '%s' was NOT verified!\n"), dfile); + assert_signer_true = 0; } xfree (dfile); } diff --git a/g10/options.h b/g10/options.h index 3ecf57ffb..9015e321f 100644 --- a/g10/options.h +++ b/g10/options.h @@ -235,6 +235,10 @@ struct value. */ int limit_card_insert_tries; + /* The list of --assert-signer option values. Note: The values are + * modify to be uppercase if they represent a fingerrint */ + strlist_t assert_signer_list; + struct { /* If set, require an 0x19 backsig to be present on signatures diff --git a/g10/t-keydb-get-keyblock.c b/g10/t-keydb-get-keyblock.c index 90ce6e9a6..e40be9cc1 100644 --- a/g10/t-keydb-get-keyblock.c +++ b/g10/t-keydb-get-keyblock.c @@ -67,3 +67,12 @@ do_test (int argc, char *argv[]) release_kbnode (kb1); xfree (ctrl); } + +int assert_signer_true = 0; + +void +check_assert_signer_list (const char *mainpkhex, const char *pkhex) +{ + (void)mainpkhex; + (void)pkhex; +} diff --git a/g10/t-keydb.c b/g10/t-keydb.c index 4c78dac48..9055d5b94 100644 --- a/g10/t-keydb.c +++ b/g10/t-keydb.c @@ -105,3 +105,13 @@ do_test (int argc, char *argv[]) keydb_release (hd2); xfree (ctrl); } + + +int assert_signer_true = 0; + +void +check_assert_signer_list (const char *mainpkhex, const char *pkhex) +{ + (void)mainpkhex; + (void)pkhex; +} diff --git a/g10/t-stutter.c b/g10/t-stutter.c index 503a92004..7b2ea4b37 100644 --- a/g10/t-stutter.c +++ b/g10/t-stutter.c @@ -611,3 +611,12 @@ do_test (int argc, char *argv[]) xfree (filename); } + +int assert_signer_true = 0; + +void +check_assert_signer_list (const char *mainpkhex, const char *pkhex) +{ + (void)mainpkhex; + (void)pkhex; +} diff --git a/g10/verify.c b/g10/verify.c index fc18882b0..e9792939d 100644 --- a/g10/verify.c +++ b/g10/verify.c @@ -1,6 +1,8 @@ /* verify.c - Verify signed data * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005, 2006, * 2007, 2010 Free Software Foundation, Inc. + * Copyright (C) 2003, 2006-2008, 2010-2011, 2015-2017, + * 2020, 2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -16,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 @@ -281,3 +284,124 @@ gpg_verify (ctrl_t ctrl, int sig_fd, int data_fd, estream_t out_fp) release_armor_context (afx); return rc; } + + +static int +is_fingerprint (const char *string) +{ + int n; + + if (!string || !*string) + return 0; + for (n=0; hexdigitp (string); string++) + n++; + if (!*string && (n == 40 || n == 64)) + return 1; /* v4 or v5 fingerprint. */ + + return 0; +} + + +/* This function shall be called with the main and subkey fingerprint + * iff a signature is fully valid. If the option --assert-signer is + * active it check whether the signing key matches one of the keys + * given by this option and if so, sets a global flag. */ +void +check_assert_signer_list (const char *mainpkhex, const char *pkhex) +{ + gpg_error_t err; + strlist_t item; + const char *fname; + estream_t fp = NULL; + int lnr; + int n, c; + char *p, *pend; + char line[256]; + + if (!opt.assert_signer_list) + return; /* Nothing to do. */ + if (assert_signer_true) + return; /* Already one valid signature seen. */ + + for (item = opt.assert_signer_list; item; item = item->next) + { + if (is_fingerprint (item->d)) + { + ascii_strupr (item->d); + if (!strcmp (item->d, mainpkhex) || !strcmp (item->d, pkhex)) + { + assert_signer_true = 1; + write_status_text (STATUS_ASSERT_SIGNER, item->d); + if (!opt.quiet) + log_info ("signer '%s' matched\n", item->d); + goto leave; + } + } + else /* Assume this is a file - read and compare. */ + { + fname = item->d; + es_fclose (fp); + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error (_("error opening '%s': %s\n"), + fname, gpg_strerror (err)); + continue; + } + + lnr = 0; + err = 0; + while (es_fgets (line, DIM(line)-1, fp)) + { + lnr++; + + n = strlen (line); + if (!n || line[n-1] != '\n') + { + /* Eat until end of line. */ + while ( (c=es_getc (fp)) != EOF && c != '\n') + ; + err = gpg_error (GPG_ERR_INCOMPLETE_LINE); + log_error (_("file '%s', line %d: %s\n"), + fname, lnr, gpg_strerror (err)); + continue; + } + line[--n] = 0; /* Chop the LF. */ + if (n && line[n-1] == '\r') + line[--n] = 0; /* Chop an optional CR. */ + + /* Allow for empty lines and spaces */ + for (p=line; spacep (p); p++) + ; + if (!*p || *p == '#') + continue; + + /* Get the first token and ignore trailing stuff. */ + for (pend = p; *pend && !spacep (pend); pend++) + ; + *pend = 0; + ascii_strupr (p); + + if (!strcmp (p, mainpkhex) || !strcmp (p, pkhex)) + { + assert_signer_true = 1; + write_status_text (STATUS_ASSERT_SIGNER, p); + if (!opt.quiet) + log_info ("signer '%s' matched '%s', line %d\n", + p, fname, lnr); + goto leave; + } + } + if (!err && !es_feof (fp)) + { + err = gpg_error_from_syserror (); + log_error (_("error reading '%s', line %d: %s\n"), + fname, lnr, gpg_strerror (err)); + } + } + } + + leave: + es_fclose (fp); +} From cb055ecb9109385c65f715edfd9bf3ca2f1521d3 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 6 Apr 2023 11:32:52 +0900 Subject: [PATCH 58/60] gpg: Fix handling of importing cv25519 secret key. * g10/import.c (transfer_secret_keys): Only emit a warning when secret key is not encrypted. -- Fixing-commit: dbfb7f809b89cfe05bdacafdb91a2d485b9fe2e0 GnuPG-bug-id: 6322 Signed-off-by: NIIBE Yutaka --- g10/import.c | 1 + 1 file changed, 1 insertion(+) diff --git a/g10/import.c b/g10/import.c index 9fab46ca6..1ed40a63c 100644 --- a/g10/import.c +++ b/g10/import.c @@ -2656,6 +2656,7 @@ transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, * in case of cv25519. We have only opaque MPIs here. */ if (pk->pubkey_algo == PUBKEY_ALGO_ECDH && !strcmp (curvestr, "1.3.6.1.4.1.3029.1.5.1") + && !gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1) && gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_OPAQUE)) { const unsigned char *pp; From 3013137f744f43e17f61da8f7ca5cefe5ba65981 Mon Sep 17 00:00:00 2001 From: Eva Bolten Date: Thu, 6 Apr 2023 09:39:55 +0200 Subject: [PATCH 59/60] po: Fix in German translation -- --- po/de.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/po/de.po b/po/de.po index 0f8fe1d95..eff9b9634 100644 --- a/po/de.po +++ b/po/de.po @@ -332,7 +332,7 @@ msgid "Please enter the passphrase to%0Aprotect your new key" msgstr "Bitte geben Sie das Passwort ein,%0Aum Ihren Schlüssel zu schützen." msgid "Please enter the new passphrase" -msgstr "Bitte geben Sie das Passwort ein:" +msgstr "Bitte geben Sie das neue Passwort ein:" msgid "Options used for startup" msgstr "Optionen zum Start des Programms" @@ -6715,7 +6715,7 @@ msgstr "Lesen des öffentlichen Schlüssels fehlgeschlagen: %s\n" #. * the %s at the start and end of the string. #, c-format msgid "%sNumber: %s%%0AHolder: %s%%0ACounter: %lu%s" -msgstr "%sNummer: %s%%0ABesitzer: %s%%0AAnzahl: %lu%s" +msgstr "%sNummer: %s%%0ABesitzer: %s%%0AZähler: %lu%s" #, c-format msgid "using default PIN as %s\n" From d965ee8d65f9a3c40ae494c0e738a81e15f68071 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 12 Apr 2023 17:10:08 +0200 Subject: [PATCH 60/60] gpg: Curvenames may now compared case insensitive. * common/openpgp-oid.c (openpgp_curve_to_oid): Repalce strmcp by ascii_strcasecmp. (openpgp_oid_or_name_to_curve): Ditto. (openpgp_is_curve_supported): Ditto. (get_keyalgo_string): Ditto. -- It was just to hard to remember the correct capitalization of names like brainpoolP512r1. --- common/openpgp-oid.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/common/openpgp-oid.c b/common/openpgp-oid.c index 510e09f4a..493054950 100644 --- a/common/openpgp-oid.c +++ b/common/openpgp-oid.c @@ -444,8 +444,9 @@ openpgp_curve_to_oid (const char *name, unsigned int *r_nbits, int *r_algo) if (name) { for (i=0; oidtable[i].name; i++) - if (!strcmp (oidtable[i].name, name) - || (oidtable[i].alias && !strcmp (oidtable[i].alias, name))) + if (!ascii_strcasecmp (oidtable[i].name, name) + || (oidtable[i].alias + && !ascii_strcasecmp (oidtable[i].alias, name))) { oidstr = oidtable[i].oidstr; nbits = oidtable[i].nbits; @@ -457,7 +458,7 @@ openpgp_curve_to_oid (const char *name, unsigned int *r_nbits, int *r_algo) /* If not found assume the input is already an OID and check whether we support it. */ for (i=0; oidtable[i].name; i++) - if (!strcmp (name, oidtable[i].oidstr)) + if (!ascii_strcasecmp (name, oidtable[i].oidstr)) { oidstr = oidtable[i].oidstr; nbits = oidtable[i].nbits; @@ -506,9 +507,10 @@ openpgp_oid_or_name_to_curve (const char *oidname, int canon) return NULL; for (i=0; oidtable[i].name; i++) - if (!strcmp (oidtable[i].oidstr, oidname) - || !strcmp (oidtable[i].name, oidname) - || (oidtable[i].alias &&!strcmp (oidtable[i].alias, oidname))) + if (!ascii_strcasecmp (oidtable[i].oidstr, oidname) + || !ascii_strcasecmp (oidtable[i].name, oidname) + || (oidtable[i].alias + && !ascii_strcasecmp (oidtable[i].alias, oidname))) return !canon && oidtable[i].alias? oidtable[i].alias : oidtable[i].name; return NULL; @@ -570,8 +572,9 @@ openpgp_is_curve_supported (const char *name, int *r_algo, *r_nbits = 0; for (idx = 0; idx < DIM (oidtable) && oidtable[idx].name; idx++) { - if ((!strcmp (name, oidtable[idx].name) - || (oidtable[idx].alias && !strcmp (name, (oidtable[idx].alias)))) + if ((!ascii_strcasecmp (name, oidtable[idx].name) + || (oidtable[idx].alias + && !ascii_strcasecmp (name, (oidtable[idx].alias)))) && curve_supported_p (oidtable[idx].name)) { if (r_algo) @@ -673,7 +676,7 @@ get_keyalgo_string (enum gcry_pk_algos algo, { if (keyalgo_strings[i].algo == algo && keyalgo_strings[i].curve && curve - && !strcmp (keyalgo_strings[i].curve, curve)) + && !ascii_strcasecmp (keyalgo_strings[i].curve, curve)) return keyalgo_strings[i].name; }