aboutsummaryrefslogtreecommitdiffstats
path: root/agent
diff options
context:
space:
mode:
Diffstat (limited to 'agent')
-rw-r--r--agent/agent.h25
-rw-r--r--agent/cache.c358
-rw-r--r--agent/call-daemon.c51
-rw-r--r--agent/call-pinentry.c44
-rw-r--r--agent/call-scd.c10
-rw-r--r--agent/command-ssh.c4
-rw-r--r--agent/command.c62
-rw-r--r--agent/cvt-openpgp.c11
-rw-r--r--agent/divert-scd.c35
-rw-r--r--agent/divert-tpm2.c6
-rw-r--r--agent/genkey.c18
-rw-r--r--agent/gpg-agent.c484
-rw-r--r--agent/keyformat.txt520
-rw-r--r--agent/pkdecrypt.c684
-rw-r--r--agent/trustlist.c14
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. */
};