diff options
Diffstat (limited to 'agent')
-rw-r--r-- | agent/agent.h | 25 | ||||
-rw-r--r-- | agent/cache.c | 358 | ||||
-rw-r--r-- | agent/call-daemon.c | 51 | ||||
-rw-r--r-- | agent/call-pinentry.c | 44 | ||||
-rw-r--r-- | agent/call-scd.c | 10 | ||||
-rw-r--r-- | agent/command-ssh.c | 4 | ||||
-rw-r--r-- | agent/command.c | 62 | ||||
-rw-r--r-- | agent/cvt-openpgp.c | 11 | ||||
-rw-r--r-- | agent/divert-scd.c | 35 | ||||
-rw-r--r-- | agent/divert-tpm2.c | 6 | ||||
-rw-r--r-- | agent/genkey.c | 18 | ||||
-rw-r--r-- | agent/gpg-agent.c | 484 | ||||
-rw-r--r-- | agent/keyformat.txt | 520 | ||||
-rw-r--r-- | agent/pkdecrypt.c | 684 | ||||
-rw-r--r-- | agent/trustlist.c | 14 |
15 files changed, 1414 insertions, 912 deletions
diff --git a/agent/agent.h b/agent/agent.h index 06bc1e046..dbb3000dd 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -288,8 +288,11 @@ struct server_control_s unsigned int raw_value: 1; unsigned int is_pss: 1; /* DATA holds PSS formated data. */ } digest; + unsigned int have_keygrip: 1; + unsigned int have_keygrip1: 1; unsigned char keygrip[20]; - int have_keygrip; + unsigned char keygrip1[20]; /* Another keygrip for hybrid crypto. */ + /* A flag to enable a hack to send the PKAUTH command instead of the PKSIGN command to the scdaemon. */ @@ -428,6 +431,7 @@ void *get_agent_daemon_notify_event (void); #endif void agent_sighup_action (void); int map_pk_openpgp_to_gcry (int openpgp_algo); +void agent_kick_the_loop (void); /*-- command.c --*/ gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, @@ -533,7 +537,7 @@ int agent_clear_passphrase (ctrl_t ctrl, /*-- cache.c --*/ void initialize_module_cache (void); void deinitialize_module_cache (void); -void agent_cache_housekeeping (void); +struct timespec *agent_cache_expiration (void); void agent_flush_cache (int pincache_only); int agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, const char *data, int ttl); @@ -556,6 +560,18 @@ gpg_error_t agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, const unsigned char *ciphertext, size_t ciphertextlen, membuf_t *outbuf, int *r_padding); +enum kemids + { + KEM_PQC_PGP, + KEM_PGP, + KEM_CMS + }; + +gpg_error_t agent_kem_decrypt (ctrl_t ctrl, const char *desc_text, int kemid, + const unsigned char *ct, size_t ctlen, + const unsigned char *option, size_t optionlen, + membuf_t *outbuf); + /*-- genkey.c --*/ #define CHECK_CONSTRAINTS_NOT_EMPTY 1 #define CHECK_CONSTRAINTS_NEW_SYMKEY 2 @@ -689,6 +705,9 @@ gpg_error_t divert_writekey (ctrl_t ctrl, int force, const char *serialno, const char *keyref, const char *keydata, size_t keydatalen); +gpg_error_t agent_card_ecc_kem (ctrl_t ctrl, const unsigned char *ecc_ct, + size_t ecc_point_len, unsigned char *ecc_ecdh); + /*-- call-daemon.c --*/ gpg_error_t daemon_start (enum daemon_type type, ctrl_t ctrl); assuan_context_t daemon_type_ctx (enum daemon_type type, ctrl_t ctrl); @@ -737,6 +756,7 @@ int agent_card_pkdecrypt (ctrl_t ctrl, const char *desc_text, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen, int *r_padding); + int agent_card_readcert (ctrl_t ctrl, const char *id, char **r_buf, size_t *r_buflen); int agent_card_readkey (ctrl_t ctrl, const char *id, @@ -758,7 +778,6 @@ void agent_card_free_keyinfo (struct card_key_info_s *l); gpg_error_t agent_card_keyinfo (ctrl_t ctrl, const char *keygrip, int cap, struct card_key_info_s **result); - /*-- learncard.c --*/ int agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force); diff --git a/agent/cache.c b/agent/cache.c index 7616dafc1..e8544205f 100644 --- a/agent/cache.c +++ b/agent/cache.c @@ -53,8 +53,20 @@ struct secret_data_s { char data[1]; /* A string. */ }; -/* The cache object. */ +/* The type of cache object. */ typedef struct cache_item_s *ITEM; + +/* The timer entry in a linked list. */ +struct timer_s { + ITEM next; + int tv_sec; + int reason; +}; +#define CACHE_EXPIRE_UNUSED 0 +#define CACHE_EXPIRE_LAST_ACCESS 1 +#define CACHE_EXPIRE_CREATION 2 + +/* The cache object. */ struct cache_item_s { ITEM next; time_t created; @@ -63,12 +75,18 @@ struct cache_item_s { struct secret_data_s *pw; cache_mode_t cache_mode; int restricted; /* The value of ctrl->restricted is part of the key. */ + struct timer_s t; char key[1]; }; /* The cache himself. */ static ITEM thecache; +/* The timer list of expiration, in active. */ +static ITEM the_timer_list; +/* Newly created entries, to be inserted into the timer list. */ +static ITEM the_timer_list_new; + /* NULL or the last cache key stored by agent_store_cache_hit. */ static char *last_stored_cache_key; @@ -193,100 +211,302 @@ new_data (const char *string, struct secret_data_s **r_data) } +static void +insert_to_timer_list_new (ITEM entry) +{ + entry->t.next = the_timer_list_new; + the_timer_list_new = entry; +} -/* Check whether there are items to expire. */ +/* Insert to the active timer list. */ static void -housekeeping (void) +insert_to_timer_list (struct timespec *ts, ITEM entry) { - ITEM r, rprev; - time_t current = gnupg_get_time (); + ITEM e, eprev; - /* First expire the actual data */ - for (r=thecache; r; r = r->next) + if (!the_timer_list || ts->tv_sec >= entry->t.tv_sec) { - if (r->cache_mode == CACHE_MODE_PIN) - ; /* Don't let it expire - scdaemon explicitly flushes them. */ - else if (r->pw && r->ttl >= 0 && r->accessed + r->ttl < current) + if (the_timer_list) { - if (DBG_CACHE) - log_debug (" expired '%s'.%d (%ds after last access)\n", - r->key, r->restricted, r->ttl); - release_data (r->pw); - r->pw = NULL; - r->accessed = current; + the_timer_list->t.tv_sec += ts->tv_sec - entry->t.tv_sec; + if (ts->tv_nsec >= 500000000) + the_timer_list->t.tv_sec++; } + + ts->tv_sec = entry->t.tv_sec; + ts->tv_nsec = 0; + + entry->t.tv_sec = 0; + entry->t.next = the_timer_list; + the_timer_list = entry; + return; } - /* Second, make sure that we also remove them based on the created - * stamp so that the user has to enter it from time to time. We - * don't do this for data items which are used to storage secrets in - * meory and are not user entered passphrases etc. */ - for (r=thecache; r; r = r->next) + entry->t.tv_sec -= ts->tv_sec; + eprev = NULL; + for (e = the_timer_list; e; e = e->t.next) { - unsigned long maxttl; + if (e->t.tv_sec > entry->t.tv_sec) + break; - switch (r->cache_mode) - { - case CACHE_MODE_DATA: - case CACHE_MODE_PIN: - continue; /* No MAX TTL here. */ - case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break; - default: maxttl = opt.max_cache_ttl; break; - } - if (r->pw && r->created + maxttl < current) - { - if (DBG_CACHE) - log_debug (" expired '%s'.%d (%lus after creation)\n", - r->key, r->restricted, opt.max_cache_ttl); - release_data (r->pw); - r->pw = NULL; - r->accessed = current; - } + eprev = e; + entry->t.tv_sec -= e->t.tv_sec; + } + + entry->t.next = e; + if (e) + e->t.tv_sec -= entry->t.tv_sec; + + if (eprev) + eprev->t.next = entry; + else + the_timer_list = entry; +} + +static void +remove_from_timer_list (ITEM entry) +{ + ITEM e, eprev; + + eprev = NULL; + for (e = the_timer_list; e; e = e->t.next) + if (e != entry) + eprev = e; + else + { + if (e->t.next) + e->t.next->t.tv_sec += e->t.tv_sec; + + if (eprev) + eprev->t.next = e->t.next; + else + the_timer_list = e->t.next; + + break; + } + + entry->t.next = NULL; + entry->t.tv_sec = 0; +} + +static void +remove_from_timer_list_new (ITEM entry) +{ + ITEM e, eprev; + + eprev = NULL; + for (e = the_timer_list_new; e; e = e->t.next) + if (e != entry) + eprev = e; + else + { + if (eprev) + eprev->t.next = e->t.next; + else + the_timer_list_new = e->t.next; + + break; + } + + entry->t.next = NULL; + entry->t.tv_sec = 0; +} + +static int +compute_expiration (ITEM r) +{ + unsigned long maxttl; + time_t current = gnupg_get_time (); + time_t next; + + if (r->cache_mode == CACHE_MODE_PIN) + return 0; /* Don't let it expire - scdaemon explicitly flushes them. */ + + if (!r->pw) + { + /* Expire an old and unused entry after 30 minutes. */ + r->t.tv_sec = 60*30; + r->t.reason = CACHE_EXPIRE_UNUSED; + return 1; } - /* Third, make sure that we don't have too many items in the list. - * Expire old and unused entries after 30 minutes. */ - for (rprev=NULL, r=thecache; r; ) + switch (r->cache_mode) { - if (!r->pw && r->ttl >= 0 && r->accessed + 60*30 < current) - { - ITEM r2 = r->next; - if (DBG_CACHE) - log_debug (" removed '%s'.%d (mode %d) (slot not used for 30m)\n", - r->key, r->restricted, r->cache_mode); - xfree (r); - if (!rprev) - thecache = r2; - else - rprev->next = r2; - r = r2; - } - else + case CACHE_MODE_DATA: + case CACHE_MODE_PIN: + maxttl = 0; /* No MAX TTL here. */ + break; + case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break; + default: maxttl = opt.max_cache_ttl; break; + } + + if (maxttl) + { + if (r->created + maxttl < current) { - rprev = r; - r = r->next; + r->t.tv_sec = 0; + r->t.reason = CACHE_EXPIRE_CREATION; + return 1; } + + next = r->created + maxttl - current; + } + else + next = 0; + + if (r->ttl >= 0 && (next == 0 || r->ttl < next)) + { + r->t.tv_sec = r->ttl; + r->t.reason = CACHE_EXPIRE_LAST_ACCESS; + return 1; } + + if (next) + { + r->t.tv_sec = next; + r->t.reason = CACHE_EXPIRE_CREATION; + return 1; + } + + return 0; } +static void +update_expiration (ITEM entry, int is_new_entry) +{ + if (!is_new_entry) + { + remove_from_timer_list (entry); + remove_from_timer_list_new (entry); + } -void -agent_cache_housekeeping (void) + if (compute_expiration (entry)) + { + insert_to_timer_list_new (entry); + agent_kick_the_loop (); + } +} + + +/* Expire the cache entry. Returns 1 when the entry should be removed + * from the cache. */ +static int +do_expire (ITEM e) { - int res; + if (!e->pw) + /* Unused entry after 30 minutes. */ + return 1; - if (DBG_CACHE) - log_debug ("agent_cache_housekeeping\n"); + if (e->t.reason == CACHE_EXPIRE_LAST_ACCESS) + { + if (DBG_CACHE) + log_debug (" expired '%s'.%d (%ds after last access)\n", + e->key, e->restricted, e->ttl); + } + else + { + if (DBG_CACHE) + log_debug (" expired '%s'.%d (%lus after creation)\n", + e->key, e->restricted, opt.max_cache_ttl); + } + + release_data (e->pw); + e->pw = NULL; + e->accessed = 0; + + if (compute_expiration (e)) + insert_to_timer_list_new (e); + + return 0; +} + + +struct timespec * +agent_cache_expiration (void) +{ + static struct timespec abstime; + static struct timespec timeout; + struct timespec *tp; + struct timespec curtime; + int res; + int expired = 0; + ITEM e, enext; res = npth_mutex_lock (&cache_lock); if (res) log_fatal ("failed to acquire cache mutex: %s\n", strerror (res)); - housekeeping (); + npth_clock_gettime (&curtime); + if (the_timer_list) + { + if (npth_timercmp (&abstime, &curtime, <)) + expired = 1; + else + npth_timersub (&abstime, &curtime, &timeout); + } + + if (expired && (e = the_timer_list) && e->t.tv_sec == 0) + { + the_timer_list = e->t.next; + e->t.next = NULL; + + if (do_expire (e)) + { + ITEM r, rprev; + + if (DBG_CACHE) + log_debug (" removed '%s'.%d (mode %d) (slot not used for 30m)\n", + e->key, e->restricted, e->cache_mode); + + rprev = NULL; + for (r = thecache; r; r = r->next) + if (r == e) + { + if (!rprev) + thecache = r->next; + else + rprev->next = r->next; + break; + } + else + rprev = r; + + remove_from_timer_list_new (e); + + xfree (e); + } + } + + if (expired || !the_timer_list) + timeout.tv_sec = timeout.tv_nsec = 0; + + for (e = the_timer_list_new; e; e = enext) + { + enext = e->t.next; + e->t.next = NULL; + insert_to_timer_list (&timeout, e); + } + the_timer_list_new = NULL; + + if (!the_timer_list) + tp = NULL; + else + { + if (the_timer_list->t.tv_sec != 0) + { + timeout.tv_sec += the_timer_list->t.tv_sec; + the_timer_list->t.tv_sec = 0; + } + + npth_timeradd (&timeout, &curtime, &abstime); + tp = &timeout; + } res = npth_mutex_unlock (&cache_lock); if (res) log_fatal ("failed to release cache mutex: %s\n", strerror (res)); + + return tp; } @@ -314,6 +534,7 @@ agent_flush_cache (int pincache_only) release_data (r->pw); r->pw = NULL; r->accessed = 0; + update_expiration (r, 0); } } @@ -358,7 +579,6 @@ agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, if (DBG_CACHE) log_debug ("agent_put_cache '%s'.%d (mode %d) requested ttl=%d\n", key, restricted, cache_mode, ttl); - housekeeping (); if (!ttl) { @@ -410,6 +630,7 @@ agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, err = new_data (data, &r->pw); if (err) log_error ("error replacing cache item: %s\n", gpg_strerror (err)); + update_expiration (r, 0); } } else if (data) /* Insert. */ @@ -431,6 +652,7 @@ agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, { r->next = thecache; thecache = r; + update_expiration (r, 1); } } if (err) @@ -478,7 +700,6 @@ agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) log_debug ("agent_get_cache '%s'.%d (mode %d)%s ...\n", key, restricted, cache_mode, last_stored? " (stored cache key)":""); - housekeeping (); for (r=thecache; r; r = r->next) { @@ -500,7 +721,10 @@ agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) * below. Note also that we don't update the accessed time * for data items. */ if (r->cache_mode != CACHE_MODE_DATA) - r->accessed = gnupg_get_time (); + { + r->accessed = gnupg_get_time (); + update_expiration (r, 0); + } if (DBG_CACHE) log_debug ("... hit\n"); if (r->pw->totallen < 32) diff --git a/agent/call-daemon.c b/agent/call-daemon.c index e1c5669e9..806ef5dc1 100644 --- a/agent/call-daemon.c +++ b/agent/call-daemon.c @@ -98,7 +98,6 @@ static npth_mutex_t start_daemon_lock; struct wait_child_thread_parm_s { enum daemon_type type; - pid_t pid; }; @@ -109,54 +108,14 @@ wait_child_thread (void *arg) int err; struct wait_child_thread_parm_s *parm = arg; enum daemon_type type = parm->type; - pid_t pid = parm->pid; -#ifndef HAVE_W32_SYSTEM - int wstatus; -#endif const char *name = opt.daemon_program[type]; struct daemon_global_s *g = &daemon_global[type]; struct daemon_local_s *sl; xfree (parm); /* We have copied all data to the stack. */ -#ifdef HAVE_W32_SYSTEM - npth_unprotect (); - /* Note that although we use a pid_t here, it is actually a HANDLE. */ - WaitForSingleObject ((HANDLE)pid, INFINITE); - npth_protect (); + assuan_pipe_wait_server_termination (g->primary_ctx, NULL, 0); log_info ("daemon %s finished\n", name); -#else /* !HAVE_W32_SYSTEM*/ - - again: - npth_unprotect (); - err = waitpid (pid, &wstatus, 0); - npth_protect (); - - if (err < 0) - { - if (errno == EINTR) - goto again; - log_error ("waitpid for %s failed: %s\n", name, strerror (errno)); - return NULL; - } - else - { - if (WIFEXITED (wstatus)) - log_info ("daemon %s finished (status %d)\n", - name, WEXITSTATUS (wstatus)); - else if (WIFSIGNALED (wstatus)) - log_info ("daemon %s killed by signal %d\n", name, WTERMSIG (wstatus)); - else - { - if (WIFSTOPPED (wstatus)) - log_info ("daemon %s stopped by signal %d\n", - name, WSTOPSIG (wstatus)); - goto again; - } - - assuan_set_flag (g->primary_ctx, ASSUAN_NO_WAITPID, 1); - } -#endif /*!HAVE_W32_SYSTEM*/ agent_flush_cache (1); /* Flush the PIN cache. */ @@ -471,8 +430,8 @@ daemon_start (enum daemon_type type, ctrl_t ctrl) char buf[100]; #ifdef HAVE_W32_SYSTEM - snprintf (buf, sizeof buf, "OPTION event-signal=%lx", - (unsigned long)get_agent_daemon_notify_event ()); + snprintf (buf, sizeof buf, "OPTION event-signal=%p", + get_agent_daemon_notify_event ()); #else snprintf (buf, sizeof buf, "OPTION event-signal=%d", SIGUSR2); #endif @@ -496,7 +455,6 @@ daemon_start (enum daemon_type type, ctrl_t ctrl) } wctp->type = type; - wctp->pid = assuan_get_pid (g->primary_ctx); err = npth_attr_init (&tattr); if (!err) { @@ -561,10 +519,9 @@ agent_daemon_dump_state (void) for (i = 0; i < DAEMON_MAX_TYPE; i++) { struct daemon_global_s *g = &daemon_global[i]; - log_info ("%s: name %s primary_ctx=%p pid=%ld reusable=%d\n", __func__, + log_info ("%s: name %s primary_ctx=%p reusable=%d\n", __func__, gnupg_module_name (daemon_modules[i]), g->primary_ctx, - (long)assuan_get_pid (g->primary_ctx), g->primary_ctx_reusable); if (g->socket_name) log_info ("%s: socket='%s'\n", __func__, g->socket_name); diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index d236e1107..4a999ca9a 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -128,8 +128,9 @@ initialize_module_call_pinentry (void) void agent_query_dump_state (void) { - log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%p\n", - entry_ctx, (long)assuan_get_pid (entry_ctx), (void*)popup_tid); + log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%lx\n", + entry_ctx, (long)assuan_get_pid (entry_ctx), + (unsigned long)popup_tid); } /* Called to make sure that a popup window owned by the current @@ -1288,8 +1289,6 @@ build_cmd_setdesc (char *line, size_t linelen, const char *desc) static void * watch_sock (void *arg) { - pid_t pid = assuan_get_pid (entry_ctx); - while (1) { int err; @@ -1302,7 +1301,7 @@ watch_sock (void *arg) FD_ZERO (&fdset); FD_SET (FD2INT (sock), &fdset); - err = npth_select (FD2INT (sock)+1, &fdset, NULL, NULL, &timeout); + err = npth_select (FD2NUM (sock)+1, &fdset, NULL, NULL, &timeout); if (err < 0) { @@ -1317,17 +1316,7 @@ watch_sock (void *arg) break; } - if (pid == (pid_t)(-1)) - ; /* No pid available can't send a kill. */ -#ifdef HAVE_W32_SYSTEM - /* Older versions of assuan set PID to 0 on Windows to indicate an - invalid value. */ - else if (pid != (pid_t) INVALID_HANDLE_VALUE && pid != 0) - TerminateProcess ((HANDLE)pid, 1); -#else - else if (pid > 0) - kill (pid, SIGINT); -#endif + assuan_pipe_kill_server (entry_ctx); return NULL; } @@ -2124,7 +2113,6 @@ void agent_popup_message_stop (ctrl_t ctrl) { int rc; - pid_t pid; (void)ctrl; @@ -2137,26 +2125,10 @@ agent_popup_message_stop (ctrl_t ctrl) return; } - pid = assuan_get_pid (entry_ctx); - if (pid == (pid_t)(-1)) - ; /* No pid available can't send a kill. */ - else if (popup_finished) + if (popup_finished) ; /* Already finished and ready for joining. */ -#ifdef HAVE_W32_SYSTEM - /* Older versions of assuan set PID to 0 on Windows to indicate an - invalid value. */ - else if (pid != (pid_t) INVALID_HANDLE_VALUE - && pid != 0) - { - HANDLE process = (HANDLE) pid; - - /* Arbitrary error code. */ - TerminateProcess (process, 1); - } -#else - else if (pid > 0) - kill (pid, SIGINT); -#endif + else + assuan_pipe_kill_server (entry_ctx); /* Now wait for the thread to terminate. */ rc = npth_join (popup_tid, NULL); diff --git a/agent/call-scd.c b/agent/call-scd.c index 91e28e68c..3da16e619 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -548,7 +548,8 @@ padding_info_cb (void *opaque, const char *line) if ((s=has_leading_keyword (line, "PADDING"))) { - *r_padding = atoi (s); + if (r_padding) + *r_padding = atoi (s); } else if ((s=has_leading_keyword (line, "PINCACHE_PUT"))) err = handle_pincache_put (s); @@ -560,8 +561,8 @@ padding_info_cb (void *opaque, const char *line) /* Decipher INDATA using the current card. Note that the returned * value is not an s-expression but the raw data as returned by * scdaemon. The padding information is stored at R_PADDING with -1 - * for not known. DESC_TEXT is an additional parameter passed to - * GETPIN_CB. */ + * for not known, when it's not NULL. DESC_TEXT is an additional + * parameter passed to GETPIN_CB. */ int agent_card_pkdecrypt (ctrl_t ctrl, const char *keyid, @@ -579,7 +580,8 @@ agent_card_pkdecrypt (ctrl_t ctrl, size_t len; *r_buf = NULL; - *r_padding = -1; /* Unknown. */ + if (r_padding) + *r_padding = -1; /* Unknown. */ rc = start_scd (ctrl); if (rc) return rc; diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 189acd7f8..02f069dea 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -3952,7 +3952,11 @@ start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client) es_syshd_t syshd; syshd.type = ES_SYSHD_SOCK; +#ifdef HAVE_SOCKET + syshd.u.sock = (SOCKET)sock_client; +#else syshd.u.sock = sock_client; +#endif get_client_info (sock_client, &peer_info); ctrl->client_pid = peer_info.pid; diff --git a/agent/command.c b/agent/command.c index 40322f385..b152a5ea4 100644 --- a/agent/command.c +++ b/agent/command.c @@ -241,7 +241,7 @@ reset_notify (assuan_context_t ctx, char *line) (void) line; memset (ctrl->keygrip, 0, 20); - ctrl->have_keygrip = 0; + ctrl->have_keygrip = ctrl->have_keygrip1 = 0; ctrl->digest.valuelen = 0; xfree (ctrl->digest.data); ctrl->digest.data = NULL; @@ -796,8 +796,8 @@ cmd_havekey (assuan_context_t ctx, char *line) static const char hlp_sigkey[] = - "SIGKEY <hexstring_with_keygrip>\n" - "SETKEY <hexstring_with_keygrip>\n" + "SIGKEY [--another] <hexstring_with_keygrip>\n" + "SETKEY [--another] <hexstring_with_keygrip>\n" "\n" "Set the key used for a sign or decrypt operation."; static gpg_error_t @@ -805,11 +805,17 @@ cmd_sigkey (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); + int opt_another; - rc = parse_keygrip (ctx, line, ctrl->keygrip); + opt_another = has_option (line, "--another"); + line = skip_options (line); + rc = parse_keygrip (ctx, line, opt_another? ctrl->keygrip1 : ctrl->keygrip); if (rc) return rc; - ctrl->have_keygrip = 1; + if (opt_another) + ctrl->have_keygrip1 = 1; + else + ctrl->have_keygrip = 1; return 0; } @@ -1043,10 +1049,14 @@ cmd_pksign (assuan_context_t ctx, char *line) static const char hlp_pkdecrypt[] = - "PKDECRYPT [<options>]\n" + "PKDECRYPT [--kem[=<kemid>] [<options>]\n" "\n" "Perform the actual decrypt operation. Input is not\n" - "sensitive to eavesdropping."; + "sensitive to eavesdropping.\n" + "If the --kem option is used, decryption is done with the KEM,\n" + "inquiring upper-layer option, when needed. KEMID can be\n" + "specified with --kem option; Valid value is: PQC-PGP, PGP, or CMS.\n" + "Default is PQC-PGP."; static gpg_error_t cmd_pkdecrypt (assuan_context_t ctx, char *line) { @@ -1055,22 +1065,52 @@ cmd_pkdecrypt (assuan_context_t ctx, char *line) unsigned char *value; size_t valuelen; membuf_t outbuf; - int padding; + int padding = -1; + unsigned char *option = NULL; + size_t optionlen = 0; + const char *p; + int kemid = -1; - (void)line; + p = has_option_name (line, "--kem"); + if (p) + { + kemid = KEM_PQC_PGP; + if (*p == '=') + { + p++; + if (!strcmp (p, "PQC-PGP")) + kemid = KEM_PQC_PGP; + else if (!strcmp (p, "PGP")) + kemid = KEM_PGP; + else if (!strcmp (p, "CMS")) + kemid = KEM_CMS; + else + return set_error (GPG_ERR_ASS_PARAMETER, "invalid KEM algorithm"); + } + } /* First inquire the data to decrypt */ rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_CIPHERTEXT); if (!rc) rc = assuan_inquire (ctx, "CIPHERTEXT", &value, &valuelen, MAXLEN_CIPHERTEXT); + if (!rc && kemid > KEM_PQC_PGP) + rc = assuan_inquire (ctx, "OPTION", + &option, &optionlen, MAXLEN_CIPHERTEXT); if (rc) return rc; init_membuf (&outbuf, 512); - rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc, - value, valuelen, &outbuf, &padding); + if (kemid < 0) + rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc, + value, valuelen, &outbuf, &padding); + else + { + rc = agent_kem_decrypt (ctrl, ctrl->server_local->keydesc, kemid, + value, valuelen, option, optionlen, &outbuf); + xfree (option); + } xfree (value); if (rc) clear_outbuf (&outbuf); diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c index 50755c0fd..420dbb464 100644 --- a/agent/cvt-openpgp.c +++ b/agent/cvt-openpgp.c @@ -1384,6 +1384,17 @@ extract_private_key (gcry_sexp_t s_key, int req_private_key_data, err = gcry_sexp_extract_param (list, NULL, format, array+0, array+1, NULL); } + else if ( !strcmp (name, (algoname = "kyber512")) + || !strcmp (name, (algoname = "kyber768")) + || !strcmp (name, (algoname = "kyber1024"))) + { + format = "/ps?"; + elems = "ps?"; + npkey = 1; + nskey = 2; + err = gcry_sexp_extract_param (list, NULL, format, + array+0, array+1, NULL); + } else { err = gpg_error (GPG_ERR_PUBKEY_ALGO); diff --git a/agent/divert-scd.c b/agent/divert-scd.c index ed0173ea1..d8c2bcca7 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -377,10 +377,10 @@ divert_pksign (ctrl_t ctrl, const unsigned char *grip, } -/* Decrypt the value given asn an S-expression in CIPHER using the +/* Decrypt the value given as an s-expression in CIPHER using the key identified by SHADOW_INFO and return the plaintext in an allocated buffer in R_BUF. The padding information is stored at - R_PADDING with -1 for not known. */ + R_PADDING with -1 for not known, when it's not NULL. */ int divert_pkdecrypt (ctrl_t ctrl, const unsigned char *grip, @@ -399,7 +399,8 @@ divert_pkdecrypt (ctrl_t ctrl, bin2hex (grip, 20, hexgrip); - *r_padding = -1; + if (r_padding) + *r_padding = -1; s = cipher; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); @@ -485,6 +486,34 @@ divert_pkdecrypt (ctrl_t ctrl, return rc; } +gpg_error_t +agent_card_ecc_kem (ctrl_t ctrl, const unsigned char *ecc_ct, + size_t ecc_point_len, unsigned char *ecc_ecdh) +{ + gpg_error_t err = 0; + char *ecdh = NULL; + size_t len; + int rc; + + rc = agent_card_pkdecrypt (ctrl, ctrl->keygrip, getpin_cb, ctrl, NULL, + ecc_ct, ecc_point_len, &ecdh, &len, NULL); + if (rc) + return rc; + + if (len != ecc_point_len) + { + if (opt.verbose) + log_info ("%s: ECC result length invalid (%zu != %zu)\n", + __func__, len, ecc_point_len); + return gpg_error (GPG_ERR_INV_DATA); + } + else + memcpy (ecc_ecdh, ecdh, len); + + xfree (ecdh); + return err; +} + gpg_error_t divert_writekey (ctrl_t ctrl, int force, const char *serialno, diff --git a/agent/divert-tpm2.c b/agent/divert-tpm2.c index 2496d091a..6ebb9ef78 100644 --- a/agent/divert-tpm2.c +++ b/agent/divert-tpm2.c @@ -106,7 +106,8 @@ divert_tpm2_pkdecrypt (ctrl_t ctrl, const unsigned char *s; size_t n; - *r_padding = -1; + if (r_padding) + *r_padding = -1; s = cipher; if (*s != '(') @@ -125,7 +126,8 @@ divert_tpm2_pkdecrypt (ctrl_t ctrl, return gpg_error (GPG_ERR_INV_SEXP); if (smatch (&s, n, "rsa")) { - *r_padding = 0; + if (r_padding) + *r_padding = 0; if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); s++; diff --git a/agent/genkey.c b/agent/genkey.c index 444f89f79..503a7eb53 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -161,7 +161,7 @@ do_check_passphrase_pattern (ctrl_t ctrl, const char *pw, unsigned int flags) const char *pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CHECK_PATTERN); estream_t stream_to_check_pattern = NULL; const char *argv[10]; - pid_t pid; + gnupg_process_t proc; int result, i; const char *pattern; char *patternfname; @@ -204,11 +204,17 @@ do_check_passphrase_pattern (ctrl_t ctrl, const char *pw, unsigned int flags) argv[i] = NULL; log_assert (i < sizeof argv); - if (gnupg_spawn_process (pgmname, argv, NULL, 0, - &stream_to_check_pattern, NULL, NULL, &pid)) + if (gnupg_process_spawn (pgmname, argv, + GNUPG_PROCESS_STDIN_PIPE, + NULL, NULL, &proc)) result = 1; /* Execute error - assume password should no be used. */ else { + int status; + + gnupg_process_get_streams (proc, 0, &stream_to_check_pattern, + NULL, NULL); + es_set_binary (stream_to_check_pattern); if (es_fwrite (pw, strlen (pw), 1, stream_to_check_pattern) != 1) { @@ -219,11 +225,13 @@ do_check_passphrase_pattern (ctrl_t ctrl, const char *pw, unsigned int flags) else es_fflush (stream_to_check_pattern); es_fclose (stream_to_check_pattern); - if (gnupg_wait_process (pgmname, pid, 1, NULL)) + gnupg_process_wait (proc, 1); + gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &status); + if (status) result = 1; /* Helper returned an error - probably a match. */ else result = 0; /* Success; i.e. no match. */ - gnupg_release_process (pid); + gnupg_process_release (proc); } xfree (patternfname); diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 5305098d2..3c71ba65d 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -341,15 +341,13 @@ static struct debug_flags_s debug_flags [] = #define MIN_PASSPHRASE_NONALPHA (1) #define MAX_PASSPHRASE_DAYS (0) -/* The timer tick used for housekeeping stuff. Note that on Windows - * we use a SetWaitableTimer seems to signal earlier than about 2 - * seconds. Thus we use 4 seconds on all platforms. - * CHECK_OWN_SOCKET_INTERVAL defines how often we check - * our own socket in standard socket mode. If that value is 0 we - * don't check at all. All values are in seconds. */ -#define TIMERTICK_INTERVAL (4) +/* CHECK_OWN_SOCKET_INTERVAL defines how often we check our own socket + * in standard socket mode. If that value is 0 we don't check at all. + * Values is in seconds. */ #define CHECK_OWN_SOCKET_INTERVAL (60) - +/* CHECK_PROBLEMS_INTERVAL defines how often we check the existence of + * parent process and homedir. Value is in seconds. */ +#define CHECK_PROBLEMS_INTERVAL (4) /* Flag indicating that the ssh-agent subsystem has been enabled. */ static int ssh_support; @@ -384,9 +382,6 @@ static int startup_signal_mask_valid; /* Flag to indicate that a shutdown was requested. */ static int shutdown_pending; -/* Counter for the currently running own socket checks. */ -static int check_own_socket_running; - /* Flags to indicate that check_own_socket shall not be called. */ static int disable_check_own_socket; @@ -396,6 +391,12 @@ static int is_supervised; /* Flag indicating to start the daemon even if one already runs. */ static int steal_socket; +/* Flag to monitor problems. */ +static int problem_detected; +#define AGENT_PROBLEM_SOCKET_TAKEOVER (1 << 0) +#define AGENT_PROBLEM_PARENT_HAS_GONE (1 << 1) +#define AGENT_PROBLEM_HOMEDIR_REMOVED (1 << 2) + /* Flag to inhibit socket removal in cleanup. */ static int inhibit_socket_removal; @@ -432,6 +433,17 @@ static assuan_sock_nonce_t socket_nonce_ssh; * Let's try this as default. Change at runtime with --listen-backlog. */ static int listen_backlog = 64; +#ifdef HAVE_W32_SYSTEM +/* The event to break the select call. */ +static HANDLE the_event2; +#elif defined(HAVE_PSELECT_NO_EINTR) +/* An FD to break the select call. */ +static int event_pipe_fd; +#else +/* PID of the main thread. */ +static pid_t main_thread_pid; +#endif + /* Default values for options passed to the pinentry. */ static char *default_display; static char *default_ttyname; @@ -452,9 +464,14 @@ static const char *debug_level; the log file after a SIGHUP if it didn't changed. Malloced. */ static char *current_logfile; -/* The handle_tick() function may test whether a parent is still - * running. We record the PID of the parent here or -1 if it should - * be watched. */ +#ifdef HAVE_W32_SYSTEM +#define HAVE_PARENT_PID_SUPPORT 0 +#else +#define HAVE_PARENT_PID_SUPPORT 1 +#endif +/* The check_others_thread() function may test whether a parent is + * still running. We record the PID of the parent here or -1 if it + * should be watched. */ static pid_t parent_pid = (pid_t)(-1); /* This flag is true if the inotify mechanism for detecting the @@ -462,11 +479,6 @@ static pid_t parent_pid = (pid_t)(-1); * alternative but portable stat based check. */ static int have_homedir_inotify; -/* Depending on how gpg-agent was started, the homedir inotify watch - * may not be reliable. This flag is set if we assume that inotify - * works reliable. */ -static int reliable_homedir_inotify; - /* Number of active connections. */ static int active_connections; @@ -516,13 +528,13 @@ static void agent_deinit_default_ctrl (ctrl_t ctrl); static void handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_extra, gnupg_fd_t listen_fd_browser, - gnupg_fd_t listen_fd_ssh); -static void check_own_socket (void); + gnupg_fd_t listen_fd_ssh, + int reliable_homedir_inotify); static int check_for_running_agent (int silent); - -/* Pth wrapper function definitions. */ -ASSUAN_SYSTEM_NPTH_IMPL; - +#if CHECK_OWN_SOCKET_INTERVAL > 0 +static void *check_own_socket_thread (void *arg); +#endif +static void *check_others_thread (void *arg); /* Functions. @@ -1050,6 +1062,7 @@ thread_init_once (void) * initialized and thus Libgcrypt could not set its system call * clamp. */ gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0); + assuan_control (ASSUAN_CONTROL_REINIT_SYSCALL_CLAMP, NULL); } @@ -1057,7 +1070,6 @@ static void initialize_modules (void) { thread_init_once (); - assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); initialize_module_cache (); initialize_module_call_pinentry (); initialize_module_daemon (); @@ -1085,6 +1097,7 @@ main (int argc, char **argv) int gpgconf_list = 0; gpg_error_t err; struct assuan_malloc_hooks malloc_hooks; + int reliable_homedir_inotify = 1; early_system_init (); @@ -1117,7 +1130,6 @@ main (int argc, char **argv) assuan_set_malloc_hooks (&malloc_hooks); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); assuan_sock_init (); - assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH); setup_libassuan_logging (&opt.debug, NULL); setup_libgcrypt_logging (); @@ -1583,7 +1595,7 @@ main (int argc, char **argv) log_info ("listening on: std=%d extra=%d browser=%d ssh=%d\n", fd, fd_extra, fd_browser, fd_ssh); - handle_connections (fd, fd_extra, fd_browser, fd_ssh); + handle_connections (fd, fd_extra, fd_browser, fd_ssh, 1); #endif /*!HAVE_W32_SYSTEM*/ } else if (!is_daemon) @@ -1811,14 +1823,14 @@ main (int argc, char **argv) log_get_prefix (&oldflags); log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED); opt.running_detached = 1; - - /* Unless we are running with a program given on the command - * line we can assume that the inotify things works and thus - * we can avoid the regular stat calls. */ - if (!argc) - reliable_homedir_inotify = 1; } + /* When we are running with a program given on the command + * line, the inotify things may not work well and thus + * we cannot avoid the regular stat calls. */ + if (argc) + reliable_homedir_inotify = 0; + { struct sigaction sa; @@ -1837,7 +1849,8 @@ main (int argc, char **argv) } log_info ("%s %s started\n", gpgrt_strusage(11), gpgrt_strusage(13) ); - handle_connections (fd, fd_extra, fd_browser, fd_ssh); + handle_connections (fd, fd_extra, fd_browser, fd_ssh, + reliable_homedir_inotify); assuan_sock_close (fd); } @@ -2139,39 +2152,45 @@ get_agent_active_connection_count (void) notification event. Calling it the first time creates that event. */ #if defined(HAVE_W32_SYSTEM) +static void * +create_an_event (void) +{ + HANDLE h, h2; + SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; + + /* We need to use a manual reset event object due to the way our + w32-pth wait function works: If we would use an automatic + reset event we are not able to figure out which handle has + been signaled because at the time we single out the signaled + handles using WFSO the event has already been reset due to + the WFMO. */ + h = CreateEvent (&sa, TRUE, FALSE, NULL); + if (!h) + log_error ("can't create an event: %s\n", w32_strerror (-1) ); + else if (!DuplicateHandle (GetCurrentProcess(), h, + GetCurrentProcess(), &h2, + EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) + { + log_error ("setting synchronize for an event failed: %s\n", + w32_strerror (-1) ); + CloseHandle (h); + } + else + { + CloseHandle (h); + return h2; + } + + return INVALID_HANDLE_VALUE; +} + void * get_agent_daemon_notify_event (void) { static HANDLE the_event = INVALID_HANDLE_VALUE; if (the_event == INVALID_HANDLE_VALUE) - { - HANDLE h, h2; - SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; - - /* We need to use a manual reset event object due to the way our - w32-pth wait function works: If we would use an automatic - reset event we are not able to figure out which handle has - been signaled because at the time we single out the signaled - handles using WFSO the event has already been reset due to - the WFMO. */ - h = CreateEvent (&sa, TRUE, FALSE, NULL); - if (!h) - log_error ("can't create scd notify event: %s\n", w32_strerror (-1) ); - else if (!DuplicateHandle (GetCurrentProcess(), h, - GetCurrentProcess(), &h2, - EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) - { - log_error ("setting synchronize for scd notify event failed: %s\n", - w32_strerror (-1) ); - CloseHandle (h); - } - else - { - CloseHandle (h); - the_event = h2; - } - } + the_event = create_an_event (); return the_event; } @@ -2428,57 +2447,6 @@ create_directories (void) } - -/* This is the worker for the ticker. It is called every few seconds - and may only do fast operations. */ -static void -handle_tick (void) -{ - static time_t last_minute; - struct stat statbuf; - - if (!last_minute) - last_minute = time (NULL); - - /* If we are running as a child of another process, check whether - the parent is still alive and shutdown if not. */ -#ifndef HAVE_W32_SYSTEM - if (parent_pid != (pid_t)(-1)) - { - if (kill (parent_pid, 0)) - { - shutdown_pending = 2; - log_info ("parent process died - shutting down\n"); - log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13)); - cleanup (); - agent_exit (0); - } - } -#endif /*HAVE_W32_SYSTEM*/ - - /* Code to be run from time to time. */ -#if CHECK_OWN_SOCKET_INTERVAL > 0 - if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL)) - { - check_own_socket (); - last_minute = time (NULL); - } -#endif - - /* Need to check for expired cache entries. */ - agent_cache_housekeeping (); - - /* Check whether the homedir is still available. */ - if (!shutdown_pending - && (!have_homedir_inotify || !reliable_homedir_inotify) - && gnupg_stat (gnupg_homedir (), &statbuf) && errno == ENOENT) - { - shutdown_pending = 1; - log_info ("homedir has been removed - shutting down\n"); - } -} - - /* A global function which allows us to call the reload stuff from other places too. This is only used when build for W32. */ void @@ -2537,6 +2505,11 @@ handle_signal (int signo) agent_sigusr2_action (); break; + case SIGCONT: + /* Do nothing, but break the syscall. */ + log_debug ("SIGCONT received - breaking select\n"); + break; + case SIGTERM: if (!shutdown_pending) log_info ("SIGTERM received - shutting down ...\n"); @@ -2574,7 +2547,7 @@ check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce) if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce)) { log_info (_("error reading nonce on fd %d: %s\n"), - FD2INT(ctrl->thread_startup.fd), strerror (errno)); + FD_DBG (ctrl->thread_startup.fd), strerror (errno)); assuan_sock_close (ctrl->thread_startup.fd); xfree (ctrl); return -1; @@ -2874,12 +2847,12 @@ do_start_connection_thread (ctrl_t ctrl) agent_init_default_ctrl (ctrl); if (opt.verbose > 1 && !DBG_IPC) log_info (_("handler 0x%lx for fd %d started\n"), - (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + (unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd)); start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd); if (opt.verbose > 1 && !DBG_IPC) log_info (_("handler 0x%lx for fd %d terminated\n"), - (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + (unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd)); agent_deinit_default_ctrl (ctrl); xfree (ctrl); @@ -2954,12 +2927,12 @@ start_connection_thread_ssh (void *arg) agent_init_default_ctrl (ctrl); if (opt.verbose) log_info (_("ssh handler 0x%lx for fd %d started\n"), - (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + (unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd)); start_command_handler_ssh (ctrl, ctrl->thread_startup.fd); if (opt.verbose) log_info (_("ssh handler 0x%lx for fd %d terminated\n"), - (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + (unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd)); agent_deinit_default_ctrl (ctrl); xfree (ctrl); @@ -2968,13 +2941,36 @@ start_connection_thread_ssh (void *arg) } +void +agent_kick_the_loop (void) +{ + /* Kick the select loop. */ +#ifdef HAVE_W32_SYSTEM + int ret = SetEvent (the_event2); + if (ret == 0) + log_error ("SetEvent for agent_kick_the_loop failed: %s\n", + w32_strerror (-1)); +#else +# ifdef HAVE_PSELECT_NO_EINTR + write (event_pipe_fd, "", 1); +# else + int ret = kill (main_thread_pid, SIGCONT); + if (ret < 0) + log_error ("sending signal for agent_kick_the_loop failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); +# endif +#endif +} + + /* Connection handler loop. Wait for connection requests and spawn a thread after accepting a connection. */ static void handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_extra, gnupg_fd_t listen_fd_browser, - gnupg_fd_t listen_fd_ssh) + gnupg_fd_t listen_fd_ssh, + int reliable_homedir_inotify) { gpg_error_t err; npth_attr_t tattr; @@ -2985,12 +2981,15 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t fd; int nfd; int saved_errno; - struct timespec abstime; - struct timespec curtime; - struct timespec timeout; + struct timespec *tp; #ifdef HAVE_W32_SYSTEM - HANDLE events[2]; + HANDLE events[3]; unsigned int events_set; +#else + int signo; +# ifdef HAVE_PSELECT_NO_EINTR + int pipe_fd[2]; +# endif #endif int sock_inotify_fd = -1; int home_inotify_fd = -1; @@ -3018,11 +3017,24 @@ handle_connections (gnupg_fd_t listen_fd, npth_sigev_add (SIGUSR1); npth_sigev_add (SIGUSR2); npth_sigev_add (SIGINT); + npth_sigev_add (SIGCONT); npth_sigev_add (SIGTERM); npth_sigev_fini (); +# ifdef HAVE_PSELECT_NO_EINTR + ret = gnupg_create_pipe (pipe_fd); + if (ret) + { + log_error ("pipe creation failed: %s\n", gpg_strerror (ret)); + return; + } + event_pipe_fd = pipe_fd[1]; +# else + main_thread_pid = getpid (); +# endif #else events[0] = get_agent_daemon_notify_event (); - events[1] = INVALID_HANDLE_VALUE; + events[1] = the_event2 = create_an_event (); + events[2] = INVALID_HANDLE_VALUE; #endif if (disable_check_own_socket) @@ -3034,7 +3046,7 @@ handle_connections (gnupg_fd_t listen_fd, gpg_strerror (err)); } - if (disable_check_own_socket) + if (!reliable_homedir_inotify) home_inotify_fd = -1; else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd, gnupg_homedir ()))) @@ -3046,6 +3058,27 @@ handle_connections (gnupg_fd_t listen_fd, else have_homedir_inotify = 1; +#if CHECK_OWN_SOCKET_INTERVAL > 0 + if (!disable_check_own_socket && sock_inotify_fd == -1) + { + npth_t thread; + + err = npth_create (&thread, &tattr, check_own_socket_thread, NULL); + if (err) + log_error ("error spawning check_own_socket_thread: %s\n", strerror (err)); + } +#endif + + if ((HAVE_PARENT_PID_SUPPORT && parent_pid != (pid_t)(-1)) + || !have_homedir_inotify) + { + npth_t thread; + + err = npth_create (&thread, &tattr, check_others_thread, NULL); + if (err) + log_error ("error spawning check_others_thread: %s\n", strerror (err)); + } + /* On Windows we need to fire up a separate thread to listen for requests from Putty (an SSH client), so we can replace Putty's Pageant (its ssh-agent implementation). */ @@ -3075,24 +3108,24 @@ handle_connections (gnupg_fd_t listen_fd, FD_ZERO (&fdset); FD_SET (FD2INT (listen_fd), &fdset); - nfd = FD2INT (listen_fd); + nfd = FD2NUM (listen_fd); if (listen_fd_extra != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_extra), &fdset); if (FD2INT (listen_fd_extra) > nfd) - nfd = FD2INT (listen_fd_extra); + nfd = FD2NUM (listen_fd_extra); } if (listen_fd_browser != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_browser), &fdset); if (FD2INT (listen_fd_browser) > nfd) - nfd = FD2INT (listen_fd_browser); + nfd = FD2NUM (listen_fd_browser); } if (listen_fd_ssh != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_ssh), &fdset); if (FD2INT (listen_fd_ssh) > nfd) - nfd = FD2INT (listen_fd_ssh); + nfd = FD2NUM (listen_fd_ssh); } if (sock_inotify_fd != -1) { @@ -3112,15 +3145,12 @@ handle_connections (gnupg_fd_t listen_fd, listentbl[2].l_fd = listen_fd_browser; listentbl[3].l_fd = listen_fd_ssh; - npth_clock_gettime (&abstime); - abstime.tv_sec += TIMERTICK_INTERVAL; - for (;;) { /* Shutdown test. */ if (shutdown_pending) { - if (active_connections == 0) + if (active_connections == 0 || is_supervised) break; /* ready */ /* Do not accept new connections but keep on running the @@ -3149,28 +3179,23 @@ handle_connections (gnupg_fd_t listen_fd, thus a simple assignment is fine to copy the entire set. */ read_fdset = fdset; - npth_clock_gettime (&curtime); - if (!(npth_timercmp (&curtime, &abstime, <))) - { - /* Timeout. */ - handle_tick (); - npth_clock_gettime (&abstime); - abstime.tv_sec += TIMERTICK_INTERVAL; - } - npth_timersub (&abstime, &curtime, &timeout); +#ifdef HAVE_PSELECT_NO_EINTR + FD_SET (pipe_fd[0], &read_fdset); + if (nfd < pipe_fd[0]) + nfd = pipe_fd[0]; +#endif + + tp = agent_cache_expiration (); #ifndef HAVE_W32_SYSTEM - ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, + ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, tp, npth_sigev_sigmask ()); saved_errno = errno; - { - int signo; - while (npth_sigev_get_pending (&signo)) - handle_signal (signo); - } + while (npth_sigev_get_pending (&signo)) + handle_signal (signo); #else - ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, + ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, tp, events, &events_set); saved_errno = errno; @@ -3186,11 +3211,47 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_sleep (1); continue; } + +#ifndef HAVE_W32_SYSTEM + if ((problem_detected & AGENT_PROBLEM_PARENT_HAS_GONE)) + { + shutdown_pending = 2; + log_info ("parent process died - shutting down\n"); + log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13)); + cleanup (); + agent_exit (0); + } +#endif + + if ((problem_detected & AGENT_PROBLEM_SOCKET_TAKEOVER)) + { + /* We may not remove the socket as it is now in use by another + server. */ + inhibit_socket_removal = 1; + shutdown_pending = 2; + log_info ("this process is useless - shutting down\n"); + } + + if ((problem_detected & AGENT_PROBLEM_HOMEDIR_REMOVED)) + { + shutdown_pending = 1; + log_info ("homedir has been removed - shutting down\n"); + } + if (ret <= 0) /* Interrupt or timeout. Will be handled when calculating the next timeout. */ continue; +#ifdef HAVE_PSELECT_NO_EINTR + if (FD_ISSET (pipe_fd[0], &read_fdset)) + { + char buf[256]; + + read (pipe_fd[0], buf, sizeof buf); + } +#endif + /* The inotify fds are set even when a shutdown is pending (see * above). So we must handle them in any case. To avoid that * they trigger a second time we close them immediately. */ @@ -3198,7 +3259,10 @@ handle_connections (gnupg_fd_t listen_fd, && FD_ISSET (sock_inotify_fd, &read_fdset) && gnupg_inotify_has_name (sock_inotify_fd, GPG_AGENT_SOCK_NAME)) { - shutdown_pending = 1; + /* We may not remove the socket (if any), as it may be now + in use by another server. */ + inhibit_socket_removal = 1; + shutdown_pending = 2; close (sock_inotify_fd); sock_inotify_fd = -1; log_info ("socket file has been removed - shutting down\n"); @@ -3227,8 +3291,8 @@ handle_connections (gnupg_fd_t listen_fd, continue; plen = sizeof paddr; - fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd), - (struct sockaddr *)&paddr, &plen)); + fd = assuan_sock_accept (listentbl[idx].l_fd, + (struct sockaddr *)&paddr, &plen); if (fd == GNUPG_INVALID_FD) { log_error ("accept failed for %s: %s\n", @@ -3268,13 +3332,21 @@ handle_connections (gnupg_fd_t listen_fd, close (sock_inotify_fd); if (home_inotify_fd != -1) close (home_inotify_fd); +#ifdef HAVE_W32_SYSTEM + if (the_event2 != INVALID_HANDLE_VALUE) + CloseHandle (the_event2); +#endif +#ifdef HAVE_PSELECT_NO_EINTR + close (pipe_fd[0]); + close (pipe_fd[1]); +#endif cleanup (); log_info (_("%s %s stopped\n"), gpgrt_strusage(11), gpgrt_strusage(13)); npth_attr_destroy (&tattr); } - +#if CHECK_OWN_SOCKET_INTERVAL > 0 /* Helper for check_own_socket. */ static gpg_error_t check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length) @@ -3285,20 +3357,18 @@ check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length) } -/* The thread running the actual check. We need to run this in a - separate thread so that check_own_thread can be called from the - timer tick. */ -static void * -check_own_socket_thread (void *arg) +/* Check whether we are still listening on our own socket. In case + another gpg-agent process started after us has taken ownership of + our socket, we would linger around without any real task. Thus we + better check once in a while whether we are really needed. */ +static int +do_check_own_socket (const char *sockname) { int rc; - char *sockname = arg; assuan_context_t ctx = NULL; membuf_t mb; char *buffer; - check_own_socket_running++; - rc = assuan_new (&ctx); if (rc) { @@ -3336,58 +3406,78 @@ check_own_socket_thread (void *arg) xfree (buffer); leave: - xfree (sockname); if (ctx) assuan_release (ctx); - if (rc) - { - /* We may not remove the socket as it is now in use by another - server. */ - inhibit_socket_removal = 1; - shutdown_pending = 2; - log_info ("this process is useless - shutting down\n"); - } - check_own_socket_running--; - return NULL; -} + return rc; +} -/* Check whether we are still listening on our own socket. In case - another gpg-agent process started after us has taken ownership of - our socket, we would linger around without any real task. Thus we - better check once in a while whether we are really needed. */ -static void -check_own_socket (void) +/* The thread running the actual check. */ +static void * +check_own_socket_thread (void *arg) { char *sockname; - npth_t thread; - npth_attr_t tattr; - int err; - - if (disable_check_own_socket) - return; - if (check_own_socket_running || shutdown_pending) - return; /* Still running or already shutting down. */ + (void)arg; sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); if (!sockname) - return; /* Out of memory. */ + return NULL; /* Out of memory. */ - err = npth_attr_init (&tattr); - if (err) + while (!problem_detected) { - xfree (sockname); - return; + if (shutdown_pending) + goto leave; + + gnupg_sleep (CHECK_OWN_SOCKET_INTERVAL); + + if (do_check_own_socket (sockname)) + problem_detected |= AGENT_PROBLEM_SOCKET_TAKEOVER; } - npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); - err = npth_create (&thread, &tattr, check_own_socket_thread, sockname); - if (err) - log_error ("error spawning check_own_socket_thread: %s\n", strerror (err)); - npth_attr_destroy (&tattr); + + agent_kick_the_loop (); + + leave: + xfree (sockname); + return NULL; } +#endif + +/* The thread running other checks. */ +static void * +check_others_thread (void *arg) +{ + const char *homedir = gnupg_homedir (); + + (void)arg; + + while (!problem_detected) + { + struct stat statbuf; + + if (shutdown_pending) + goto leave; + + gnupg_sleep (CHECK_PROBLEMS_INTERVAL); + + /* If we are running as a child of another process, check whether + the parent is still alive and shutdown if not. */ +#ifndef HAVE_W32_SYSTEM + if (parent_pid != (pid_t)(-1) && kill (parent_pid, 0)) + problem_detected |= AGENT_PROBLEM_PARENT_HAS_GONE; +#endif /*HAVE_W32_SYSTEM*/ + + /* Check whether the homedir is still available. */ + if (!have_homedir_inotify + && gnupg_stat (homedir, &statbuf) && errno == ENOENT) + problem_detected |= AGENT_PROBLEM_HOMEDIR_REMOVED; + } + agent_kick_the_loop (); + leave: + return NULL; +} /* Figure out whether an agent is available and running. Prints an error if not. If SILENT is true, no messages are printed. diff --git a/agent/keyformat.txt b/agent/keyformat.txt deleted file mode 100644 index e0c4df0f0..000000000 --- a/agent/keyformat.txt +++ /dev/null @@ -1,520 +0,0 @@ -keyformat.txt emacs, please switch to -*- org -*- mode -------------- - - -Some notes on the format of the secret keys used with gpg-agent. - -* Location of keys - -The secret keys[1] are stored on a per file basis in a directory below -the ~/.gnupg home directory. This directory is named - - private-keys-v1.d - -and should have permissions 700. - -The secret keys are stored in files with a name matching the -hexadecimal representation of the keygrip[2] and suffixed with ".key". - -* Extended Private Key Format - -** Overview -GnuPG 2.3+ uses a new format to store private keys that is both -more flexible and easier to read and edit by human beings. The new -format stores name,value-pairs using the common mail and http header -convention. Example (here indented with two spaces): - - Description: Key to sign all GnuPG released tarballs. - The key is actually stored on a smart card. - Use-for-ssh: yes - OpenSSH-cert: long base64 encoded string wrapped so that this - key file can be easily edited with a standard editor. - Token: D2760001240102000005000011730000 OPENPGP.1 - - Token: FF020001008A77C1 PIV.9C - - Key: (shadowed-private-key - (rsa - (n #00AA1AD2A55FD8C8FDE9E1941772D9CC903FA43B268CB1B5A1BAFDC900 - 2961D8AEA153424DC851EF13B83AC64FBE365C59DC1BD3E83017C90D4365B4 - 83E02859FC13DB5842A00E969480DB96CE6F7D1C03600392B8E08EF0C01FC7 - 19F9F9086B25AD39B4F1C2A2DF3E2BE317110CFFF21D4A11455508FE407997 - 601260816C8422297C0637BB291C3A079B9CB38A92CE9E551F80AA0EBF4F0E - 72C3F250461E4D31F23A7087857FC8438324A013634563D34EFDDCBF2EA80D - F9662C9CCD4BEF2522D8BDFED24CEF78DC6B309317407EAC576D889F88ADA0 - 8C4FFB480981FB68C5C6CA27503381D41018E6CDC52AAAE46B166BDC10637A - E186A02BA2497FDC5D1221#) - (e #00010001#) - (shadowed t1-v1 - (#D2760001240102000005000011730000# OPENPGP.1) - ))) - -GnuPG 2.2 is also able to read and write keys using the new format -However, it only makes use of some of the values. - -Keys in the extended format can be recognized by looking at the first -byte of the file. If it starts with a '(' it is a naked S-expression, -otherwise it is a key in extended format. - -*** Names -A name must start with a letter and end with a colon. Valid -characters are all ASCII letters, numbers and the hyphen. Comparison -of names is done case insensitively. Names may be used several times -to represent an array of values. Note that the name "Key" is special -in that it is madandory must occur only once. - -*** Values -Values are UTF-8 encoded strings. Values can be wrapped at any point, -and continued in the next line indicated by leading whitespace. A -continuation line with one leading space does not introduce a blank so -that the lines can be effectively concatenated. A blank line as part -of a continuation line encodes a newline. - -*** Comments -Lines containing only whitespace, and lines starting with whitespace -followed by '#' are considered to be comments and are ignored. - -** Well known names -*** Description -This is a human readable string describing the key. - -*** Key -The name "Key" is special in that it is mandatory and must occur only -once. The associated value holds the actual S-expression with the -cryptographic key. The S-expression is formatted using the 'Advanced -Format' (GCRYSEXP_FMT_ADVANCED) that avoids non-printable characters -so that the file can be easily inspected and edited. See section -'Private Key Format' below for details. - -*** Created -The UTC time the key was created in ISO compressed format -(yyyymmddThhmmss). This information can be used to re-create an -OpenPGP key. - -*** Label -This is a short human readable description for the key which can be -used by the software to describe the key in a user interface. For -example as part of the description in a prompt for a PIN or -passphrase. It is often used instead of a comment element as present -in the S-expression of the "Key" item. - -*** OpenSSH-cert -This takes a base64 encoded string wrapped so that this -key file can be easily edited with a standard editor. Several of such -items can be used. - -*** Token -If such an item exists it overrides the info given by the "shadow" -parameter in the S-expression. Using this item makes it possible to -describe a key which is stored on several tokens and also makes it -easy to update this info using a standard editor. The syntax is -similar to the "shadow" parameter: - -- Serialnumber of the token. -- Key reference from the token in full format (e.g. "OpenPGP.2"). -- An optional fixed length of the PIN or "-". -- The human readable serial number of a card. This is usually what is - printed on the actual card. This value is taken directly from the - card but when asking to insert a card it is useful to have this - value available. GnuPG takes care of creating and possibly updating - this entry. This is percent-plus-escaped. - - -*** Use-for-ssh -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. 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. If a negative value is used -this overrides currently active (inserted) cards and thus allows to -prefer on-disk keys over inserted cards. A value of -1 has the -highest priority; values are capped at -999 and have a lower priority -but still above the positive values, inserted cards or the order in -sshcontrol. - - -*** Use-for-p11 -If given and the value is "yes" or "1" the key is allowed for use by -GnuPG's PKCS#11 interface (Scute). Note that Scute needs to be -configured to use this optimization. - -*** Remote-list -Allow to list the key with the KEYINFO command from a remote machine -via the extra socket. A boolean value is expected; the default is -"no". Note that KEYINFO will anyway provide information if the -keygrip is specified. - -*** Confirm -If given and the value is "yes", a user will be asked confirmation by -a dialog window when the key is about to be used for -PKSIGN/PKAUTH/PKDECRYPT operation. If the value is "restricted", it -is only asked for the access through extra/browser socket. - -*** Prompt -This field is for card key. If given and the value is "yes" -(default), a user will be prompted about insertion of the card by a -dialog window when card is not available. When the value is "no", a -card operation is refused with GPG_ERR_UNUSABLE_SECKEY error. - -*** Backup-info -This gives information for a backup of the key. The follwoing fields -are space delimited: - -- Hexified keygrip (uppercase) to make it easy to identify the - filename. When restoring software should make sure that the keygrip - matches the one derived from the "Key" field. -- Backup time in as ISO string. -- Name of the backup software. -- Arbitrary information. - -* Private Key Format -** Unprotected Private Key Format - -The content of the file is an S-Expression like the ones used with -Libgcrypt. Here is an example of an unprotected file: - -(private-key - (rsa - (n #00e0ce9..[some bytes not shown]..51#) - (e #010001#) - (d #046129F..[some bytes not shown]..81#) - (p #00e861b..[some bytes not shown]..f1#) - (q #00f7a7c..[some bytes not shown]..61#) - (u #304559a..[some bytes not shown]..9b#) - ) - (created-at timestamp) - (uri http://foo.bar x-foo:whatever_you_want) - (comment whatever) -) - -"comment", "created-at" and "uri" are optional. "comment" is -currently used to keep track of ssh key comments. "created-at" is used -to keep track of the creation time stamp used with OpenPGP keys; it is -optional but required for some operations to calculate the fingerprint -of the key. This timestamp should be a string with the number of -seconds since Epoch or an ISO time string (yyyymmddThhmmss). - -** Protected Private Key Format - -A protected key is like this: - -(protected-private-key - (rsa - (n #00e0ce9..[some bytes not shown]..51#) - (e #010001#) - (protected mode (parms) encrypted_octet_string) - (protected-at <isotimestamp>) - ) - (uri http://foo.bar x-foo:whatever_you_want) - (comment whatever) -) - - -In this scheme the encrypted_octet_string is encrypted according to -the algorithm described after the keyword protected; most protection -algorithms need some parameters, which are given in a list before the -encrypted_octet_string. The result of the decryption process is a -list of the secret key parameters. The protected-at expression is -optional; the isotimestamp is 15 bytes long (e.g. "19610711T172000"). - -The currently defined protection modes are: - -*** openpgp-s2k3-sha1-aes-cbc - - This describes an algorithm using AES in CBC mode for - encryption, SHA-1 for integrity protection and the String to Key - algorithm 3 from OpenPGP (rfc4880). - - Example: - - (protected openpgp-s2k3-sha1-aes-cbc - ((sha1 16byte_salt no_of_iterations) 16byte_iv) - encrypted_octet_string - ) - - The encrypted_octet string should yield this S-Exp (in canonical - representation) after decryption: - - ( - ( - (d #046129F..[some bytes not shown]..81#) - (p #00e861b..[some bytes not shown]..f1#) - (q #00f7a7c..[some bytes not shown]..61#) - (u #304559a..[some bytes not shown]..9b#) - ) - (hash sha1 #...[hashvalue]...#) - ) - - For padding reasons, random bytes are appended to this list - they can - easily be stripped by looking for the end of the list. - - The hash is calculated on the concatenation of the public key and - secret key parameter lists: i.e. it is required to hash the - concatenation of these 6 canonical encoded lists for RSA, including - the parenthesis, the algorithm keyword and (if used) the protected-at - list. - - (rsa - (n #00e0ce9..[some bytes not shown]..51#) - (e #010001#) - (d #046129F..[some bytes not shown]..81#) - (p #00e861b..[some bytes not shown]..f1#) - (q #00f7a7c..[some bytes not shown]..61#) - (u #304559a..[some bytes not shown]..9b#) - (protected-at "18950523T000000") - ) - - After decryption the hash must be recalculated and compared against - the stored one - If they don't match the integrity of the key is not - given. - -*** openpgp-s2k3-ocb-aes - - This describes an algorithm using AES-128 in OCB mode, a nonce - of 96 bit, a taglen of 128 bit, and the String to Key algorithm 3 - from OpenPGP (rfc4880). - - Example: - - (protected openpgp-s2k3-ocb-aes - ((sha1 16byte_salt no_of_iterations) 12byte_nonce) - encrypted_octet_string - ) - - The encrypted_octet string should yield this S-Exp (in canonical - representation) after decryption: - - ( - ( - (d #046129F..[some bytes not shown]..81#) - (p #00e861b..[some bytes not shown]..f1#) - (q #00f7a7c..[some bytes not shown]..61#) - (u #304559a..[some bytes not shown]..9b#) - ) - ) - - For padding reasons, random bytes may be appended to this list - - they can easily be stripped by looking for the end of the list. - - The associated data required for this protection mode is the list - forming the public key parameters. For the above example this is - is this canonical encoded S-expression: - - (rsa - (n #00e0ce9..[some bytes not shown]..51#) - (e #010001#) - (protected-at "18950523T000000") - ) - -*** openpgp-native - - This is a wrapper around the OpenPGP Private Key Transport format - which resembles the standard OpenPGP format and allows the use of an - existing key without re-encrypting to the default protection format. - - Example: - - (protected openpgp-native - (openpgp-private-key - (version V) - (algo PUBKEYALGO) - (skey _ P1 _ P2 _ P3 ... e PN) - (csum n) - (protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT))) - - Note that the public key parameters in SKEY are duplicated and - should be identical to their copies in the standard parameter - elements. Here is an example of an entire protected private key - using this format: - - (protected-private-key - (rsa - (n #00e0ce9..[some bytes not shown]..51#) - (e #010001#) - (protected openpgp-native - (openpgp-private-key - (version 4) - (algo rsa) - (skey _ #00e0ce9..[some bytes not shown]..51# - _ #010001# - e #.........................#) - (protection sha1 aes #aabbccddeeff00112233445566778899# - 3 sha1 #2596f93e85f41e53# 3:190)))) - (uri http://foo.bar x-foo:whatever_you_want) - (comment whatever)) - -** Shadowed Private Key Format - -To keep track of keys stored on IC cards we use a third format for -private kyes which are called shadow keys as they are only a reference -to keys stored on a token: - -(shadowed-private-key - (rsa - (n #00e0ce9..[some bytes not shown]..51#) - (e #010001#) - (shadowed protocol (info)) - ) - (uri http://foo.bar x-foo:whatever_you_want) - (comment whatever) -) - -The currently used protocols are "t1-v1" (token info version 1) and -"tpm2-v1" (TPM format key information). The second list with the -information has this layout for "t1-v1": - -(card_serial_number id_string_of_key fixed_pin_length) - -FIXED_PIN_LENGTH is optional. It can be used to store the length of -the PIN; a value of 0 indicates that this information is not -available. The rationale for this field is that some pinpad equipped -readers don't allow passing a variable length PIN. - -This is the (info) layout for "tpm2-v1": - -(parent tpm_private_string tpm_public_string) - -Although this precise format is encapsulated inside the tpm2daemon -itself and nothing in gpg ever uses this. - -More items may be added to the list. - -** OpenPGP Private Key Transfer Format - -This format is used to transfer keys between gpg and gpg-agent. - -(openpgp-private-key - (version V) - (algo PUBKEYALGO) - (curve CURVENAME) - (skey _ P1 _ P2 _ P3 ... e PN) - (csum n) - (protection PROTTYPE PROTALGO IV S2KMODE S2KHASH S2KSALT S2KCOUNT)) - - - * V is the packet version number (3 or 4). - * PUBKEYALGO is a Libgcrypt algo name - * CURVENAME is the name of the curve - only used with ECC. - * P1 .. PN are the parameters; the public parameters are never encrypted - the secrect key parameters are encrypted if the "protection" list is - given. To make this more explicit each parameter is preceded by a - flag "_" for cleartext or "e" for encrypted text. - * CSUM is the deprecated 16 bit checksum as defined by OpenPGP. This - is an optional element. - * If PROTTYPE is "sha1" the new style SHA1 checksum is used if it is "sum" - the old 16 bit checksum (above) is used and if it is "none" no - protection at all is used. - * PROTALGO is a Libgcrypt style cipher algorithm name - * IV is the initialization verctor. - * S2KMODE is the value from RFC-4880. - * S2KHASH is a libgcrypt style hash algorithm identifier. - * S2KSALT is the 8 byte salt - * S2KCOUNT is the count value from RFC-4880. - -** Persistent Passphrase Format - -Note: That this has not yet been implemented. - -To allow persistent storage of cached passphrases we use a scheme -similar to the private-key storage format. This is a master -passphrase format where each file may protect several secrets under -one master passphrase. It is possible to have several of those files -each protected by a dedicated master passphrase. Clear text keywords -allow listing the available protected passphrases. - -The name of the files with these protected secrets have this form: -pw-<string>.dat. STRING may be an arbitrary string, as a default name -for the passphrase storage the name "pw-default.dat" is suggested. - - -(protected-shared-secret - ((desc descriptive_text) - (key [key_1] (keyword_1 keyword_2 keyword_n)) - (key [key_2] (keyword_21 keyword_22 keyword_2n)) - (key [key_n] (keyword_n1 keyword_n2 keyword_nn)) - (protected mode (parms) encrypted_octet_string) - (protected-at <isotimestamp>) - ) -) - -After decryption the encrypted_octet_string yields this S-expression: - -( - ( - (value key_1 value_1) - (value key_2 value_2) - (value key_n value_n) - ) - (hash sha1 #...[hashvalue]...#) -) - -The "descriptive_text" is displayed with the prompt to enter the -unprotection passphrase. - -KEY_1 to KEY_N are unique identifiers for the shared secret, for -example an URI. In case this information should be kept confidential -as well, they may not appear in the unprotected part; however they are -mandatory in the encrypted_octet_string. The list of keywords is -optional. The order of the "key" lists and the order of the "value" -lists must match, that is the first "key"-list is associated with the -first "value" list in the encrypted_octet_string. - -The protection mode etc. is identical to the protection mode as -described for the private key format. - -list of the secret key parameters. The protected-at expression is -optional; the isotimestamp is 15 bytes long (e.g. "19610711T172000"). - -The "hash" in the encrypted_octet_string is calculated on the -concatenation of the key list and value lists: i.e it is required to -hash the concatenation of all these lists, including the -parenthesis and (if used) the protected-at list. - -Example: - -(protected-shared-secret - ((desc "List of system passphrases") - (key "uid-1002" ("Knuth" "Donald Ervin Knuth")) - (key "uid-1001" ("Dijkstra" "Edsger Wybe Dijkstra")) - (key) - (protected mode (parms) encrypted_octet_string) - (protected-at "20100915T111722") - ) -) - -with "encrypted_octet_string" decoding to: - -( - ( - (value 4:1002 "signal flags at the lock") - (value 4:1001 "taocp") - (value 1:0 "premature optimization is the root of all evil") - ) - (hash sha1 #0102030405060708091011121314151617181920#) -) - -To compute the hash this S-expression (in canoncical format) was -hashed: - - ((desc "List of system passphrases") - (key "uid-1002" ("Knuth" "Donald Ervin Knuth")) - (key "uid-1001" ("Dijkstra" "Edsger Wybe Dijkstra")) - (key) - (value 4:1002 "signal flags at the lock") - (value 4:1001 "taocp") - (value 1:0 "premature optimization is the root of all evil") - (protected-at "20100915T111722") - ) - -* Notes - -[1] I usually use the terms private and secret key exchangeable but prefer the -term secret key because it can be visually be better distinguished -from the term public key. - -[2] The keygrip is a unique identifier for a key pair, it is -independent of any protocol, so that the same key can be used with -different protocols. PKCS-15 calls this a subjectKeyHash; it can be -calculated using Libgcrypt's gcry_pk_get_keygrip (). - -[3] Even when canonical representation are required we will show the -S-expression here in a more readable representation. diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index c26f21d35..efaf53098 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -27,8 +27,83 @@ #include <sys/stat.h> #include "agent.h" +#include "../common/openpgpdefs.h" +/* Table with parameters for KEM decryption. Use get_ecc_parms to + * find an entry. */ +struct ecc_params +{ + const char *curve; /* Canonical name of the curve. */ + size_t pubkey_len; /* Pubkey in the SEXP representation. */ + size_t scalar_len; + size_t point_len; + size_t shared_len; + int hash_algo; + int kem_algo; + int scalar_reverse; +}; + +static const struct ecc_params ecc_table[] = + { + { + "Curve25519", + 33, 32, 32, 32, + GCRY_MD_SHA3_256, GCRY_KEM_RAW_X25519, + 1 + }, + { + "X448", + 56, 56, 56, 64, + GCRY_MD_SHA3_512, GCRY_KEM_RAW_X448, + 0 + }, + { + "brainpoolP256r1", + 65, 32, 65, 32, + GCRY_MD_SHA3_256, GCRY_KEM_RAW_BP256, + 0 + }, + { + "brainpoolP384r1", + 97, 48, 97, 64, + GCRY_MD_SHA3_512, GCRY_KEM_RAW_BP384, + 0 + }, + { + "brainpoolP512r1", + 129, 64, 129, 64, + GCRY_MD_SHA3_512, GCRY_KEM_RAW_BP512, + 0 + }, + { NULL, 0, 0, 0, 0, 0, 0, 0 } +}; + + +/* Maximum buffer sizes required for ECC KEM. Keep this aligned to + * the ecc_table above. */ +#define ECC_SCALAR_LEN_MAX 64 +#define ECC_POINT_LEN_MAX (1+2*64) +#define ECC_HASH_LEN_MAX 64 + + + +/* Return the ECC parameters for CURVE. CURVE is expected to be the + * canonical name. */ +static const struct ecc_params * +get_ecc_params (const char *curve) +{ + int i; + + for (i = 0; ecc_table[i].curve; i++) + if (!strcmp (ecc_table[i].curve, curve)) + return &ecc_table[i]; + + return NULL; +} + + + /* DECRYPT the stuff in ciphertext which is expected to be a S-Exp. Try to get the key from CTRL and write the decoded stuff back to OUTFP. The padding information is stored at R_PADDING with -1 @@ -41,7 +116,6 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, gcry_sexp_t s_skey = NULL, s_cipher = NULL, s_plain = NULL; unsigned char *shadow_info = NULL; gpg_error_t err = 0; - int no_shadow_info = 0; char *buf = NULL; size_t len; @@ -70,17 +144,13 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, err = agent_key_from_file (ctrl, NULL, desc_text, NULL, &shadow_info, CACHE_MODE_NORMAL, NULL, &s_skey, NULL, NULL); - if (gpg_err_code (err) == GPG_ERR_NO_SECKEY) - no_shadow_info = 1; - else if (err) + if (err && gpg_err_code (err) != GPG_ERR_NO_SECKEY) { log_error ("failed to read the secret key\n"); - goto leave; } - - if (shadow_info || no_shadow_info) + else if (shadow_info + || err /* gpg_err_code (err) == GPG_ERR_NO_SECKEY */) { /* divert operation to the smartcard */ - if (!gcry_sexp_canon_len (ciphertext, ciphertextlen, NULL, NULL)) { err = gpg_error (GPG_ERR_INV_SEXP); @@ -95,12 +165,12 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, &buf, &len, r_padding); if (err) { - /* We restore the original error (ie. no seckey) is no card + /* We restore the original error (ie. no seckey) as 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) + && !shadow_info) err = gpg_error (GPG_ERR_NO_SECKEY); else log_error ("smartcard decryption failed: %s\n", gpg_strerror (err)); @@ -157,3 +227,597 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, xfree (shadow_info); return err; } + + +/* Reverse BUFFER to change the endianness. */ +static void +reverse_buffer (unsigned char *buffer, unsigned int length) +{ + unsigned int tmp, i; + + for (i=0; i < length/2; i++) + { + tmp = buffer[i]; + buffer[i] = buffer[length-1-i]; + buffer[length-1-i] = tmp; + } +} + + +static gpg_error_t +ecc_extract_pk_from_key (const struct ecc_params *ecc, gcry_sexp_t s_skey, + unsigned char *ecc_pk) +{ + gpg_error_t err; + unsigned int nbits; + const unsigned char *p; + size_t len; + gcry_mpi_t ecc_pk_mpi = NULL; + + err = gcry_sexp_extract_param (s_skey, NULL, "/q", &ecc_pk_mpi, NULL); + if (err) + { + if (opt.verbose) + log_info ("%s: extracting q and d from ECC key failed\n", __func__); + return err; + } + + p = gcry_mpi_get_opaque (ecc_pk_mpi, &nbits); + len = (nbits+7)/8; + if (len != ecc->pubkey_len) + { + if (opt.verbose) + log_info ("%s: ECC public key length invalid (%zu)\n", __func__, len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + else if (len == ecc->point_len) + memcpy (ecc_pk, p, ecc->point_len); + else if (len == ecc->point_len + 1 && p[0] == 0x40) + /* Remove the 0x40 prefix (for Curve25519) */ + memcpy (ecc_pk, p+1, ecc->point_len); + else + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + if (DBG_CRYPTO) + log_printhex (ecc_pk, ecc->pubkey_len, "ECC pubkey:"); + + leave: + mpi_release (ecc_pk_mpi); + return err; +} + +static gpg_error_t +ecc_extract_sk_from_key (const struct ecc_params *ecc, gcry_sexp_t s_skey, + unsigned char *ecc_sk) +{ + gpg_error_t err; + unsigned int nbits; + const unsigned char *p; + size_t len; + gcry_mpi_t ecc_sk_mpi = NULL; + + err = gcry_sexp_extract_param (s_skey, NULL, "/d", &ecc_sk_mpi, NULL); + if (err) + { + if (opt.verbose) + log_info ("%s: extracting d from ECC key failed\n", __func__); + return err; + } + + p = gcry_mpi_get_opaque (ecc_sk_mpi, &nbits); + len = (nbits+7)/8; + if (len > ecc->scalar_len) + { + if (opt.verbose) + log_info ("%s: ECC secret key too long (%zu)\n", __func__, len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + memset (ecc_sk, 0, ecc->scalar_len - len); + memcpy (ecc_sk + ecc->scalar_len - len, p, len); + if (ecc->scalar_reverse) + reverse_buffer (ecc_sk, ecc->scalar_len); + mpi_release (ecc_sk_mpi); + ecc_sk_mpi = NULL; + + if (DBG_CRYPTO) + log_printhex (ecc_sk, ecc->scalar_len, "ECC seckey:"); + + leave: + mpi_release (ecc_sk_mpi); + return err; +} + +static gpg_error_t +ecc_raw_kem (const struct ecc_params *ecc, gcry_sexp_t s_skey, + const unsigned char *ecc_ct, unsigned char *ecc_ecdh) +{ + gpg_error_t err = 0; + unsigned char ecc_sk[ECC_SCALAR_LEN_MAX]; + + if (ecc->scalar_len > ECC_SCALAR_LEN_MAX) + { + if (opt.verbose) + log_info ("%s: ECC scalar length invalid (%zu)\n", + __func__, ecc->scalar_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + err = ecc_extract_sk_from_key (ecc, s_skey, ecc_sk); + if (err) + goto leave; + + err = gcry_kem_decap (ecc->kem_algo, ecc_sk, ecc->scalar_len, + ecc_ct, ecc->point_len, ecc_ecdh, ecc->point_len, + NULL, 0); + if (err) + { + if (opt.verbose) + log_info ("%s: gcry_kem_decap for ECC failed\n", __func__); + } + + leave: + wipememory (ecc_sk, sizeof ecc_sk); + + return err; +} + +static gpg_error_t +get_cardkey (ctrl_t ctrl, const char *keygrip, gcry_sexp_t *r_s_pk) +{ + gpg_error_t err; + unsigned char *pkbuf; + size_t pkbuflen; + + err = agent_card_readkey (ctrl, keygrip, &pkbuf, NULL); + if (err) + return err; + + pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); + err = gcry_sexp_sscan (r_s_pk, NULL, (char*)pkbuf, pkbuflen); + if (err) + log_error ("failed to build S-Exp from received card key: %s\n", + gpg_strerror (err)); + + xfree (pkbuf); + return err; +} + +static gpg_error_t +ecc_get_curve (ctrl_t ctrl, gcry_sexp_t s_skey, const char **r_curve) +{ + gpg_error_t err = 0; + gcry_sexp_t s_skey_card = NULL; + const char *curve = NULL; + gcry_sexp_t key; + + *r_curve = NULL; + + if (!s_skey) + { + err = get_cardkey (ctrl, ctrl->keygrip, &s_skey_card); + if (err) + goto leave; + + key = s_skey_card; + } + else + key = s_skey; + + curve = get_ecc_curve_from_key (key); + if (!curve) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + *r_curve = curve; + + leave: + gcry_sexp_release (s_skey_card); + return err; +} + +/* Given a private key in SEXP by S_SKEY0 and a cipher text by ECC_CT + * with length ECC_POINT_LEN, do ECC-KEM operation. Result is + * returned in the memory referred by ECC_SS. Shared secret length is + * returned in the memory referred by R_SHARED_LEN. CTRL is used to + * access smartcard, internally. */ +static gpg_error_t +ecc_pgp_kem_decrypt (ctrl_t ctrl, gcry_sexp_t s_skey0, + unsigned char *shadow_info0, + const unsigned char *ecc_ct, size_t ecc_point_len, + unsigned char *ecc_ss, size_t *r_shared_len) +{ + gpg_error_t err; + unsigned char ecc_ecdh[ECC_POINT_LEN_MAX]; + unsigned char ecc_pk[ECC_POINT_LEN_MAX]; + const char *curve; + const struct ecc_params *ecc = NULL; + + if (ecc_point_len > ECC_POINT_LEN_MAX) + return gpg_error (GPG_ERR_INV_DATA); + + err = ecc_get_curve (ctrl, s_skey0, &curve); + if (err) + { + if ((gpg_err_code (err) == GPG_ERR_CARD_REMOVED + || gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) + && !s_skey0) + err = gpg_error (GPG_ERR_NO_SECKEY); + return err; + } + + ecc = get_ecc_params (curve); + if (!ecc) + { + if (opt.verbose) + log_info ("%s: curve '%s' not supported\n", __func__, curve); + return gpg_error (GPG_ERR_BAD_SECKEY); + } + + *r_shared_len = ecc->shared_len; + + if (DBG_CRYPTO) + log_debug ("ECC curve: %s\n", curve); + + if (ecc->point_len != ecc_point_len) + { + if (opt.verbose) + log_info ("%s: ECC cipher text length invalid (%zu != %zu)\n", + __func__, ecc->point_len, ecc_point_len); + return gpg_error (GPG_ERR_INV_DATA); + } + + err = ecc_extract_pk_from_key (ecc, s_skey0, ecc_pk); + if (err) + return err; + + if (DBG_CRYPTO) + log_printhex (ecc_ct, ecc->point_len, "ECC ephem:"); + + if (shadow_info0 || !s_skey0) + { + if (s_skey0 && agent_is_tpm2_key (s_skey0)) + { + log_error ("TPM decryption failed: %s\n", gpg_strerror (err)); + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + else + { + err = agent_card_ecc_kem (ctrl, ecc_ct, ecc->point_len, ecc_ecdh); + if (err) + { + log_error ("smartcard decryption failed: %s\n", + gpg_strerror (err)); + return err; + } + } + } + else + err = ecc_raw_kem (ecc, s_skey0, ecc_ct, ecc_ecdh); + + if (err) + return err; + + if (DBG_CRYPTO) + log_printhex (ecc_ecdh, ecc_point_len, "ECC ecdh:"); + + err = gnupg_ecc_kem_kdf (ecc_ss, ecc->shared_len, ecc->hash_algo, + ecc_ecdh, ecc->point_len, ecc_ct, ecc->point_len, + ecc_pk, ecc->point_len); + + wipememory (ecc_ecdh, sizeof ecc_ecdh); + + if (err) + { + if (opt.verbose) + log_info ("%s: kdf for ECC failed\n", __func__); + return err; + } + + if (DBG_CRYPTO) + log_printhex (ecc_ss, ecc->shared_len, "ECC shared:"); + + return 0; +} + +/* For composite PGP KEM (ECC+ML-KEM), decrypt CIPHERTEXT using KEM API. + First keygrip is for ECC, second keygrip is for PQC. CIPHERTEXT + should follow the format of: + + (enc-val(pqc(c%d)(e%m)(k%m)(s%m)(fixed-info&))) + c: cipher identifier (symmetric) + e: ECDH ciphertext + k: ML-KEM ciphertext + s: encrypted session key + fixed-info: A buffer with the fixed info. + + FIXME: For now, possible keys on smartcard are not supported. + */ +static gpg_error_t +composite_pgp_kem_decrypt (ctrl_t ctrl, const char *desc_text, + gcry_sexp_t s_cipher, membuf_t *outbuf) +{ + gcry_sexp_t s_skey0 = NULL; + gcry_sexp_t s_skey1 = NULL; + unsigned char *shadow_info0 = NULL; + unsigned char *shadow_info1 = NULL; + gpg_error_t err = 0; + + unsigned int nbits; + size_t len; + + int algo; + gcry_mpi_t encrypted_sessionkey_mpi = NULL; + const unsigned char *encrypted_sessionkey; + size_t encrypted_sessionkey_len; + + gcry_mpi_t ecc_ct_mpi = NULL; + const unsigned char *ecc_ct; + size_t ecc_ct_len; + unsigned char ecc_ss[ECC_HASH_LEN_MAX]; + size_t ecc_shared_len, ecc_point_len; + + enum gcry_kem_algos mlkem_kem_algo; + gcry_mpi_t mlkem_sk_mpi = NULL; + gcry_mpi_t mlkem_ct_mpi = NULL; + const unsigned char *mlkem_sk; + size_t mlkem_sk_len; + const unsigned char *mlkem_ct; + size_t mlkem_ct_len; + unsigned char mlkem_ss[GCRY_KEM_MLKEM1024_SHARED_LEN]; + size_t mlkem_ss_len; + + unsigned char kek[32]; + size_t kek_len = 32; /* AES-256 is mandatory */ + + gcry_cipher_hd_t hd; + unsigned char sessionkey[256]; + size_t sessionkey_len; + gcry_buffer_t fixed_info = { 0, 0, 0, NULL }; + + err = agent_key_from_file (ctrl, NULL, desc_text, + ctrl->keygrip, &shadow_info0, + CACHE_MODE_NORMAL, NULL, &s_skey0, NULL, NULL); + if (err && gpg_err_code (err) != GPG_ERR_NO_SECKEY) + { + log_error ("failed to read the secret key\n"); + goto leave; + } + + err = agent_key_from_file (ctrl, NULL, desc_text, + ctrl->keygrip1, &shadow_info1, + CACHE_MODE_NORMAL, NULL, &s_skey1, NULL, NULL); + /* Here assumes no smartcard for ML-KEM, but private key in a file. */ + if (err) + { + log_error ("failed to read the another secret key\n"); + goto leave; + } + + err = gcry_sexp_extract_param (s_cipher, NULL, "%dc/eks&'fixed-info'", + &algo, &ecc_ct_mpi, &mlkem_ct_mpi, + &encrypted_sessionkey_mpi, &fixed_info, NULL); + if (err) + { + if (opt.verbose) + log_info ("%s: extracting parameters failed\n", __func__); + goto leave; + } + + ecc_ct = gcry_mpi_get_opaque (ecc_ct_mpi, &nbits); + ecc_ct_len = (nbits+7)/8; + + len = gcry_cipher_get_algo_keylen (algo); + encrypted_sessionkey = gcry_mpi_get_opaque (encrypted_sessionkey_mpi, &nbits); + encrypted_sessionkey_len = (nbits+7)/8; + if (len == 0 || encrypted_sessionkey_len != len + 8) + { + if (opt.verbose) + log_info ("%s: encrypted session key length %zu" + " does not match the length for algo %d\n", + __func__, encrypted_sessionkey_len, algo); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + /* Firstly, ECC part. */ + ecc_point_len = ecc_ct_len; + err = ecc_pgp_kem_decrypt (ctrl, s_skey0, shadow_info0, ecc_ct, ecc_point_len, + ecc_ss, &ecc_shared_len); + if (err) + goto leave; + + /* Secondly, PQC part. For now, we assume ML-KEM. */ + err = gcry_sexp_extract_param (s_skey1, NULL, "/s", &mlkem_sk_mpi, NULL); + if (err) + { + if (opt.verbose) + log_info ("%s: extracting s from PQ key failed\n", __func__); + goto leave; + } + mlkem_sk = gcry_mpi_get_opaque (mlkem_sk_mpi, &nbits); + mlkem_sk_len = (nbits+7)/8; + if (mlkem_sk_len == GCRY_KEM_MLKEM512_SECKEY_LEN) + { + mlkem_kem_algo = GCRY_KEM_MLKEM512; + mlkem_ss_len = GCRY_KEM_MLKEM512_SHARED_LEN; + mlkem_ct_len = GCRY_KEM_MLKEM512_CIPHER_LEN; + } + else if (mlkem_sk_len == GCRY_KEM_MLKEM768_SECKEY_LEN) + { + mlkem_kem_algo = GCRY_KEM_MLKEM768; + mlkem_ss_len = GCRY_KEM_MLKEM768_SHARED_LEN; + mlkem_ct_len = GCRY_KEM_MLKEM768_CIPHER_LEN; + } + else if (mlkem_sk_len == GCRY_KEM_MLKEM1024_SECKEY_LEN) + { + mlkem_kem_algo = GCRY_KEM_MLKEM1024; + mlkem_ss_len = GCRY_KEM_MLKEM1024_SHARED_LEN; + mlkem_ct_len = GCRY_KEM_MLKEM1024_CIPHER_LEN; + } + else + { + if (opt.verbose) + log_info ("%s: PQ key length invalid (%zu)\n", __func__, mlkem_sk_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + mlkem_ct = gcry_mpi_get_opaque (mlkem_ct_mpi, &nbits); + len = (nbits+7)/8; + if (len != mlkem_ct_len) + { + if (opt.verbose) + log_info ("%s: PQ cipher text length invalid (%zu)\n", + __func__, mlkem_ct_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + err = gcry_kem_decap (mlkem_kem_algo, mlkem_sk, mlkem_sk_len, + mlkem_ct, mlkem_ct_len, mlkem_ss, mlkem_ss_len, + NULL, 0); + if (err) + { + if (opt.verbose) + log_info ("%s: gcry_kem_decap for PQ failed\n", __func__); + goto leave; + } + + mpi_release (mlkem_sk_mpi); + mlkem_sk_mpi = NULL; + + /* Then, combine two shared secrets and ciphertexts into one KEK */ + err = gnupg_kem_combiner (kek, kek_len, + ecc_ss, ecc_shared_len, ecc_ct, ecc_point_len, + mlkem_ss, mlkem_ss_len, mlkem_ct, mlkem_ct_len, + fixed_info.data, fixed_info.size); + if (err) + { + if (opt.verbose) + log_info ("%s: KEM combiner failed\n", __func__); + goto leave; + } + + mpi_release (ecc_ct_mpi); + ecc_ct_mpi = NULL; + mpi_release (mlkem_ct_mpi); + mlkem_ct_mpi = NULL; + + if (DBG_CRYPTO) + { + log_printhex (kek, kek_len, "KEK key: "); + } + + err = gcry_cipher_open (&hd, GCRY_CIPHER_AES256, + GCRY_CIPHER_MODE_AESWRAP, 0); + if (err) + { + if (opt.verbose) + log_error ("ecdh failed to initialize AESWRAP: %s\n", + gpg_strerror (err)); + goto leave; + } + + err = gcry_cipher_setkey (hd, kek, kek_len); + + sessionkey_len = encrypted_sessionkey_len - 8; + err = gcry_cipher_decrypt (hd, sessionkey, sessionkey_len, + encrypted_sessionkey, encrypted_sessionkey_len); + gcry_cipher_close (hd); + + mpi_release (encrypted_sessionkey_mpi); + encrypted_sessionkey_mpi = NULL; + + if (err) + { + log_error ("KEM decrypt failed: %s\n", gpg_strerror (err)); + goto leave; + } + + put_membuf_printf (outbuf, + "(5:value%u:", (unsigned int)sessionkey_len); + put_membuf (outbuf, sessionkey, sessionkey_len); + put_membuf (outbuf, ")", 2); + + leave: + wipememory (ecc_ss, sizeof ecc_ss); + wipememory (mlkem_ss, sizeof mlkem_ss); + wipememory (kek, sizeof kek); + wipememory (sessionkey, sizeof sessionkey); + + mpi_release (ecc_ct_mpi); + mpi_release (mlkem_sk_mpi); + mpi_release (mlkem_ct_mpi); + mpi_release (encrypted_sessionkey_mpi); + gcry_free (fixed_info.data); + gcry_sexp_release (s_skey0); + gcry_sexp_release (s_skey1); + xfree (shadow_info0); + xfree (shadow_info1); + return err; +} + +/* DECRYPT the encrypted stuff (like encrypted session key) in + CIPHERTEXT using KEM API, with KEMID. Keys (or a key) are + specified in CTRL. DESC_TEXT is used to retrieve private key. + OPTION can be specified for upper layer option for KEM. Decrypted + stuff (like session key) is written to OUTBUF. + */ +gpg_error_t +agent_kem_decrypt (ctrl_t ctrl, const char *desc_text, int kemid, + const unsigned char *ciphertext, size_t ciphertextlen, + const unsigned char *option, size_t optionlen, + membuf_t *outbuf) +{ + gcry_sexp_t s_cipher = NULL; + gpg_error_t err = 0; + + /* For now, only PQC-PGP is supported. */ + if (kemid != KEM_PQC_PGP) + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + + (void)optionlen; + if (kemid == KEM_PQC_PGP && option) + { + log_error ("PQC-PGP requires no option\n"); + return gpg_error (GPG_ERR_INV_ARG); + } + + if (!ctrl->have_keygrip) + { + log_error ("speculative decryption not yet supported\n"); + return gpg_error (GPG_ERR_NO_SECKEY); + } + + if (!ctrl->have_keygrip1) + { + log_error ("Composite KEM requires two KEYGRIPs\n"); + return gpg_error (GPG_ERR_NO_SECKEY); + } + + err = gcry_sexp_sscan (&s_cipher, NULL, (char*)ciphertext, ciphertextlen); + if (err) + { + log_error ("failed to convert ciphertext: %s\n", gpg_strerror (err)); + return gpg_error (GPG_ERR_INV_DATA); + } + + if (DBG_CRYPTO) + { + log_printhex (ctrl->keygrip, 20, "keygrip0:"); + log_printhex (ctrl->keygrip1, 20, "keygrip1:"); + gcry_log_debugsxp ("cipher", s_cipher); + } + + err = composite_pgp_kem_decrypt (ctrl, desc_text, s_cipher, outbuf); + + gcry_sexp_release (s_cipher); + return err; +} diff --git a/agent/trustlist.c b/agent/trustlist.c index 330f233b8..fce23de15 100644 --- a/agent/trustlist.c +++ b/agent/trustlist.c @@ -38,14 +38,14 @@ struct trustitem_s { struct { - int disabled:1; /* This entry is disabled. */ - int for_pgp:1; /* Set by '*' or 'P' as first flag. */ - int for_smime:1; /* Set by '*' or 'S' as first flag. */ - int relax:1; /* Relax checking of root certificate + unsigned int disabled:1; /* This entry is disabled. */ + unsigned int for_pgp:1; /* Set by '*' or 'P' as first flag. */ + unsigned int for_smime:1; /* Set by '*' or 'S' as first flag. */ + unsigned int relax:1; /* Relax checking of root certificate 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. */ + unsigned int cm:1; /* Use chain model for validation. */ + unsigned int qual:1; /* Root CA for qualified signatures. */ + unsigned int de_vs:1; /* Root CA for de-vs compliant PKI. */ } flags; unsigned char fpr[20]; /* The binary fingerprint. */ }; |