diff --git a/agent/agent.h b/agent/agent.h index 2bdee97c8..6004f2d42 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -1,744 +1,745 @@ /* agent.h - Global definitions for the agent * Copyright (C) 2001, 2002, 2003, 2005, 2011 Free Software Foundation, Inc. * Copyright (C) 2015 g10 Code GmbH. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef AGENT_H #define AGENT_H #ifdef GPG_ERR_SOURCE_DEFAULT #error GPG_ERR_SOURCE_DEFAULT already defined #endif #define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGAGENT #include #define map_assuan_err(a) \ map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) #include #include #include #include "../common/util.h" #include "../common/membuf.h" #include "../common/sysutils.h" /* (gnupg_fd_t) */ #include "../common/session-env.h" #include "../common/shareddefs.h" /* To convey some special hash algorithms we use algorithm numbers reserved for application use. */ #ifndef GCRY_MODULE_ID_USER #define GCRY_MODULE_ID_USER 1024 #endif #define MD_USER_TLS_MD5SHA1 (GCRY_MODULE_ID_USER+1) /* Maximum length of a digest. */ #define MAX_DIGEST_LEN 64 /* The maximum length of a passphrase (in bytes). Note: this is further constrained by the Assuan line length (and any other text on the same line). However, the Assuan line length is 1k bytes so this shouldn't be a problem in practice. */ #define MAX_PASSPHRASE_LEN 255 /* The daemons we support. When you add a new daemon, add to both the daemon_type and the daemon_modules array in call-daemon.c */ enum daemon_type { DAEMON_SCD, DAEMON_TPM2D, DAEMON_MAX_TYPE }; /* A large struct name "opt" to keep global flags */ EXTERN_UNLESS_MAIN_MODULE struct { unsigned int debug; /* Debug flags (DBG_foo_VALUE) */ int verbose; /* Verbosity level */ int quiet; /* Be as quiet as possible */ int dry_run; /* Don't change any persistent data */ int batch; /* Batch mode */ /* True if we handle sigusr2. */ int sigusr2_enabled; /* Environment settings gathered at program start or changed using the Assuan command UPDATESTARTUPTTY. */ session_env_t startup_env; char *startup_lc_ctype; char *startup_lc_messages; /* Enable pinentry debugging (--debug 1024 should also be used). */ int debug_pinentry; /* Filename of the program to start as pinentry. */ const char *pinentry_program; /* Filename of the program to handle daemon tasks. */ const char *daemon_program[DAEMON_MAX_TYPE]; int disable_daemon[DAEMON_MAX_TYPE]; /* Never use the daemon. */ int no_grab; /* Don't let the pinentry grab the keyboard */ /* The name of the file pinentry shall touch before exiting. If this is not set the file name of the standard socket is used. */ const char *pinentry_touch_file; /* A string where the first character is used by the pinentry as a custom invisible character. */ char *pinentry_invisible_char; /* The timeout value for the Pinentry in seconds. This is passed to the pinentry if it is not 0. It is up to the pinentry to act upon this timeout value. */ unsigned long pinentry_timeout; /* If set, then passphrase formatting is enabled in pinentry. */ int pinentry_formatted_passphrase; /* The default and maximum TTL of cache entries. */ unsigned long def_cache_ttl; /* Default. */ unsigned long def_cache_ttl_ssh; /* for SSH. */ unsigned long max_cache_ttl; /* Default. */ unsigned long max_cache_ttl_ssh; /* for SSH. */ /* Flag disallowing bypassing of the warning. */ int enforce_passphrase_constraints; /* The require minmum length of a passphrase. */ unsigned int min_passphrase_len; /* The minimum number of non-alpha characters in a passphrase. */ unsigned int min_passphrase_nonalpha; /* File name with a patternfile or NULL if not enabled. If the * second one is set, it is used for symmetric only encryption * instead of the former. */ const char *check_passphrase_pattern; const char *check_sym_passphrase_pattern; /* If not 0 the user is asked to change his passphrase after these number of days. */ unsigned int max_passphrase_days; /* If set, a passphrase history will be written and checked at each passphrase change. */ int enable_passphrase_history; /* If set the extended key format is used for new keys. Note that * this may have the value 2 in which case * --disable-extended-key-format won't have any effect and thus * effectivley locking it. This is required to support existing * profiles which lock the use of --enable-extended-key-format. */ int enable_extended_key_format; int running_detached; /* We are running detached from the tty. */ /* If this global option is true, the passphrase cache is ignored for signing operations. */ int ignore_cache_for_signing; /* If this global option is true, the user is allowed to interactively mark certificate in trustlist.txt as trusted. */ int allow_mark_trusted; /* If this global option is true, the Assuan command PRESET_PASSPHRASE is allowed. */ int allow_preset_passphrase; /* If this global option is true, the Assuan option pinentry-mode=loopback is allowed. */ int allow_loopback_pinentry; /* Allow the use of an external password cache. If this option is enabled (which is the default) we send an option to Pinentry to allow it to enable such a cache. */ int allow_external_cache; /* If this global option is true, the Assuan option of Pinentry allow-emacs-prompt is allowed. */ int allow_emacs_pinentry; int keep_tty; /* Don't switch the TTY (for pinentry) on request */ int keep_display; /* Don't switch the DISPLAY (for pinentry) on request */ /* This global option indicates the use of an extra socket. Note that we use a hack for cleanup handling in gpg-agent.c: If the value is less than 2 the name has not yet been malloced. */ int extra_socket; /* This global option indicates the use of an extra socket for web browsers. Note that we use a hack for cleanup handling in gpg-agent.c: If the value is less than 2 the name has not yet been malloced. */ int browser_socket; /* The digest algorithm to use for ssh fingerprints when * communicating with the user. */ int ssh_fingerprint_digest; /* The value of the option --s2k-count. If this option is not given * or 0 an auto-calibrated value is used. */ unsigned long s2k_count; } opt; /* Bit values for the --debug option. */ #define DBG_MPI_VALUE 2 /* debug mpi details */ #define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ #define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ #define DBG_CACHE_VALUE 64 /* debug the caching */ #define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ #define DBG_HASHING_VALUE 512 /* debug hashing operations */ #define DBG_IPC_VALUE 1024 /* Enable Assuan debugging. */ /* Test macros for the debug option. */ #define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) #define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) #define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) #define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) #define DBG_IPC (opt.debug & DBG_IPC_VALUE) /* Forward reference for local definitions in command.c. */ struct server_local_s; /* Declaration of objects from command-ssh.c. */ struct ssh_control_file_s; typedef struct ssh_control_file_s *ssh_control_file_t; /* Forward reference for local definitions in call-scd.c. */ struct daemon_local_s; /* Collection of data per session (aka connection). */ struct server_control_s { /* Private data used to fire up the connection thread. We use this structure do avoid an extra allocation for only a few bytes while spawning a new connection thread. */ struct { gnupg_fd_t fd; } thread_startup; /* Flag indicating the connection is run in restricted mode. A value of 1 if used for --extra-socket, a value of 2 is used for --browser-socket. */ int restricted; /* Private data of the server (command.c). */ struct server_local_s *server_local; /* Private data of the daemon (call-XXX.c). */ struct daemon_local_s *d_local[DAEMON_MAX_TYPE]; /* Environment settings for the connection. */ session_env_t session_env; char *lc_ctype; char *lc_messages; unsigned long client_pid; int client_uid; /* The current pinentry mode. */ pinentry_mode_t pinentry_mode; /* The TTL used for the --preset option of certain commands. */ int cache_ttl_opt_preset; /* Information on the currently used digest (for signing commands). */ struct { char *data; /* NULL or malloced data of length VALUELEN. If this is set the other fields are ignored. Used for PureEdDSA and RSA with PSS (in which case data_is_pss is also set). */ int valuelen; int algo; unsigned char value[MAX_DIGEST_LEN]; unsigned int raw_value: 1; unsigned int is_pss: 1; /* DATA holds PSS formated data. */ } digest; unsigned char keygrip[20]; int have_keygrip; /* A flag to enable a hack to send the PKAUTH command instead of the PKSIGN command to the scdaemon. */ int use_auth_call; /* A flag to inhibit enforced passphrase change during an explicit passwd command. */ int in_passwd; /* The current S2K which might be different from the calibrated count. */ unsigned long s2k_count; /* If pinentry is active for this thread. It can be more than 1, when pinentry is called recursively. */ int pinentry_active; }; /* Status of pinentry. */ enum { PINENTRY_STATUS_CLOSE_BUTTON = 1 << 0, PINENTRY_STATUS_PIN_REPEATED = 1 << 8, - PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9 + PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9, + PINENTRY_STATUS_PASSWORD_GENERATED = 1 << 10 }; /* Information pertaining to pinentry requests. */ struct pin_entry_info_s { int min_digits; /* min. number of digits required or 0 for freeform entry */ int max_digits; /* max. number of allowed digits allowed*/ int max_tries; /* max. number of allowed tries. */ unsigned int constraints_flags; /* CHECK_CONSTRAINTS_... */ int failed_tries; /* Number of tries so far failed. */ int with_qualitybar; /* Set if the quality bar should be displayed. */ int with_repeat; /* Request repetition of the passphrase. */ int repeat_okay; /* Repetition worked. */ unsigned int status; /* Status. */ gpg_error_t (*check_cb)(struct pin_entry_info_s *); /* CB used to check the PIN */ void *check_cb_arg; /* optional argument which might be of use in the CB */ const char *cb_errtext; /* used by the cb to display a specific error */ size_t max_length; /* Allocated length of the buffer PIN. */ char pin[1]; /* The buffer to hold the PIN or passphrase. It's actual allocated length is given by MAX_LENGTH (above). */ }; /* Types of the private keys. */ enum { PRIVATE_KEY_UNKNOWN = 0, /* Type of key is not known. */ PRIVATE_KEY_CLEAR = 1, /* The key is not protected. */ PRIVATE_KEY_PROTECTED = 2, /* The key is protected. */ PRIVATE_KEY_SHADOWED = 3, /* The key is a stub for a smartcard based key. */ PROTECTED_SHARED_SECRET = 4, /* RFU. */ PRIVATE_KEY_OPENPGP_NONE = 5 /* openpgp-native with protection "none". */ }; /* Values for the cache_mode arguments. */ typedef enum { CACHE_MODE_IGNORE = 0, /* Special mode to bypass the cache. */ CACHE_MODE_ANY, /* Any mode except ignore and data matches. */ CACHE_MODE_NORMAL, /* Normal cache (gpg-agent). */ CACHE_MODE_USER, /* GET_PASSPHRASE related cache. */ CACHE_MODE_SSH, /* SSH related cache. */ CACHE_MODE_NONCE, /* This is a non-predictable nonce. */ CACHE_MODE_PIN, /* PINs stored/retrieved by scdaemon. */ CACHE_MODE_DATA /* Arbitrary data. */ } cache_mode_t; /* The TTL is seconds used for adding a new nonce mode cache item. */ #define CACHE_TTL_NONCE 120 /* The TTL in seconds used by the --preset option of some commands. This is the default value changeable by an OPTION command. */ #define CACHE_TTL_OPT_PRESET 900 /* The type of a function to lookup a TTL by a keygrip. */ typedef int (*lookup_ttl_t)(const char *hexgrip); /* This is a special version of the usual _() gettext macro. It assumes a server connection control variable with the name "ctrl" and uses that to translate a string according to the locale set for the connection. The macro LunderscoreIMPL is used by i18n to actually define the inline function when needed. */ #if defined (ENABLE_NLS) || defined (USE_SIMPLE_GETTEXT) #define L_(a) agent_Lunderscore (ctrl, (a)) #define LunderscorePROTO \ static inline const char *agent_Lunderscore (ctrl_t ctrl, \ const char *string) \ GNUPG_GCC_ATTR_FORMAT_ARG(2); #define LunderscoreIMPL \ static inline const char * \ agent_Lunderscore (ctrl_t ctrl, const char *string) \ { \ return ctrl? i18n_localegettext (ctrl->lc_messages, string) \ /* */: gettext (string); \ } #else #define L_(a) (a) #endif /* Information from scdaemon for card keys. */ struct card_key_info_s { struct card_key_info_s *next; char keygrip[41]; char *serialno; char *idstr; }; /*-- gpg-agent.c --*/ void agent_exit (int rc) GPGRT_ATTR_NORETURN; /* Also implemented in other tools */ void agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what, int printchar, int current, int total), ctrl_t ctrl); gpg_error_t agent_copy_startup_env (ctrl_t ctrl); const char *get_agent_socket_name (void); const char *get_agent_ssh_socket_name (void); int get_agent_active_connection_count (void); #ifdef HAVE_W32_SYSTEM void *get_agent_daemon_notify_event (void); #endif void agent_sighup_action (void); int map_pk_openpgp_to_gcry (int openpgp_algo); /*-- command.c --*/ gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, const char *extra); gpg_error_t agent_write_status (ctrl_t ctrl, const char *keyword, ...) GPGRT_ATTR_SENTINEL(0); gpg_error_t agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...) GPGRT_ATTR_PRINTF(3,4); void bump_key_eventcounter (void); void bump_card_eventcounter (void); void start_command_handler (ctrl_t, gnupg_fd_t, gnupg_fd_t); gpg_error_t pinentry_loopback (ctrl_t, const char *keyword, unsigned char **buffer, size_t *size, size_t max_length); gpg_error_t pinentry_loopback_confirm (ctrl_t ctrl, const char *desc, int ask_confirmation, const char *ok, const char *notok); #ifdef HAVE_W32_SYSTEM int serve_mmapped_ssh_request (ctrl_t ctrl, unsigned char *request, size_t maxreqlen); #endif /*HAVE_W32_SYSTEM*/ /*-- command-ssh.c --*/ ssh_control_file_t ssh_open_control_file (void); void ssh_close_control_file (ssh_control_file_t cf); gpg_error_t ssh_read_control_file (ssh_control_file_t cf, char *r_hexgrip, int *r_disabled, int *r_ttl, int *r_confirm); gpg_error_t ssh_search_control_file (ssh_control_file_t cf, const char *hexgrip, int *r_disabled, int *r_ttl, int *r_confirm); void start_command_handler_ssh (ctrl_t, gnupg_fd_t); /*-- findkey.c --*/ gpg_error_t agent_modify_description (const char *in, const char *comment, const gcry_sexp_t key, char **result); int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force, const char *serialno, const char *keyref, time_t timestamp); gpg_error_t agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, const unsigned char *grip, unsigned char **shadow_info, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, gcry_sexp_t *result, char **r_passphrase); gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result); gpg_error_t agent_public_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result); int agent_pk_get_algo (gcry_sexp_t s_key); int agent_is_tpm2_key(gcry_sexp_t s_key); int agent_key_available (const unsigned char *grip); gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, int *r_keytype, unsigned char **r_shadow_info, unsigned char **r_shadow_info_type); gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, int force, int only_stubs); /*-- call-pinentry.c --*/ void initialize_module_call_pinentry (void); void agent_query_dump_state (void); void agent_reset_query (ctrl_t ctrl); int pinentry_active_p (ctrl_t ctrl, int waitseconds); gpg_error_t agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *inital_errtext, struct pin_entry_info_s *pininfo, const char *keyinfo, cache_mode_t cache_mode); int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext, int with_qualitybar, const char *keyinfo, cache_mode_t cache_mode, struct pin_entry_info_s *pininfo); int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok, const char *notokay, int with_cancel); int agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn); int agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn); void agent_popup_message_stop (ctrl_t ctrl); int agent_clear_passphrase (ctrl_t ctrl, const char *keyinfo, cache_mode_t cache_mode); /*-- cache.c --*/ void initialize_module_cache (void); void deinitialize_module_cache (void); void agent_cache_housekeeping (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); char *agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode); void agent_store_cache_hit (const char *key); /*-- pksign.c --*/ gpg_error_t agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, gcry_sexp_t *signature_sexp, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, const void *overridedata, size_t overridedatalen); gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, membuf_t *outbuf, cache_mode_t cache_mode); /*-- pkdecrypt.c --*/ 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); /*-- genkey.c --*/ #define CHECK_CONSTRAINTS_NOT_EMPTY 1 #define CHECK_CONSTRAINTS_NEW_SYMKEY 2 int check_passphrase_constraints (ctrl_t ctrl, const char *pw, unsigned int flags, char **failed_constraint); gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, char **r_passphrase); int agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, const char *keyparam, size_t keyparmlen, int no_protection, const char *override_passphrase, int preset, membuf_t *outbuf); gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey, char **passphrase_addr); /*-- protect.c --*/ void set_s2k_calibration_time (unsigned int milliseconds); unsigned long get_calibrated_s2k_count (void); unsigned long get_standard_s2k_count (void); unsigned char get_standard_s2k_count_rfc4880 (void); unsigned long get_standard_s2k_time (void); int agent_protect (const unsigned char *plainkey, const char *passphrase, unsigned char **result, size_t *resultlen, unsigned long s2k_count, int use_ocb); gpg_error_t agent_unprotect (ctrl_t ctrl, const unsigned char *protectedkey, const char *passphrase, gnupg_isotime_t protected_at, unsigned char **result, size_t *resultlen); int agent_private_key_type (const unsigned char *privatekey); unsigned char *make_shadow_info (const char *serialno, const char *idstring); int agent_shadow_key (const unsigned char *pubkey, const unsigned char *shadow_info, unsigned char **result); int agent_shadow_key_type (const unsigned char *pubkey, const unsigned char *shadow_info, const unsigned char *type, unsigned char **result); gpg_error_t agent_get_shadow_info (const unsigned char *shadowkey, unsigned char const **shadow_info); gpg_error_t agent_get_shadow_info_type (const unsigned char *shadowkey, unsigned char const **shadow_info, unsigned char **shadow_type); gpg_error_t parse_shadow_info (const unsigned char *shadow_info, char **r_hexsn, char **r_idstr, int *r_pinlen); gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo, int s2kmode, const unsigned char *s2ksalt, unsigned int s2kcount, unsigned char *key, size_t keylen); gpg_error_t agent_write_shadow_key (const unsigned char *grip, const char *serialno, const char *keyid, const unsigned char *pkbuf, int force); /*-- trustlist.c --*/ void initialize_module_trustlist (void); gpg_error_t agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled); gpg_error_t agent_listtrusted (void *assuan_context); gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag); void agent_reload_trustlist (void); /*-- divert-tpm2.c --*/ #ifdef HAVE_LIBTSS int divert_tpm2_pksign (ctrl_t ctrl, const char *desc_text, const unsigned char *digest, size_t digestlen, int algo, const unsigned char *shadow_info, unsigned char **r_sig, size_t *r_siglen); int divert_tpm2_pkdecrypt (ctrl_t ctrl, const char *desc_text, const unsigned char *cipher, const unsigned char *shadow_info, char **r_buf, size_t *r_len, int *r_padding); int divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t s_skey); #else /*!HAVE_LIBTSS*/ static inline int divert_tpm2_pksign (ctrl_t ctrl, const char *desc_text, const unsigned char *digest, size_t digestlen, int algo, const unsigned char *shadow_info, unsigned char **r_sig, size_t *r_siglen) { (void)ctrl; (void)desc_text; (void)digest; (void)digestlen; (void)algo; (void)shadow_info; (void)r_sig; (void)r_siglen; return gpg_error (GPG_ERR_NOT_SUPPORTED); } static inline int divert_tpm2_pkdecrypt (ctrl_t ctrl, const char *desc_text, const unsigned char *cipher, const unsigned char *shadow_info, char **r_buf, size_t *r_len, int *r_padding) { (void)ctrl; (void)desc_text; (void)cipher; (void)shadow_info; (void)r_buf; (void)r_len; (void)r_padding; return gpg_error (GPG_ERR_NOT_SUPPORTED); } static inline int divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t s_skey) { (void)ctrl; (void)grip; (void)s_skey; return gpg_error (GPG_ERR_NOT_SUPPORTED); } #endif /*!HAVE_LIBTSS*/ /*-- divert-scd.c --*/ int divert_pksign (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, const unsigned char *digest, size_t digestlen, int algo, const unsigned char *shadow_info, unsigned char **r_sig, size_t *r_siglen); int divert_pkdecrypt (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, const unsigned char *cipher, const unsigned char *shadow_info, char **r_buf, size_t *r_len, int *r_padding); int divert_generic_cmd (ctrl_t ctrl, const char *cmdline, void *assuan_context); gpg_error_t divert_writekey (ctrl_t ctrl, int force, const char *serialno, const char *keyref, const char *keydata, size_t keydatalen); /*-- 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); gpg_error_t daemon_unlock (enum daemon_type type, ctrl_t ctrl, gpg_error_t rc); void initialize_module_daemon (void); void agent_daemon_dump_state (void); int agent_daemon_check_running (enum daemon_type type); void agent_daemon_check_aliveness (void); void agent_reset_daemon (ctrl_t ctrl); void agent_kill_daemon (enum daemon_type type); /*-- call-tpm2d.c --*/ int agent_tpm2d_writekey (ctrl_t ctrl, unsigned char **shadow_info, gcry_sexp_t s_skey); int agent_tpm2d_pksign (ctrl_t ctrl, const unsigned char *digest, size_t digestlen, const unsigned char *shadow_info, unsigned char **r_sig, size_t *r_siglen); int agent_tpm2d_pkdecrypt (ctrl_t ctrl, const unsigned char *cipher, size_t cipherlen, const unsigned char *shadow_info, char **r_buf, size_t *r_len); /*-- call-scd.c --*/ int agent_card_learn (ctrl_t ctrl, void (*kpinfo_cb)(void*, const char *), void *kpinfo_cb_arg, void (*certinfo_cb)(void*, const char *), void *certinfo_cb_arg, void (*sinfo_cb)(void*, const char *, size_t, const char *), void *sinfo_cb_arg); int agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand); int agent_card_pksign (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg, const char *desc_text, int mdalgo, const unsigned char *indata, size_t indatalen, unsigned char **r_buf, size_t *r_buflen); int agent_card_pkdecrypt (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, const char *, char*,size_t), void *getpin_cb_arg, 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, unsigned char **r_buf, char **r_keyref); gpg_error_t agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, const char *keyref, const char *keydata, size_t keydatalen, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg); gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result, const char *keygrip); int agent_card_scd (ctrl_t ctrl, const char *cmdline, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg, void *assuan_context); 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); /*-- cvt-openpgp.c --*/ gpg_error_t extract_private_key (gcry_sexp_t s_key, int req_private_key_data, const char **r_algoname, int *r_npkey, int *r_nskey, const char **r_format, gcry_mpi_t *mpi_array, int arraysize, gcry_sexp_t *r_curve, gcry_sexp_t *r_flags); /*-- sexp-secret.c --*/ gpg_error_t fixup_when_ecc_private_key (unsigned char *buf, size_t *buflen_p); gpg_error_t sexp_sscan_private_key (gcry_sexp_t *result, size_t *r_erroff, unsigned char *buf); #endif /*AGENT_H*/ diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index d0e4f3d08..45ec1b58e 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -1,2150 +1,2177 @@ /* call-pinentry.c - Spawn the pinentry to query stuff from the user * Copyright (C) 2001, 2002, 2004, 2007, 2008, * 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM # include # include # include # include #endif #include #include "agent.h" #include #include "../common/sysutils.h" #include "../common/i18n.h" #include "../common/zb32.h" #ifdef _POSIX_OPEN_MAX #define MAX_OPEN_FDS _POSIX_OPEN_MAX #else #define MAX_OPEN_FDS 20 #endif /* Because access to the pinentry must be serialized (it is and shall be a global mutually exclusive dialog) we better timeout pending requests after some time. 1 minute seem to be a reasonable time. */ #define LOCK_TIMEOUT (1*60) -/* Define the maximum tries to generate a pin for the GENPIN inquire */ -#define MAX_GENPIN_TRIES 10 /* Define the number of bits to use for a generated pin. The * passphrase will be rendered as zbase32 which results for 150 bits * in a string of 30 characters. That fits nicely into the 5 * character blocking which pinentry can do. 128 bits would actually * be sufficient but can't be formatted nicely. */ #define DEFAULT_GENPIN_BITS 150 /* The assuan context of the current pinentry. */ static assuan_context_t entry_ctx; /* A list of features of the current pinentry. */ static struct { /* The Pinentry support RS+US tabbing. This means that a RS (0x1e) * starts a new tabbing block in which a US (0x1f) followed by a * colon marks a colon. A pinentry can use this to pretty print * name value pairs. */ unsigned int tabbing:1; } entry_features; /* A mutex used to serialize access to the pinentry. */ static npth_mutex_t entry_lock; /* The thread ID of the popup working thread. */ static npth_t popup_tid; /* A flag used in communication between the popup working thread and its stop function. */ static int popup_finished; /* Data to be passed to our callbacks, */ struct entry_parm_s { int lines; size_t size; unsigned char *buffer; int status; unsigned int constraints_flags; }; /* This function must be called once to initialize this module. This has to be done before a second thread is spawned. We can't do the static initialization because Pth emulation code might not be able to do a static init; in particular, it is not possible for W32. */ void initialize_module_call_pinentry (void) { static int initialized; int err; if (!initialized) { err = npth_mutex_init (&entry_lock, NULL); if (err) log_fatal ("error initializing mutex: %s\n", strerror (err)); initialized = 1; } } /* This function may be called to print information pertaining to the current state of this module to the log. */ 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); } /* Called to make sure that a popup window owned by the current connection gets closed. */ void agent_reset_query (ctrl_t ctrl) { if (entry_ctx && popup_tid && ctrl->pinentry_active) { agent_popup_message_stop (ctrl); } } /* Unlock the pinentry so that another thread can start one and disconnect that pinentry - we do this after the unlock so that a stalled pinentry does not block other threads. Fixme: We should have a timeout in Assuan for the disconnect operation. */ static gpg_error_t unlock_pinentry (ctrl_t ctrl, gpg_error_t rc) { assuan_context_t ctx = entry_ctx; int err; if (rc) { if (DBG_IPC) log_debug ("error calling pinentry: %s <%s>\n", gpg_strerror (rc), gpg_strsource (rc)); /* Change the source of the error to pinentry so that the final consumer of the error code knows that the problem is with pinentry. For backward compatibility we do not do that for some common error codes. */ switch (gpg_err_code (rc)) { case GPG_ERR_NO_PIN_ENTRY: case GPG_ERR_CANCELED: case GPG_ERR_FULLY_CANCELED: case GPG_ERR_ASS_UNKNOWN_INQUIRE: case GPG_ERR_ASS_TOO_MUCH_DATA: case GPG_ERR_NO_PASSPHRASE: case GPG_ERR_BAD_PASSPHRASE: case GPG_ERR_BAD_PIN: break; case GPG_ERR_CORRUPTED_PROTECTION: /* This comes from gpg-agent. */ break; default: rc = gpg_err_make (GPG_ERR_SOURCE_PINENTRY, gpg_err_code (rc)); break; } } if (--ctrl->pinentry_active == 0) { entry_ctx = NULL; err = npth_mutex_unlock (&entry_lock); if (err) { log_error ("failed to release the entry lock: %s\n", strerror (err)); if (!rc) rc = gpg_error_from_errno (err); } assuan_release (ctx); } return rc; } /* Helper for at_fork_cb which can also be called by the parent to * show which envvars will be set. */ static void atfork_core (ctrl_t ctrl, int debug_mode) { int iterator = 0; const char *name, *assname, *value; while ((name = session_env_list_stdenvnames (&iterator, &assname))) { /* For all new envvars (!ASSNAME) and the two medium old ones * which do have an assuan name but are conveyed using * environment variables, update the environment of the forked * process. We also pass DISPLAY despite that --display is also * used when exec-ing the pinentry. The reason is that for * example the qt5ct tool does not have any arguments and thus * relies on the DISPLAY envvar. The use case here is a global * envvar like "QT_QPA_PLATFORMTHEME=qt5ct" which for example is * useful when using the Qt pinentry under GNOME or XFCE. */ if (!assname || (!opt.keep_display && !strcmp (name, "DISPLAY")) || !strcmp (name, "XAUTHORITY") || !strcmp (name, "PINENTRY_USER_DATA")) { value = session_env_getenv (ctrl->session_env, name); if (value) { if (debug_mode) log_debug ("pinentry: atfork used setenv(%s,%s)\n",name,value); else gnupg_setenv (name, value, 1); } } } } /* To make sure we leave no secrets in our image after forking of the pinentry, we use this callback. */ static void atfork_cb (void *opaque, int where) { ctrl_t ctrl = opaque; if (!where) { gcry_control (GCRYCTL_TERM_SECMEM); atfork_core (ctrl, 0); } } /* Status line callback for the FEATURES status. */ static gpg_error_t getinfo_features_cb (void *opaque, const char *line) { const char *args; char **tokens; int i; (void)opaque; if ((args = has_leading_keyword (line, "FEATURES"))) { tokens = strtokenize (args, " "); if (!tokens) return gpg_error_from_syserror (); for (i=0; tokens[i]; i++) if (!strcmp (tokens[i], "tabbing")) entry_features.tabbing = 1; xfree (tokens); } return 0; } static gpg_error_t getinfo_pid_cb (void *opaque, const void *buffer, size_t length) { unsigned long *pid = opaque; char pidbuf[50]; /* There is only the pid in the server's response. */ if (length >= sizeof pidbuf) length = sizeof pidbuf -1; if (length) { strncpy (pidbuf, buffer, length); pidbuf[length] = 0; *pid = strtoul (pidbuf, NULL, 10); } return 0; } /* Fork off the pin entry if this has not already been done. Note, that this function must always be used to acquire the lock for the pinentry - we will serialize _all_ pinentry calls. */ static gpg_error_t start_pinentry (ctrl_t ctrl) { int rc = 0; const char *full_pgmname; const char *pgmname; assuan_context_t ctx; const char *argv[5]; assuan_fd_t no_close_list[3]; int i; const char *tmpstr; unsigned long pinentry_pid; const char *value; struct timespec abstime; char *flavor_version; int err; if (ctrl->pinentry_active) { /* It's trying to use pinentry recursively. In this situation, the thread holds ENTRY_LOCK already. */ ctrl->pinentry_active++; return 0; } npth_clock_gettime (&abstime); abstime.tv_sec += LOCK_TIMEOUT; err = npth_mutex_timedlock (&entry_lock, &abstime); if (err) { if (err == ETIMEDOUT) rc = gpg_error (GPG_ERR_TIMEOUT); else rc = gpg_error_from_errno (rc); log_error (_("failed to acquire the pinentry lock: %s\n"), gpg_strerror (rc)); return rc; } if (entry_ctx) return 0; if (opt.verbose) log_info ("starting a new PIN Entry\n"); #ifdef HAVE_W32_SYSTEM fflush (stdout); fflush (stderr); #endif if (fflush (NULL)) { #ifndef HAVE_W32_SYSTEM gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); #endif log_error ("error flushing pending output: %s\n", strerror (errno)); /* At least Windows XP fails here with EBADF. According to docs and Wine an fflush(NULL) is the same as _flushall. However the Wine implementation does not flush stdin,stdout and stderr - see above. Let's try to ignore the error. */ #ifndef HAVE_W32_SYSTEM return unlock_pinentry (ctrl, tmperr); #endif } full_pgmname = opt.pinentry_program; if (!full_pgmname || !*full_pgmname) full_pgmname = gnupg_module_name (GNUPG_MODULE_NAME_PINENTRY); if ( !(pgmname = strrchr (full_pgmname, '/'))) pgmname = full_pgmname; else pgmname++; /* OS X needs the entire file name in argv[0], so that it can locate the resource bundle. For other systems we stick to the usual convention of supplying only the name of the program. */ #ifdef __APPLE__ argv[0] = full_pgmname; #else /*!__APPLE__*/ argv[0] = pgmname; #endif /*__APPLE__*/ if (!opt.keep_display && (value = session_env_getenv (ctrl->session_env, "DISPLAY"))) { argv[1] = "--display"; argv[2] = value; argv[3] = NULL; } else argv[1] = NULL; i=0; if (!opt.running_detached) { if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); } no_close_list[i] = ASSUAN_INVALID_FD; rc = assuan_new (&ctx); if (rc) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); return rc; } ctrl->pinentry_active = 1; entry_ctx = ctx; /* We don't want to log the pinentry communication to make the logs easier to read. We might want to add a new debug option to enable pinentry logging. */ #ifdef ASSUAN_NO_LOGGING assuan_set_flag (ctx, ASSUAN_NO_LOGGING, !opt.debug_pinentry); #endif /* Connect to the pinentry and perform initial handshaking. Note that atfork is used to change the environment for pinentry. We start the server in detached mode to suppress the console window under Windows. */ rc = assuan_pipe_connect (entry_ctx, full_pgmname, argv, no_close_list, atfork_cb, ctrl, ASSUAN_PIPE_CONNECT_DETACHED); if (rc) { log_error ("can't connect to the PIN entry module '%s': %s\n", full_pgmname, gpg_strerror (rc)); return unlock_pinentry (ctrl, gpg_error (GPG_ERR_NO_PIN_ENTRY)); } if (DBG_IPC) log_debug ("connection to PIN entry established\n"); if (opt.debug_pinentry) atfork_core (ctrl, 1); /* Just show the envvars set after the fork. */ value = session_env_getenv (ctrl->session_env, "PINENTRY_USER_DATA"); if (value != NULL) { char *optstr; if (asprintf (&optstr, "OPTION pinentry-user-data=%s", value) < 0 ) return unlock_pinentry (ctrl, out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return unlock_pinentry (ctrl, rc); } rc = assuan_transact (entry_ctx, opt.no_grab? "OPTION no-grab":"OPTION grab", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) { if (gpg_err_code (rc) == GPG_ERR_NOT_SUPPORTED || gpg_err_code (rc) == GPG_ERR_UNKNOWN_OPTION) { if (opt.verbose) log_info ("Option no-grab/grab is ignored by pinentry.\n"); /* Keep going even if the feature is not supported. */ } else return unlock_pinentry (ctrl, rc); } value = session_env_getenv (ctrl->session_env, "GPG_TTY"); if (value) { char *optstr; if (asprintf (&optstr, "OPTION ttyname=%s", value) < 0 ) return unlock_pinentry (ctrl, out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (ctrl, rc); } value = session_env_getenv (ctrl->session_env, "TERM"); if (value && *value) { char *optstr; if (asprintf (&optstr, "OPTION ttytype=%s", value) < 0 ) return unlock_pinentry (ctrl, out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (ctrl, rc); } if (ctrl->lc_ctype) { char *optstr; if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 ) return unlock_pinentry (ctrl, out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (ctrl, rc); } if (ctrl->lc_messages) { char *optstr; if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 ) return unlock_pinentry (ctrl, out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (ctrl, rc); } if (opt.allow_external_cache) { /* Indicate to the pinentry that it may read from an external cache. It is essential that the pinentry respect this. If the cached password is not up to date and retry == 1, then, using a version of GPG Agent that doesn't support this, won't issue another pin request and the user won't get a chance to correct the password. */ rc = assuan_transact (entry_ctx, "OPTION allow-external-password-cache", NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return unlock_pinentry (ctrl, rc); } if (opt.allow_emacs_pinentry) { /* Indicate to the pinentry that it may read passphrase through Emacs minibuffer, if possible. */ rc = assuan_transact (entry_ctx, "OPTION allow-emacs-prompt", NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return unlock_pinentry (ctrl, rc); } { /* Provide a few default strings for use by the pinentries. This * may help a pinentry to avoid implementing localization code. * Note that gpg-agent has been set to utf-8 so that the strings * are in the expected encoding. */ static const struct { const char *key, *value; int what; } tbl[] = { /* TRANSLATORS: These are labels for buttons etc as used in * Pinentries. In your translation copy the text before the * second vertical bar verbatim; translate only the following * text. An underscore indicates that the next letter should be * used as an accelerator. Double the underscore to have * pinentry display a literal underscore. */ { "ok", N_("|pinentry-label|_OK") }, { "cancel", N_("|pinentry-label|_Cancel") }, { "yes", N_("|pinentry-label|_Yes") }, { "no", N_("|pinentry-label|_No") }, { "prompt", N_("|pinentry-label|PIN:") }, { "pwmngr", N_("|pinentry-label|_Save in password manager"), 1 }, { "cf-visi",N_("Do you really want to make your " "passphrase visible on the screen?") }, { "tt-visi",N_("|pinentry-tt|Make passphrase visible") }, { "tt-hide",N_("|pinentry-tt|Hide passphrase") }, { "capshint", N_("Caps Lock is on") }, { NULL, NULL} }; char *optstr; int idx; const char *s, *s2; for (idx=0; tbl[idx].key; idx++) { if (!opt.allow_external_cache && tbl[idx].what == 1) continue; /* No need for it. */ s = L_(tbl[idx].value); if (*s == '|' && (s2=strchr (s+1,'|'))) s = s2+1; if (asprintf (&optstr, "OPTION default-%s=%s", tbl[idx].key, s) < 0 ) return unlock_pinentry (ctrl, out_of_core ()); assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); } } /* Tell the pinentry that we would prefer that the given character is used as the invisible character by the entry widget. */ if (opt.pinentry_invisible_char) { char *optstr; if ((optstr = xtryasprintf ("OPTION invisible-char=%s", opt.pinentry_invisible_char))) { assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); /* We ignore errors because this is just a fancy thing and older pinentries do not support this feature. */ xfree (optstr); } } if (opt.pinentry_timeout) { char *optstr; if ((optstr = xtryasprintf ("SETTIMEOUT %lu", opt.pinentry_timeout))) { assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); /* We ignore errors because this is just a fancy thing. */ xfree (optstr); } } /* Tell the pinentry the name of a file it shall touch after having messed with the tty. This is optional and only supported by newer pinentries and thus we do no error checking. */ tmpstr = opt.pinentry_touch_file; if (tmpstr && !strcmp (tmpstr, "/dev/null")) tmpstr = NULL; else if (!tmpstr) tmpstr = get_agent_socket_name (); if (tmpstr) { char *optstr; if (asprintf (&optstr, "OPTION touch-file=%s", tmpstr ) < 0 ) ; else { assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); } } /* Tell Pinentry about our client. */ if (ctrl->client_pid) { char *optstr; const char *nodename = ""; #ifndef HAVE_W32_SYSTEM struct utsname utsbuf; if (!uname (&utsbuf)) nodename = utsbuf.nodename; #endif /*!HAVE_W32_SYSTEM*/ if ((optstr = xtryasprintf ("OPTION owner=%lu/%d %s", ctrl->client_pid, ctrl->client_uid, nodename))) { assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); /* We ignore errors because this is just a fancy thing and older pinentries do not support this feature. */ xfree (optstr); } } /* Ask the pinentry for its version and flavor and store that as a * string in MB. This information is useful for helping users to * figure out Pinentry problems. Note that "flavor" may also return * a status line with the features; we use a dedicated handler for * that. */ { membuf_t mb; init_membuf (&mb, 256); if (assuan_transact (entry_ctx, "GETINFO flavor", put_membuf_cb, &mb, NULL, NULL, getinfo_features_cb, NULL)) put_membuf_str (&mb, "unknown"); put_membuf_str (&mb, " "); if (assuan_transact (entry_ctx, "GETINFO version", put_membuf_cb, &mb, NULL, NULL, NULL, NULL)) put_membuf_str (&mb, "unknown"); put_membuf_str (&mb, " "); if (assuan_transact (entry_ctx, "GETINFO ttyinfo", put_membuf_cb, &mb, NULL, NULL, NULL, NULL)) put_membuf_str (&mb, "? ? ?"); put_membuf (&mb, "", 1); flavor_version = get_membuf (&mb, NULL); } /* Now ask the Pinentry for its PID. If the Pinentry is new enough it will send the pid back and we will use an inquire to notify our client. The client may answer the inquiry either with END or with CAN to cancel the pinentry. */ rc = assuan_transact (entry_ctx, "GETINFO pid", getinfo_pid_cb, &pinentry_pid, NULL, NULL, NULL, NULL); if (rc) { log_info ("You may want to update to a newer pinentry\n"); rc = 0; } else if (!rc && pinentry_pid == (unsigned long)(-1L)) log_error ("pinentry did not return a PID\n"); else { rc = agent_inq_pinentry_launched (ctrl, pinentry_pid, flavor_version); if (gpg_err_code (rc) == GPG_ERR_CANCELED || gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED) return unlock_pinentry (ctrl, gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (rc))); rc = 0; } xfree (flavor_version); return rc; } /* Returns True if the pinentry is currently active. If WAITSECONDS is greater than zero the function will wait for this many seconds before returning. */ int pinentry_active_p (ctrl_t ctrl, int waitseconds) { int err; (void)ctrl; if (waitseconds > 0) { struct timespec abstime; int rc; npth_clock_gettime (&abstime); abstime.tv_sec += waitseconds; err = npth_mutex_timedlock (&entry_lock, &abstime); if (err) { if (err == ETIMEDOUT) rc = gpg_error (GPG_ERR_TIMEOUT); else rc = gpg_error (GPG_ERR_INTERNAL); return rc; } } else { err = npth_mutex_trylock (&entry_lock); if (err) return gpg_error (GPG_ERR_LOCKED); } err = npth_mutex_unlock (&entry_lock); if (err) log_error ("failed to release the entry lock at %d: %s\n", __LINE__, strerror (errno)); return 0; } static gpg_error_t getpin_cb (void *opaque, const void *buffer, size_t length) { struct entry_parm_s *parm = opaque; if (!buffer) return 0; /* we expect the pin to fit on one line */ if (parm->lines || length >= parm->size) return gpg_error (GPG_ERR_ASS_TOO_MUCH_DATA); /* fixme: we should make sure that the assuan buffer is allocated in secure memory or read the response byte by byte */ memcpy (parm->buffer, buffer, length); parm->buffer[length] = 0; parm->lines++; return 0; } static int all_digitsp( const char *s) { for (; *s && *s >= '0' && *s <= '9'; s++) ; return !*s; } /* Return a new malloced string by unescaping the string S. Escaping is percent escaping and '+'/space mapping. A binary Nul will silently be replaced by a 0xFF. Function returns NULL to indicate an out of memory status. Parsing stops at the end of the string or a white space character. */ static char * unescape_passphrase_string (const unsigned char *s) { char *buffer, *d; buffer = d = xtrymalloc_secure (strlen ((const char*)s)+1); if (!buffer) return NULL; while (*s && !spacep (s)) { if (*s == '%' && s[1] && s[2]) { s++; *d = xtoi_2 (s); if (!*d) *d = '\xff'; d++; s += 2; } else if (*s == '+') { *d++ = ' '; s++; } else *d++ = *s++; } *d = 0; return buffer; } /* Estimate the quality of the passphrase PW and return a value in the range 0..100. */ static int estimate_passphrase_quality (const char *pw) { int goodlength = opt.min_passphrase_len + opt.min_passphrase_len/3; int length; const char *s; if (goodlength < 1) return 0; for (length = 0, s = pw; *s; s++) if (!spacep (s)) length ++; if (length > goodlength) return 100; return ((length*10) / goodlength)*10; } /* Generate a random passphrase in zBase32 encoding (RFC-6189) to be * used by Pinentry to suggest a passphrase. */ static char * generate_pin (void) { unsigned int nbits = opt.min_passphrase_len * 8; size_t nbytes; void *rand; char *generated; if (nbits < 128) nbits = DEFAULT_GENPIN_BITS; nbytes = (nbits + 7) / 8; rand = gcry_random_bytes_secure (nbytes, GCRY_STRONG_RANDOM); if (!rand) { log_error ("failed to generate random pin\n"); return NULL; } generated = zb32_encode (rand, nbits); gcry_free (rand); return generated; } /* Handle inquiries. */ struct inq_cb_parm_s { assuan_context_t ctx; unsigned int flags; /* CHECK_CONSTRAINTS_... */ + int genpinhash_valid; + char genpinhash[32]; /* Hash of the last generated pin. */ }; +/* Return true if PIN is indentical to the last generated pin. */ +static int +is_generated_pin (struct inq_cb_parm_s *parm, const char *pin) +{ + char hashbuf[32]; + + if (!parm->genpinhash_valid) + return 0; + if (!*pin) + return 0; + /* Note that we compare the hash so that we do not need to save the + * generated PIN longer than needed. */ + gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, pin, strlen (pin)); + + if (!memcmp (hashbuf, parm->genpinhash, 32)) + return 1; /* yes, it is the same. */ + + return 0; +} + + static gpg_error_t inq_cb (void *opaque, const char *line) { struct inq_cb_parm_s *parm = opaque; gpg_error_t err; const char *s; char *pin; if ((s = has_leading_keyword (line, "QUALITY"))) { char numbuf[20]; int percent; pin = unescape_passphrase_string (s); if (!pin) err = gpg_error_from_syserror (); else { percent = estimate_passphrase_quality (pin); if (check_passphrase_constraints (NULL, pin, parm->flags, NULL)) percent = -percent; snprintf (numbuf, sizeof numbuf, "%d", percent); err = assuan_send_data (parm->ctx, numbuf, strlen (numbuf)); xfree (pin); } } else if ((s = has_leading_keyword (line, "CHECKPIN"))) { char *errtext = NULL; size_t errtextlen; if (!opt.enforce_passphrase_constraints) { log_error ("unexpected inquiry 'CHECKPIN' without enforced " "passphrase constraints\n"); err = gpg_error (GPG_ERR_ASS_UNEXPECTED_CMD); goto leave; } pin = unescape_passphrase_string (s); if (!pin) err = gpg_error_from_syserror (); else { - if (check_passphrase_constraints (NULL, pin, parm->flags, &errtext)) + if (!is_generated_pin (parm, pin) + && check_passphrase_constraints (NULL, pin,parm->flags, &errtext)) { if (errtext) { /* Unescape the percent-escaped errtext because assuan_send_data escapes it again. */ errtextlen = percent_unescape_inplace (errtext, 0); err = assuan_send_data (parm->ctx, errtext, errtextlen); } else { log_error ("passphrase check failed without error text\n"); err = gpg_error (GPG_ERR_GENERAL); } } else { err = assuan_send_data (parm->ctx, NULL, 0); } xfree (errtext); xfree (pin); } } else if ((s = has_leading_keyword (line, "GENPIN"))) { - int tries; + int wasconf; - for (tries = 0; tries < MAX_GENPIN_TRIES; tries ++) + parm->genpinhash_valid = 0; + pin = generate_pin (); + if (!pin) { - pin = generate_pin (); - if (!pin) - { - log_error ("failed to generate a passphrase\n"); - err = gpg_error (GPG_ERR_GENERAL); - goto leave; - } - if (!check_passphrase_constraints (NULL, pin, parm->flags, NULL)) - { - int wasconf = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); - assuan_begin_confidential (parm->ctx); - err = assuan_send_data (parm->ctx, pin, strlen (pin)); - if (!wasconf) - assuan_end_confidential (parm->ctx); - xfree (pin); - goto leave; - } - xfree (pin); + log_error ("failed to generate a passphrase\n"); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; } - log_error ("failed to generate a passphrase after %i tries\n", - MAX_GENPIN_TRIES); - err = gpg_error (GPG_ERR_GENERAL); + wasconf = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); + assuan_begin_confidential (parm->ctx); + err = assuan_send_data (parm->ctx, pin, strlen (pin)); + if (!wasconf) + assuan_end_confidential (parm->ctx); + gcry_md_hash_buffer (GCRY_MD_SHA256, parm->genpinhash, pin, strlen (pin)); + parm->genpinhash_valid = 1; + xfree (pin); } else { log_error ("unsupported inquiry '%s' from pinentry\n", line); err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } leave: return err; } /* Helper to setup pinentry for genpin action. */ static gpg_error_t setup_genpin (ctrl_t ctrl) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; char *tmpstr, *tmpstr2; const char *tooltip; (void)ctrl; /* TRANSLATORS: This string is displayed by Pinentry as the label for generating a passphrase. */ tmpstr = try_percent_escape (L_("Suggest"), "\t\r\n\f\v"); snprintf (line, DIM(line), "SETGENPIN %s", tmpstr? tmpstr:""); xfree (tmpstr); err = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (gpg_err_code (err) == 103 /*(Old assuan error code)*/ || gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD) ; /* Ignore Unknown Command from old Pinentry versions. */ else if (err) return err; tmpstr2 = gnupg_get_help_string ("pinentry.genpin.tooltip", 0); if (tmpstr2) tooltip = tmpstr2; else { /* TRANSLATORS: This string is a tooltip, shown by pinentry when hovering over the generate button. Please use an appropriate string to describe what this is about. The length of the tooltip is limited to about 900 characters. If you do not translate this entry, a default English text (see source) will be used. The strcmp thingy is there to detect a non-translated string. */ tooltip = L_("pinentry.genpin.tooltip"); if (!strcmp ("pinentry.genpin.tooltip", tooltip)) tooltip = "Suggest a random passphrase."; } tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v"); xfree (tmpstr2); snprintf (line, DIM(line), "SETGENPIN_TT %s", tmpstr? tmpstr:""); xfree (tmpstr); err = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (gpg_err_code (err) == 103 /*(Old assuan error code)*/ || gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD) ; /* Ignore Unknown Command from old pinentry versions. */ else if (err) return err; return 0; } /* Helper to setup pinentry for formatted passphrase. */ static gpg_error_t setup_formatted_passphrase (ctrl_t ctrl) { static const struct { const char *key, *help_id, *value; } tbl[] = { /* TRANSLATORS: This is a text shown by pinentry if the option for formatted passphrase is enabled. The length is limited to about 900 characters. */ { "hint", "pinentry.formatted_passphrase.hint", N_("Note: The blanks are not part of the passphrase.") }, { NULL, NULL } }; gpg_error_t rc; char line[ASSUAN_LINELENGTH]; int idx; char *tmpstr; const char *s; char *escapedstr; (void)ctrl; if (opt.pinentry_formatted_passphrase) { snprintf (line, DIM(line), "OPTION formatted-passphrase"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return rc; for (idx=0; tbl[idx].key; idx++) { tmpstr = gnupg_get_help_string (tbl[idx].help_id, 0); if (tmpstr) s = tmpstr; else s = L_(tbl[idx].value); escapedstr = try_percent_escape (s, "\t\r\n\f\v"); xfree (tmpstr); if (escapedstr && *escapedstr) { snprintf (line, DIM(line), "OPTION formatted-passphrase-%s=%s", tbl[idx].key, escapedstr); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } else rc = 0; xfree (escapedstr); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return rc; } } return 0; } /* Helper to setup pinentry for enforced passphrase constraints. */ static gpg_error_t setup_enforced_constraints (ctrl_t ctrl) { static const struct { const char *key, *help_id, *value; } tbl[] = { { "hint-short", "pinentry.constraints.hint.short", NULL }, { "hint-long", "pinentry.constraints.hint.long", NULL }, /* TRANSLATORS: This is a text shown by pinentry as title of a dialog telling the user that the entered new passphrase does not satisfy the passphrase constraints. Please keep it short. */ { "error-title", NULL, N_("Passphrase Not Allowed") }, { NULL, NULL } }; gpg_error_t rc; char line[ASSUAN_LINELENGTH]; int idx; char *tmpstr; const char *s; char *escapedstr; (void)ctrl; if (opt.enforce_passphrase_constraints) { snprintf (line, DIM(line), "OPTION constraints-enforce"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return rc; for (idx=0; tbl[idx].key; idx++) { tmpstr = gnupg_get_help_string (tbl[idx].help_id, 0); if (tmpstr) s = tmpstr; else if (tbl[idx].value) s = L_(tbl[idx].value); else { log_error ("no help string found for %s\n", tbl[idx].help_id); continue; } escapedstr = try_percent_escape (s, "\t\r\n\f\v"); xfree (tmpstr); if (escapedstr && *escapedstr) { snprintf (line, DIM(line), "OPTION constraints-%s=%s", tbl[idx].key, escapedstr); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } else rc = 0; /* Ignore an empty string (would give an IPC error). */ xfree (escapedstr); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return rc; } } return 0; } /* Helper for agent_askpin and agent_get_passphrase. */ static gpg_error_t setup_qualitybar (ctrl_t ctrl) { int rc; char line[ASSUAN_LINELENGTH]; char *tmpstr, *tmpstr2; const char *tooltip; (void)ctrl; /* TRANSLATORS: This string is displayed by Pinentry as the label for the quality bar. */ tmpstr = try_percent_escape (L_("Quality:"), "\t\r\n\f\v"); snprintf (line, DIM(line), "SETQUALITYBAR %s", tmpstr? tmpstr:""); xfree (tmpstr); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc == 103 /*(Old assuan error code)*/ || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD) ; /* Ignore Unknown Command from old Pinentry versions. */ else if (rc) return rc; tmpstr2 = gnupg_get_help_string ("pinentry.qualitybar.tooltip", 0); if (tmpstr2) tooltip = tmpstr2; else { /* TRANSLATORS: This string is a tooltip, shown by pinentry when hovering over the quality bar. Please use an appropriate string to describe what this is about. The length of the tooltip is limited to about 900 characters. If you do not translate this entry, a default english text (see source) will be used. */ tooltip = L_("pinentry.qualitybar.tooltip"); if (!strcmp ("pinentry.qualitybar.tooltip", tooltip)) tooltip = ("The quality of the text entered above.\n" "Please ask your administrator for " "details about the criteria."); } tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v"); xfree (tmpstr2); snprintf (line, DIM(line), "SETQUALITYBAR_TT %s", tmpstr? tmpstr:""); xfree (tmpstr); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc == 103 /*(Old assuan error code)*/ || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD) ; /* Ignore Unknown Command from old pinentry versions. */ else if (rc) return rc; return 0; } /* Check the button_info line for a close action. Also check for the PIN_REPEATED flag. */ static gpg_error_t pinentry_status_cb (void *opaque, const char *line) { unsigned int *flag = opaque; const char *args; if ((args = has_leading_keyword (line, "BUTTON_INFO"))) { if (!strcmp (args, "close")) *flag |= PINENTRY_STATUS_CLOSE_BUTTON; } else if (has_leading_keyword (line, "PIN_REPEATED")) { *flag |= PINENTRY_STATUS_PIN_REPEATED; } else if (has_leading_keyword (line, "PASSWORD_FROM_CACHE")) { *flag |= PINENTRY_STATUS_PASSWORD_FROM_CACHE; } return 0; } /* Build a SETDESC command line. This is a dedicated function so that * it can remove control characters which are not supported by the * current Pinentry. */ static void build_cmd_setdesc (char *line, size_t linelen, const char *desc) { char *src, *dst; snprintf (line, linelen, "SETDESC %s", desc); if (!entry_features.tabbing) { /* Remove RS and US. */ for (src=dst=line; *src; src++) if (!strchr ("\x1e\x1f", *src)) *dst++ = *src; *dst = 0; } } /* Watch the socket's EOF condition, while checking finish of foreground thread. When EOF condition is detected, terminate the pinentry process behind the assuan pipe. */ static void * watch_sock (void *arg) { pid_t pid = assuan_get_pid (entry_ctx); while (1) { int err; fd_set fdset; struct timeval timeout = { 0, 500000 }; gnupg_fd_t sock = *(gnupg_fd_t *)arg; if (sock == GNUPG_INVALID_FD) return NULL; FD_ZERO (&fdset); FD_SET (FD2INT (sock), &fdset); err = npth_select (FD2INT (sock)+1, &fdset, NULL, NULL, &timeout); if (err < 0) { if (errno == EINTR) continue; else return NULL; } /* Possibly, it's EOF. */ if (err > 0) break; } if (pid == (pid_t)(-1)) ; /* No pid available can't send a kill. */ #ifdef HAVE_W32_SYSTEM /* Older versions of assuan set PID to 0 on Windows to indicate an invalid value. */ else if (pid != (pid_t) INVALID_HANDLE_VALUE && pid != 0) TerminateProcess ((HANDLE)pid, 1); #else else if (pid > 0) kill (pid, SIGINT); #endif return NULL; } static gpg_error_t watch_sock_start (gnupg_fd_t *sock_p, npth_t *thread_p) { npth_attr_t tattr; int err; err = npth_attr_init (&tattr); if (err) { log_error ("do_getpin: error npth_attr_init: %s\n", strerror (err)); return gpg_error_from_errno (err); } npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); err = npth_create (thread_p, &tattr, watch_sock, sock_p); npth_attr_destroy (&tattr); if (err) { log_error ("do_getpin: error spawning thread: %s\n", strerror (err)); return gpg_error_from_errno (err); } return 0; } static void watch_sock_end (gnupg_fd_t *sock_p, npth_t *thread_p) { int err; *sock_p = GNUPG_INVALID_FD; err = npth_join (*thread_p, NULL); if (err) log_error ("watch_sock_end: error joining thread: %s\n", strerror (err)); } /* Ask pinentry to get a pin by "GETPIN" command, spawning a thread detecting the socket's EOF. */ static gpg_error_t do_getpin (ctrl_t ctrl, struct entry_parm_s *parm) { gpg_error_t rc; gnupg_fd_t sock_watched = ctrl->thread_startup.fd; npth_t thread; int wasconf; struct inq_cb_parm_s inq_cb_parm; rc = watch_sock_start (&sock_watched, &thread); if (rc) return rc; inq_cb_parm.ctx = entry_ctx; inq_cb_parm.flags = parm->constraints_flags; + inq_cb_parm.genpinhash_valid = 0; wasconf = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); assuan_begin_confidential (entry_ctx); rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, parm, inq_cb, &inq_cb_parm, pinentry_status_cb, &parm->status); if (!wasconf) assuan_end_confidential (entry_ctx); + if (!rc && parm->buffer && is_generated_pin (&inq_cb_parm, parm->buffer)) + parm->status |= PINENTRY_STATUS_PASSWORD_GENERATED; + else + parm->status &= ~PINENTRY_STATUS_PASSWORD_GENERATED; + /* Most pinentries out in the wild return the old Assuan error code for canceled which gets translated to an assuan Cancel error and not to the code for a user cancel. Fix this here. */ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); /* Change error code in case the window close button was clicked to cancel the operation. */ if ((parm->status & PINENTRY_STATUS_CLOSE_BUTTON) && gpg_err_code (rc) == GPG_ERR_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); watch_sock_end (&sock_watched, &thread); return rc; } /* Call the Entry and ask for the PIN. We do check for a valid PIN number here and repeat it as long as we have invalid formed numbers. KEYINFO and CACHE_MODE are used to tell pinentry something about the key. */ gpg_error_t agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *initial_errtext, struct pin_entry_info_s *pininfo, const char *keyinfo, cache_mode_t cache_mode) { gpg_error_t rc; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; const char *errtext = NULL; int is_pin = 0; + int is_generated; if (opt.batch) return 0; /* fixme: we should return BAD PIN */ if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) { if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) return gpg_error (GPG_ERR_CANCELED); if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) { unsigned char *passphrase; size_t size; *pininfo->pin = 0; /* Reset the PIN. */ rc = pinentry_loopback (ctrl, "PASSPHRASE", &passphrase, &size, pininfo->max_length - 1); if (rc) return rc; memcpy(&pininfo->pin, passphrase, size); xfree(passphrase); pininfo->pin[size] = 0; if (pininfo->check_cb) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); } return rc; } return gpg_error(GPG_ERR_NO_PIN_ENTRY); } if (!pininfo || pininfo->max_length < 1) return gpg_error (GPG_ERR_INV_VALUE); if (!desc_text && pininfo->min_digits) desc_text = L_("Please enter your PIN, so that the secret key " "can be unlocked for this session"); else if (!desc_text) desc_text = L_("Please enter your passphrase, so that the secret key " "can be unlocked for this session"); if (prompt_text) is_pin = !!strstr (prompt_text, "PIN"); else is_pin = desc_text && strstr (desc_text, "PIN"); rc = start_pinentry (ctrl); if (rc) return rc; /* If we have a KEYINFO string and are normal, user, or ssh cache mode, we tell that the Pinentry so it may use it for own caching purposes. Most pinentries won't have this implemented and thus we do not error out in this case. */ if (keyinfo && (cache_mode == CACHE_MODE_NORMAL || cache_mode == CACHE_MODE_USER || cache_mode == CACHE_MODE_SSH)) snprintf (line, DIM(line), "SETKEYINFO %c/%s", cache_mode == CACHE_MODE_USER? 'u' : cache_mode == CACHE_MODE_SSH? 's' : 'n', keyinfo); else snprintf (line, DIM(line), "SETKEYINFO --clear"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD) return unlock_pinentry (ctrl, rc); build_cmd_setdesc (line, DIM(line), desc_text); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); snprintf (line, DIM(line), "SETPROMPT %s", prompt_text? prompt_text : is_pin? L_("PIN:") : L_("Passphrase:")); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); /* If a passphrase quality indicator has been requested and a minimum passphrase length has not been disabled, send the command to the pinentry. */ if (pininfo->with_qualitybar && opt.min_passphrase_len ) { rc = setup_qualitybar (ctrl); if (rc) return unlock_pinentry (ctrl, rc); } if (initial_errtext) { snprintf (line, DIM(line), "SETERROR %s", initial_errtext); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); } if (pininfo->with_repeat) { snprintf (line, DIM(line), "SETREPEATERROR %s", L_("does not match - try again")); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) pininfo->with_repeat = 0; /* Pinentry does not support it. */ } pininfo->repeat_okay = 0; pininfo->status = 0; for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++) { memset (&parm, 0, sizeof parm); parm.size = pininfo->max_length; *pininfo->pin = 0; /* Reset the PIN. */ parm.buffer = (unsigned char*)pininfo->pin; parm.constraints_flags = pininfo->constraints_flags; if (errtext) { /* TRANSLATORS: The string is appended to an error message in the pinentry. The %s is the actual error message, the two %d give the current and maximum number of tries. */ snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"), errtext, pininfo->failed_tries+1, pininfo->max_tries); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); errtext = NULL; } if (pininfo->with_repeat) { snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:")); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); } rc = do_getpin (ctrl, &parm); pininfo->status = parm.status; + is_generated = !!(parm.status & PINENTRY_STATUS_PASSWORD_GENERATED); + if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA) errtext = is_pin? L_("PIN too long") : L_("Passphrase too long"); else if (rc) return unlock_pinentry (ctrl, rc); - if (!errtext && pininfo->min_digits) + if (!errtext && pininfo->min_digits && !is_generated) { /* do some basic checks on the entered PIN. */ if (!all_digitsp (pininfo->pin)) errtext = L_("Invalid characters in PIN"); else if (pininfo->max_digits && strlen (pininfo->pin) > pininfo->max_digits) errtext = L_("PIN too long"); else if (strlen (pininfo->pin) < pininfo->min_digits) errtext = L_("PIN too short"); } - if (!errtext && pininfo->check_cb) + if (!errtext && pininfo->check_cb && !is_generated) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); /* When pinentry cache causes an error, return now. */ if (rc && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) return unlock_pinentry (ctrl, rc); if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) { if (pininfo->cb_errtext) errtext = pininfo->cb_errtext; else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE || gpg_err_code (rc) == GPG_ERR_BAD_PIN) errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); } else if (rc) return unlock_pinentry (ctrl, rc); } if (!errtext) { if (pininfo->with_repeat && (pininfo->status & PINENTRY_STATUS_PIN_REPEATED)) pininfo->repeat_okay = 1; return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */ } if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) { /* The password was read from the cache. Don't count this against the retry count. */ pininfo->failed_tries --; } } return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN : GPG_ERR_BAD_PASSPHRASE)); } /* Ask for the passphrase using the supplied arguments. The returned passphrase needs to be freed by the caller. PININFO is optional and can be used to have constraints checking while the pinentry dialog is open (like what we do in agent_askpin). This is very similar to agent_askpin and we should eventually merge the two functions. */ int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext, int with_qualitybar, const char *keyinfo, cache_mode_t cache_mode, struct pin_entry_info_s *pininfo) { int rc; int is_pin; + int is_generated; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; *retpass = NULL; if (opt.batch) return gpg_error (GPG_ERR_BAD_PASSPHRASE); if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) { unsigned char *passphrase; size_t size; if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) return gpg_error (GPG_ERR_CANCELED); if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK && pininfo) { *pininfo->pin = 0; /* Reset the PIN. */ rc = pinentry_loopback (ctrl, "PASSPHRASE", &passphrase, &size, pininfo->max_length - 1); if (rc) return rc; memcpy (&pininfo->pin, passphrase, size); wipememory (passphrase, size); xfree (passphrase); pininfo->pin[size] = 0; if (pininfo->check_cb) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); } return rc; } else if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) { /* Legacy variant w/o PININFO. */ return pinentry_loopback (ctrl, "PASSPHRASE", (unsigned char **)retpass, &size, MAX_PASSPHRASE_LEN); } return gpg_error (GPG_ERR_NO_PIN_ENTRY); } rc = start_pinentry (ctrl); if (rc) return rc; /* Set IS_PIN and if needed a default prompt. */ if (prompt) is_pin = !!strstr (prompt, "PIN"); else { is_pin = desc && strstr (desc, "PIN"); prompt = is_pin? L_("PIN:"): L_("Passphrase:"); } /* If we have a KEYINFO string and are normal, user, or ssh cache mode, we tell that the Pinentry so it may use it for own caching purposes. Most pinentries won't have this implemented and thus we do not error out in this case. */ if (keyinfo && (cache_mode == CACHE_MODE_NORMAL || cache_mode == CACHE_MODE_USER || cache_mode == CACHE_MODE_SSH)) snprintf (line, DIM(line), "SETKEYINFO %c/%s", cache_mode == CACHE_MODE_USER? 'u' : cache_mode == CACHE_MODE_SSH? 's' : 'n', keyinfo); else snprintf (line, DIM(line), "SETKEYINFO --clear"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD) return unlock_pinentry (ctrl, rc); if (desc) build_cmd_setdesc (line, DIM(line), desc); else snprintf (line, DIM(line), "RESET"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); snprintf (line, DIM(line), "SETPROMPT %s", prompt); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); if ((with_qualitybar || (pininfo && pininfo->with_qualitybar)) && opt.min_passphrase_len) { rc = setup_qualitybar (ctrl); if (rc) return unlock_pinentry (ctrl, rc); } if (errtext) { snprintf (line, DIM(line), "SETERROR %s", errtext); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); } rc = setup_formatted_passphrase (ctrl); if (rc) return unlock_pinentry (ctrl, rc); if (!pininfo) { /* Legacy method without PININFO. */ memset (&parm, 0, sizeof parm); parm.size = ASSUAN_LINELENGTH/2 - 5; parm.buffer = gcry_malloc_secure (parm.size+10); if (!parm.buffer) return unlock_pinentry (ctrl, out_of_core ()); rc = do_getpin (ctrl, &parm); if (rc) xfree (parm.buffer); else *retpass = parm.buffer; return unlock_pinentry (ctrl, rc); } /* We got PININFO. */ if (pininfo->with_repeat) { snprintf (line, DIM(line), "SETREPEATERROR %s", L_("does not match - try again")); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) pininfo->with_repeat = 0; /* Pinentry does not support it. */ (void)setup_genpin (ctrl); rc = setup_enforced_constraints (ctrl); if (rc) return unlock_pinentry (ctrl, rc); } pininfo->repeat_okay = 0; pininfo->status = 0; for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++) { memset (&parm, 0, sizeof parm); + parm.constraints_flags = pininfo->constraints_flags; parm.size = pininfo->max_length; parm.buffer = (unsigned char*)pininfo->pin; *pininfo->pin = 0; /* Reset the PIN. */ if (errtext) { /* TRANSLATORS: The string is appended to an error message in the pinentry. The %s is the actual error message, the two %d give the current and maximum number of tries. */ snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"), errtext, pininfo->failed_tries+1, pininfo->max_tries); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); errtext = NULL; } if (pininfo->with_repeat) { snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:")); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); } rc = do_getpin (ctrl, &parm); pininfo->status = parm.status; + is_generated = !!(parm.status & PINENTRY_STATUS_PASSWORD_GENERATED); + if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA) errtext = is_pin? L_("PIN too long") : L_("Passphrase too long"); else if (rc) return unlock_pinentry (ctrl, rc); - if (!errtext && pininfo->min_digits) + if (!errtext && pininfo->min_digits && !is_generated) { /* do some basic checks on the entered PIN. */ if (!all_digitsp (pininfo->pin)) errtext = L_("Invalid characters in PIN"); else if (pininfo->max_digits && strlen (pininfo->pin) > pininfo->max_digits) errtext = L_("PIN too long"); else if (strlen (pininfo->pin) < pininfo->min_digits) errtext = L_("PIN too short"); } - if (!errtext && pininfo->check_cb) + if (!errtext && pininfo->check_cb && !is_generated) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); /* When pinentry cache causes an error, return now. */ if (rc && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) return unlock_pinentry (ctrl, rc); if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) { if (pininfo->cb_errtext) errtext = pininfo->cb_errtext; else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE || gpg_err_code (rc) == GPG_ERR_BAD_PIN) errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); } else if (rc) return unlock_pinentry (ctrl, rc); } if (!errtext) { if (pininfo->with_repeat && (pininfo->status & PINENTRY_STATUS_PIN_REPEATED)) pininfo->repeat_okay = 1; return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */ } if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) { /* The password was read from the Pinentry's own cache. Don't count this against the retry count. */ pininfo->failed_tries--; } } return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN : GPG_ERR_BAD_PASSPHRASE)); } /* Pop up the PIN-entry, display the text and the prompt and ask the user to confirm this. We return 0 for success, ie. the user confirmed it, GPG_ERR_NOT_CONFIRMED for what the text says or an other error. If WITH_CANCEL it true an extra cancel button is displayed to allow the user to easily return a GPG_ERR_CANCELED. if the Pinentry does not support this, the user can still cancel by closing the Pinentry window. */ int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok, const char *notok, int with_cancel) { int rc; char line[ASSUAN_LINELENGTH]; if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) { if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) return gpg_error (GPG_ERR_CANCELED); if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) return pinentry_loopback_confirm (ctrl, desc, 1, ok, notok); return gpg_error (GPG_ERR_NO_PIN_ENTRY); } rc = start_pinentry (ctrl); if (rc) return rc; if (desc) build_cmd_setdesc (line, DIM(line), desc); else snprintf (line, DIM(line), "RESET"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); /* Most pinentries out in the wild return the old Assuan error code for canceled which gets translated to an assuan Cancel error and not to the code for a user cancel. Fix this here. */ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); if (rc) return unlock_pinentry (ctrl, rc); if (ok) { snprintf (line, DIM(line), "SETOK %s", ok); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); } if (notok) { /* Try to use the newer NOTOK feature if a cancel button is requested. If no cancel button is requested we keep on using the standard cancel. */ if (with_cancel) { snprintf (line, DIM(line), "SETNOTOK %s", notok); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } else rc = GPG_ERR_ASS_UNKNOWN_CMD; if (gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD) { snprintf (line, DIM(line), "SETCANCEL %s", notok); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } if (rc) return unlock_pinentry (ctrl, rc); } { gnupg_fd_t sock_watched = ctrl->thread_startup.fd; npth_t thread; rc = watch_sock_start (&sock_watched, &thread); if (!rc) { rc = assuan_transact (entry_ctx, "CONFIRM", NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); watch_sock_end (&sock_watched, &thread); } return unlock_pinentry (ctrl, rc); } } /* The thread running the popup message. */ static void * popup_message_thread (void *arg) { gpg_error_t rc; gnupg_fd_t sock_watched = *(gnupg_fd_t *)arg; npth_t thread; rc = watch_sock_start (&sock_watched, &thread); if (rc) return NULL; /* We use the --one-button hack instead of the MESSAGE command to allow the use of old Pinentries. Those old Pinentries will then show an additional Cancel button but that is mostly a visual annoyance. */ assuan_transact (entry_ctx, "CONFIRM --one-button", NULL, NULL, NULL, NULL, NULL, NULL); watch_sock_end (&sock_watched, &thread); popup_finished = 1; return NULL; } /* Pop up a message window similar to the confirm one but keep it open until agent_popup_message_stop has been called. It is crucial for the caller to make sure that the stop function gets called as soon as the message is not anymore required because the message is system modal and all other attempts to use the pinentry will fail (after a timeout). */ int agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn) { int rc; char line[ASSUAN_LINELENGTH]; npth_attr_t tattr; int err; if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) { if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) return gpg_error (GPG_ERR_CANCELED); if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) return pinentry_loopback_confirm (ctrl, desc, 0, ok_btn, NULL); return gpg_error (GPG_ERR_NO_PIN_ENTRY); } rc = start_pinentry (ctrl); if (rc) return rc; if (desc) build_cmd_setdesc (line, DIM(line), desc); else snprintf (line, DIM(line), "RESET"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); if (ok_btn) { snprintf (line, DIM(line), "SETOK %s", ok_btn); rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL); if (rc) return unlock_pinentry (ctrl, rc); } err = npth_attr_init (&tattr); if (err) return unlock_pinentry (ctrl, gpg_error_from_errno (err)); npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); popup_finished = 0; err = npth_create (&popup_tid, &tattr, popup_message_thread, &ctrl->thread_startup.fd); npth_attr_destroy (&tattr); if (err) { rc = gpg_error_from_errno (err); log_error ("error spawning popup message handler: %s\n", strerror (err) ); return unlock_pinentry (ctrl, rc); } npth_setname_np (popup_tid, "popup-message"); return 0; } /* Close a popup window. */ void agent_popup_message_stop (ctrl_t ctrl) { int rc; pid_t pid; (void)ctrl; if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) return; if (!popup_tid || !entry_ctx) { log_debug ("agent_popup_message_stop called with no active popup\n"); return; } pid = assuan_get_pid (entry_ctx); if (pid == (pid_t)(-1)) ; /* No pid available can't send a kill. */ else 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 /* Now wait for the thread to terminate. */ rc = npth_join (popup_tid, NULL); if (rc) log_debug ("agent_popup_message_stop: pth_join failed: %s\n", strerror (rc)); /* Thread IDs are opaque, but we try our best here by resetting it to the same content that a static global variable has. */ memset (&popup_tid, '\0', sizeof (popup_tid)); /* Now we can close the connection. */ unlock_pinentry (ctrl, 0); } int agent_clear_passphrase (ctrl_t ctrl, const char *keyinfo, cache_mode_t cache_mode) { int rc; char line[ASSUAN_LINELENGTH]; if (! (keyinfo && (cache_mode == CACHE_MODE_NORMAL || cache_mode == CACHE_MODE_USER || cache_mode == CACHE_MODE_SSH))) return gpg_error (GPG_ERR_NOT_SUPPORTED); rc = start_pinentry (ctrl); if (rc) return rc; snprintf (line, DIM(line), "CLEARPASSPHRASE %c/%s", cache_mode == CACHE_MODE_USER? 'u' : cache_mode == CACHE_MODE_SSH? 's' : 'n', keyinfo); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return unlock_pinentry (ctrl, rc); } diff --git a/agent/command.c b/agent/command.c index dd1a2f122..5e2dbc809 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1,4199 +1,4206 @@ /* command.c - gpg-agent command handler * Copyright (C) 2001-2011 Free Software Foundation, Inc. * Copyright (C) 2001-2013 Werner Koch * Copyright (C) 2015-2021 g10 Code GmbH. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* FIXME: we should not use the default assuan buffering but setup some buffering in secure mempory to protect session keys etc. */ #include #include #include #include #include #include #include #include #include #include #include "agent.h" #include #include "../common/i18n.h" #include "cvt-openpgp.h" #include "../common/ssh-utils.h" #include "../common/asshelp.h" #include "../common/server-help.h" /* Maximum allowed size of the inquired ciphertext. */ #define MAXLEN_CIPHERTEXT 4096 /* Maximum allowed size of the key parameters. */ #define MAXLEN_KEYPARAM 1024 /* Maximum allowed size of key data as used in inquiries (bytes). */ #define MAXLEN_KEYDATA 8192 /* Maximum length of a secret to store under one key. */ #define MAXLEN_PUT_SECRET 4096 /* The size of the import/export KEK key (in bytes). */ #define KEYWRAP_KEYSIZE (128/8) /* A shortcut to call assuan_set_error using an gpg_err_code_t and a text string. */ #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) /* Check that the maximum digest length we support has at least the length of the keygrip. */ #if MAX_DIGEST_LEN < 20 #error MAX_DIGEST_LEN shorter than keygrip #endif /* Data used to associate an Assuan context with local server data. This is this modules local part of the server_control_s struct. */ struct server_local_s { /* Our Assuan context. */ assuan_context_t assuan_ctx; /* If this flag is true, the passphrase cache is used for signing operations. It defaults to true but may be set on a per connection base. The global option opt.ignore_cache_for_signing takes precedence over this flag. */ unsigned int use_cache_for_signing : 1; /* Flag to suppress I/O logging during a command. */ unsigned int pause_io_logging : 1; /* Flag indicating that the connection is from ourselves. */ unsigned int connect_from_self : 1; /* Helper flag for io_monitor to allow suppressing of our own * greeting in some cases. See io_monitor for details. */ unsigned int greeting_seen : 1; /* If this flag is set to true the agent will be terminated after the end of the current session. */ unsigned int stopme : 1; /* Flag indicating whether pinentry notifications shall be done. */ unsigned int allow_pinentry_notify : 1; /* An allocated description for the next key operation. This is used if a pinnetry needs to be popped up. */ char *keydesc; /* Malloced KEK (Key-Encryption-Key) for the import_key command. */ void *import_key; /* Malloced KEK for the export_key command. */ void *export_key; /* Client is aware of the error code GPG_ERR_FULLY_CANCELED. */ int allow_fully_canceled; /* Last CACHE_NONCE sent as status (malloced). */ char *last_cache_nonce; /* Last PASSWD_NONCE sent as status (malloced). */ char *last_passwd_nonce; /* Per connection cache of the keyinfo from the cards. The * eventcounters for cards at the time the info was fetched is * stored here as a freshness indicator. */ struct { struct card_key_info_s *ki; unsigned int eventno; unsigned int maybe_key_change; } last_card_keyinfo; }; /* An entry for the getval/putval commands. */ struct putval_item_s { struct putval_item_s *next; size_t off; /* Offset to the value into DATA. */ size_t len; /* Length of the value. */ char d[1]; /* Key | Nul | value. */ }; /* A list of key value pairs fpr the getval/putval commands. */ static struct putval_item_s *putval_list; /* To help polling clients, we keep track of the number of certain events. This structure keeps those counters. The counters are integers and there should be no problem if they are overflowing as callers need to check only whether a counter changed. The actual values are not meaningful. */ struct { /* Incremented if any of the other counters below changed. */ unsigned int any; /* Incremented if a key is added or removed from the internal privat key database. */ unsigned int key; /* Incremented if a change of the card readers stati has been detected. */ unsigned int card; /* Internal counter to track possible changes to a key. * FIXME: This should be replaced by generic notifications from scd. */ unsigned int maybe_key_change; } eventcounter; /* Local prototypes. */ static int command_has_option (const char *cmd, const char *cmdopt); /* Release the memory buffer MB but first wipe out the used memory. */ static void clear_outbuf (membuf_t *mb) { void *p; size_t n; p = get_membuf (mb, &n); if (p) { wipememory (p, n); xfree (p); } } /* Write the content of memory buffer MB as assuan data to CTX and wipe the buffer out afterwards. */ static gpg_error_t write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb) { gpg_error_t ae; void *p; size_t n; p = get_membuf (mb, &n); if (!p) return out_of_core (); ae = assuan_send_data (ctx, p, n); memset (p, 0, n); xfree (p); return ae; } /* Clear the nonces used to enable the passphrase cache for certain multi-command command sequences. */ static void clear_nonce_cache (ctrl_t ctrl) { if (ctrl->server_local->last_cache_nonce) { agent_put_cache (ctrl, ctrl->server_local->last_cache_nonce, CACHE_MODE_NONCE, NULL, 0); xfree (ctrl->server_local->last_cache_nonce); ctrl->server_local->last_cache_nonce = NULL; } if (ctrl->server_local->last_passwd_nonce) { agent_put_cache (ctrl, ctrl->server_local->last_passwd_nonce, CACHE_MODE_NONCE, NULL, 0); xfree (ctrl->server_local->last_passwd_nonce); ctrl->server_local->last_passwd_nonce = NULL; } } /* This function is called by Libassuan whenever the client sends a reset. It has been registered similar to the other Assuan commands. */ static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) line; memset (ctrl->keygrip, 0, 20); ctrl->have_keygrip = 0; ctrl->digest.valuelen = 0; xfree (ctrl->digest.data); ctrl->digest.data = NULL; xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; clear_nonce_cache (ctrl); return 0; } /* Replace all '+' by a blank in the string S. */ static void plus_to_blank (char *s) { for (; *s; s++) { if (*s == '+') *s = ' '; } } /* Parse a hex string. Return an Assuan error code or 0 on success and the length of the parsed string in LEN. */ static int parse_hexstring (assuan_context_t ctx, const char *string, size_t *len) { const char *p; size_t n; /* parse the hash value */ for (p=string, n=0; hexdigitp (p); p++, n++) ; if (*p != ' ' && *p != '\t' && *p) return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring"); if ((n&1)) return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits"); *len = n; return 0; } /* Parse the keygrip in STRING into the provided buffer BUF. BUF must provide space for 20 bytes. BUF is not changed if the function returns an error. */ static int parse_keygrip (assuan_context_t ctx, const char *string, unsigned char *buf) { int rc; size_t n = 0; rc = parse_hexstring (ctx, string, &n); if (rc) return rc; n /= 2; if (n != 20) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of keygrip"); if (hex2bin (string, buf, 20) < 0) return set_error (GPG_ERR_BUG, "hex2bin"); return 0; } /* Parse the TTL from STRING. Leading and trailing spaces are * skipped. The value is constrained to -1 .. MAXINT. On error 0 is * returned, else the number of bytes scanned. */ static size_t parse_ttl (const char *string, int *r_ttl) { const char *string_orig = string; long ttl; char *pend; ttl = strtol (string, &pend, 10); string = pend; if (string == string_orig || !(spacep (string) || !*string) || ttl < -1L || (int)ttl != (long)ttl) { *r_ttl = 0; return 0; } while (spacep (string) || *string== '\n') string++; *r_ttl = (int)ttl; return string - string_orig; } /* Write an Assuan status line. KEYWORD is the first item on the * status line. The following arguments are all separated by a space * in the output. The last argument must be a NULL. Linefeeds and * carriage returns characters (which are not allowed in an Assuan * status line) are silently quoted in C-style. */ gpg_error_t agent_write_status (ctrl_t ctrl, const char *keyword, ...) { gpg_error_t err; va_list arg_ptr; assuan_context_t ctx = ctrl->server_local->assuan_ctx; va_start (arg_ptr, keyword); err = vprint_assuan_status_strings (ctx, keyword, arg_ptr); va_end (arg_ptr); return err; } /* This function is similar to print_assuan_status but takes a CTRL arg instead of an assuan context as first argument. */ gpg_error_t agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...) { gpg_error_t err; va_list arg_ptr; assuan_context_t ctx = ctrl->server_local->assuan_ctx; va_start (arg_ptr, format); err = vprint_assuan_status (ctx, keyword, format, arg_ptr); va_end (arg_ptr); return err; } /* Helper to notify the client about a launched Pinentry. Because that might disturb some older clients, this is only done if enabled via an option. Returns an gpg error code. */ gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, const char *extra) { char line[256]; if (!ctrl || !ctrl->server_local || !ctrl->server_local->allow_pinentry_notify) return 0; snprintf (line, DIM(line), "PINENTRY_LAUNCHED %lu%s%s", pid, extra?" ":"", extra? extra:""); return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0); } /* An agent progress callback for Libgcrypt. This has been registered * to be called via the progress dispatcher mechanism from * gpg-agent.c */ static void progress_cb (ctrl_t ctrl, const char *what, int printchar, int current, int total) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) ; else if (printchar == '\n' && what && !strcmp (what, "primegen")) agent_print_status (ctrl, "PROGRESS", "%.20s X 100 100", what); else agent_print_status (ctrl, "PROGRESS", "%.20s %c %d %d", what, printchar=='\n'?'X':printchar, current, total); } /* Helper to print a message while leaving a command. Note that this * function does not call assuan_set_error; the caller may do this * prior to calling us. */ static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) { if (err) { const char *name = assuan_get_command_name (ctx); if (!name) name = "?"; /* Not all users of gpg-agent know about the fully canceled error code; map it back if needed. */ if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) { ctrl_t ctrl = assuan_get_pointer (ctx); if (!ctrl->server_local->allow_fully_canceled) err = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED); } /* Most code from common/ does not know the error source, thus we fix this here. */ if (gpg_err_source (err) == GPG_ERR_SOURCE_UNKNOWN) err = gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (err)); if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) log_error ("command '%s' failed: %s\n", name, gpg_strerror (err)); else log_error ("command '%s' failed: %s <%s>\n", name, gpg_strerror (err), gpg_strsource (err)); } return err; } /* Take the keyinfo for cards from our local cache. Actually this * cache could be a global one but then we would need to employ * reference counting. */ struct card_key_info_s * get_keyinfo_on_cards (ctrl_t ctrl) { struct card_key_info_s *keyinfo_on_cards; if (ctrl->server_local->last_card_keyinfo.ki && ctrl->server_local->last_card_keyinfo.eventno == eventcounter.card && (ctrl->server_local->last_card_keyinfo.maybe_key_change == eventcounter.maybe_key_change)) { keyinfo_on_cards = ctrl->server_local->last_card_keyinfo.ki; } else if (!agent_card_keyinfo (ctrl, NULL, 0, &keyinfo_on_cards)) { agent_card_free_keyinfo (ctrl->server_local->last_card_keyinfo.ki); ctrl->server_local->last_card_keyinfo.ki = keyinfo_on_cards; ctrl->server_local->last_card_keyinfo.eventno = eventcounter.card; ctrl->server_local->last_card_keyinfo.maybe_key_change = eventcounter.maybe_key_change; } return keyinfo_on_cards; } static const char hlp_geteventcounter[] = "GETEVENTCOUNTER\n" "\n" "Return a status line named EVENTCOUNTER with the current values\n" "of all event counters. The values are decimal numbers in the range\n" "0 to UINT_MAX and wrapping around to 0. The actual values should\n" "not be relied upon, they shall only be used to detect a change.\n" "\n" "The currently defined counters are:\n" "\n" "ANY - Incremented with any change of any of the other counters.\n" "KEY - Incremented for added or removed private keys.\n" "CARD - Incremented for changes of the card readers stati."; static gpg_error_t cmd_geteventcounter (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); return agent_print_status (ctrl, "EVENTCOUNTER", "%u %u %u", eventcounter.any, eventcounter.key, eventcounter.card); } /* This function should be called once for all key removals or additions. This function is assured not to do any context switches. */ void bump_key_eventcounter (void) { eventcounter.key++; eventcounter.any++; } /* This function should be called for all card reader status changes. This function is assured not to do any context switches. */ void bump_card_eventcounter (void) { eventcounter.card++; eventcounter.any++; } static const char hlp_istrusted[] = "ISTRUSTED \n" "\n" "Return OK when we have an entry with this fingerprint in our\n" "trustlist"; static gpg_error_t cmd_istrusted (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc, n, i; char *p; char fpr[41]; /* Parse the fingerprint value. */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (*p || !(n == 40 || n == 32)) return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint"); i = 0; if (n==32) { strcpy (fpr, "00000000"); i += 8; } for (p=line; i < 40; p++, i++) fpr[i] = *p >= 'a'? (*p & 0xdf): *p; fpr[i] = 0; rc = agent_istrusted (ctrl, fpr, NULL); if (!rc || gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED) return rc; else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF ) return gpg_error (GPG_ERR_NOT_TRUSTED); else return leave_cmd (ctx, rc); } static const char hlp_listtrusted[] = "LISTTRUSTED\n" "\n" "List all entries from the trustlist."; static gpg_error_t cmd_listtrusted (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); rc = agent_listtrusted (ctx); return leave_cmd (ctx, rc); } static const char hlp_martrusted[] = "MARKTRUSTED \n" "\n" "Store a new key in into the trustlist."; static gpg_error_t cmd_marktrusted (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc, n, i; char *p; char fpr[41]; int flag; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); /* parse the fingerprint value */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (!spacep (p) || !(n == 40 || n == 32)) return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint"); i = 0; if (n==32) { strcpy (fpr, "00000000"); i += 8; } for (p=line; i < 40; p++, i++) fpr[i] = *p >= 'a'? (*p & 0xdf): *p; fpr[i] = 0; while (spacep (p)) p++; flag = *p++; if ( (flag != 'S' && flag != 'P') || !spacep (p) ) return set_error (GPG_ERR_ASS_PARAMETER, "invalid flag - must be P or S"); while (spacep (p)) p++; rc = agent_marktrusted (ctrl, p, fpr, flag); return leave_cmd (ctx, rc); } static const char hlp_havekey[] = "HAVEKEY \n" "HAVEKEY --list[=]\n" "\n" "Return success if at least one of the secret keys with the given\n" "keygrips is available. With --list return all availabale keygrips\n" "as binary data; with bail out at this number of keygrips"; static gpg_error_t cmd_havekey (assuan_context_t ctx, char *line) { ctrl_t ctrl; gpg_error_t err; unsigned char grip[20]; char *p; int list_mode; /* Less than 0 for no limit. */ int counter; char *dirname; gnupg_dir_t dir; gnupg_dirent_t dir_entry; char hexgrip[41]; struct card_key_info_s *keyinfo_on_cards, *l; if (has_option_name (line, "--list")) { if ((p = option_value (line, "--list"))) list_mode = atoi (p); else list_mode = -1; } else list_mode = 0; if (!list_mode) { do { err = parse_keygrip (ctx, line, grip); if (err) return err; if (!agent_key_available (grip)) return 0; /* Found. */ while (*line && *line != ' ' && *line != '\t') line++; while (*line == ' ' || *line == '\t') line++; } while (*line); /* No leave_cmd() here because errors are expected and would clutter * the log. */ return gpg_error (GPG_ERR_NO_SECKEY); } /* List mode. */ dir = NULL; dirname = NULL; ctrl = assuan_get_pointer (ctx); if (ctrl->restricted) { err = gpg_error (GPG_ERR_FORBIDDEN); goto leave; } dirname = make_filename_try (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, NULL); if (!dirname) { err = gpg_error_from_syserror (); goto leave; } dir = gnupg_opendir (dirname); if (!dir) { err = gpg_error_from_syserror (); goto leave; } counter = 0; while ((dir_entry = gnupg_readdir (dir))) { if (strlen (dir_entry->d_name) != 44 || strcmp (dir_entry->d_name + 40, ".key")) continue; strncpy (hexgrip, dir_entry->d_name, 40); hexgrip[40] = 0; if ( hex2bin (hexgrip, grip, 20) < 0 ) continue; /* Bad hex string. */ if (list_mode > 0 && ++counter > list_mode) { err = gpg_error (GPG_ERR_TRUNCATED); goto leave; } err = assuan_send_data (ctx, grip, 20); if (err) goto leave; } /* And now the keys from the current cards. If they already got a * stub, they are listed twice but we don't care. */ keyinfo_on_cards = get_keyinfo_on_cards (ctrl); for (l = keyinfo_on_cards; l; l = l->next) { if ( hex2bin (l->keygrip, grip, 20) < 0 ) continue; /* Bad hex string. */ if (list_mode > 0 && ++counter > list_mode) { err = gpg_error (GPG_ERR_TRUNCATED); goto leave; } err = assuan_send_data (ctx, grip, 20); if (err) goto leave; } err = 0; leave: gnupg_closedir (dir); xfree (dirname); return leave_cmd (ctx, err); } static const char hlp_sigkey[] = "SIGKEY \n" "SETKEY \n" "\n" "Set the key used for a sign or decrypt operation."; static gpg_error_t cmd_sigkey (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); rc = parse_keygrip (ctx, line, ctrl->keygrip); if (rc) return rc; ctrl->have_keygrip = 1; return 0; } static const char hlp_setkeydesc[] = "SETKEYDESC plus_percent_escaped_string\n" "\n" "Set a description to be used for the next PKSIGN, PKDECRYPT, IMPORT_KEY\n" "or EXPORT_KEY operation if this operation requires a passphrase. If\n" "this command is not used a default text will be used. Note, that\n" "this description implicitly selects the label used for the entry\n" "box; if the string contains the string PIN (which in general will\n" "not be translated), \"PIN\" is used, otherwise the translation of\n" "\"passphrase\" is used. The description string should not contain\n" "blanks unless they are percent or '+' escaped.\n" "\n" "The description is only valid for the next PKSIGN, PKDECRYPT,\n" "IMPORT_KEY, EXPORT_KEY, or DELETE_KEY operation."; static gpg_error_t cmd_setkeydesc (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *desc, *p; for (p=line; *p == ' '; p++) ; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* We ignore any garbage; we might late use it for other args. */ if (!*desc) return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); /* Note, that we only need to replace the + characters and should leave the other escaping in place because the escaped string is send verbatim to the pinentry which does the unescaping (but not the + replacing) */ plus_to_blank (desc); xfree (ctrl->server_local->keydesc); if (ctrl->restricted) { ctrl->server_local->keydesc = strconcat ((ctrl->restricted == 2 ? _("Note: Request from the web browser.") : _("Note: Request from a remote site.") ), "%0A%0A", desc, NULL); } else ctrl->server_local->keydesc = xtrystrdup (desc); if (!ctrl->server_local->keydesc) return out_of_core (); return 0; } static const char hlp_sethash[] = "SETHASH (--hash=)|() ]\n" "SETHASH [--pss] --inquire\n" "\n" "The client can use this command to tell the server about the data\n" "(which usually is a hash) to be signed. The option --inquire is\n" "used to ask back for to-be-signed data in case of PureEdDSA or\n" "with --pss for pre-formatted rsaPSS."; static gpg_error_t cmd_sethash (assuan_context_t ctx, char *line) { gpg_error_t err; size_t n; char *p; ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *buf; char *endp; int algo; int opt_inquire, opt_pss; /* Parse the alternative hash options which may be used instead of the algo number. */ if (has_option_name (line, "--hash")) { if (has_option (line, "--hash=sha1")) algo = GCRY_MD_SHA1; else if (has_option (line, "--hash=sha224")) algo = GCRY_MD_SHA224; else if (has_option (line, "--hash=sha256")) algo = GCRY_MD_SHA256; else if (has_option (line, "--hash=sha384")) algo = GCRY_MD_SHA384; else if (has_option (line, "--hash=sha512")) algo = GCRY_MD_SHA512; else if (has_option (line, "--hash=rmd160")) algo = GCRY_MD_RMD160; else if (has_option (line, "--hash=md5")) algo = GCRY_MD_MD5; else if (has_option (line, "--hash=tls-md5sha1")) algo = MD_USER_TLS_MD5SHA1; else if (has_option (line, "--hash=none")) algo = 0; else { err = set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm"); goto leave; } } else algo = 0; opt_pss = has_option (line, "--pss"); opt_inquire = has_option (line, "--inquire"); line = skip_options (line); if (!algo && !opt_inquire) { /* No hash option has been given: require an algo number instead */ algo = (int)strtoul (line, &endp, 10); for (line = endp; *line == ' ' || *line == '\t'; line++) ; if (!algo || gcry_md_test_algo (algo)) { err = set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL); goto leave; } } xfree (ctrl->digest.data); ctrl->digest.data = NULL; ctrl->digest.algo = algo; ctrl->digest.raw_value = 0; ctrl->digest.is_pss = opt_pss; if (opt_inquire) { /* We limit the to-be-signed data to some reasonable size which * may eventually allow us to pass that even to smartcards. */ size_t maxlen = 2048; if (algo) { err = set_error (GPG_ERR_ASS_PARAMETER, "both --inquire and an algo are specified"); goto leave; } err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen); if (!err) err = assuan_inquire (ctx, "TBSDATA", &buf, &n, maxlen); if (err) goto leave; ctrl->digest.data = buf; ctrl->digest.valuelen = n; } else { /* Parse the hash value. */ n = 0; err = parse_hexstring (ctx, line, &n); if (err) goto leave; n /= 2; if (algo == MD_USER_TLS_MD5SHA1 && n == 36) ; else if (n != 16 && n != 20 && n != 24 && n != 28 && n != 32 && n != 48 && n != 64) { err = set_error (GPG_ERR_ASS_PARAMETER, "unsupported length of hash"); goto leave; } if (n > MAX_DIGEST_LEN) { err = set_error (GPG_ERR_ASS_PARAMETER, "hash value to long"); goto leave; } buf = ctrl->digest.value; ctrl->digest.valuelen = n; for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++) buf[n] = xtoi_2 (p); for (; n < ctrl->digest.valuelen; n++) buf[n] = 0; } leave: return leave_cmd (ctx, err); } static const char hlp_pksign[] = "PKSIGN [] []\n" "\n" "Perform the actual sign operation. Neither input nor output are\n" "sensitive to eavesdropping."; static gpg_error_t cmd_pksign (assuan_context_t ctx, char *line) { gpg_error_t err; cache_mode_t cache_mode = CACHE_MODE_NORMAL; ctrl_t ctrl = assuan_get_pointer (ctx); membuf_t outbuf; char *cache_nonce = NULL; char *p; line = skip_options (line); for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; *p = '\0'; if (*line) cache_nonce = xtrystrdup (line); if (opt.ignore_cache_for_signing) cache_mode = CACHE_MODE_IGNORE; else if (!ctrl->server_local->use_cache_for_signing) cache_mode = CACHE_MODE_IGNORE; init_membuf (&outbuf, 512); err = agent_pksign (ctrl, cache_nonce, ctrl->server_local->keydesc, &outbuf, cache_mode); if (err) clear_outbuf (&outbuf); else err = write_and_clear_outbuf (ctx, &outbuf); xfree (cache_nonce); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, err); } static const char hlp_pkdecrypt[] = "PKDECRYPT []\n" "\n" "Perform the actual decrypt operation. Input is not\n" "sensitive to eavesdropping."; static gpg_error_t cmd_pkdecrypt (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *value; size_t valuelen; membuf_t outbuf; int padding; (void)line; /* 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) return rc; init_membuf (&outbuf, 512); rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc, value, valuelen, &outbuf, &padding); xfree (value); if (rc) clear_outbuf (&outbuf); else { if (padding != -1) rc = print_assuan_status (ctx, "PADDING", "%d", padding); else rc = 0; if (!rc) rc = write_and_clear_outbuf (ctx, &outbuf); } xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, rc); } static const char hlp_genkey[] = "GENKEY [--no-protection] [--preset] [--timestamp=]\n" " [--inq-passwd] [--passwd-nonce=] []\n" "\n" "Generate a new key, store the secret part and return the public\n" "part. Here is an example transaction:\n" "\n" " C: GENKEY\n" " S: INQUIRE KEYPARAM\n" " C: D (genkey (rsa (nbits 3072)))\n" " C: END\n" " S: D (public-key\n" " S: D (rsa (n 326487324683264) (e 10001)))\n" " S: OK key created\n" "\n" "If the --preset option is used the passphrase for the generated\n" "key will be added to the cache. If --inq-passwd is used an inquire\n" "with the keyword NEWPASSWD is used to request the passphrase for the\n" "new key. If a --passwd-nonce is used, the corresponding cached\n" "passphrase is used to protect the new key. If --timestamp is given\n" "its value is recorded as the key's creation time; the value is\n" "expected in ISO format (e.g. \"20030316T120000\")."; static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int no_protection; unsigned char *value = NULL; size_t valuelen; unsigned char *newpasswd = NULL; membuf_t outbuf; char *cache_nonce = NULL; char *passwd_nonce = NULL; int opt_preset; int opt_inq_passwd; size_t n; char *p, *pend; const char *s; time_t opt_timestamp; int c; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); no_protection = has_option (line, "--no-protection"); opt_preset = has_option (line, "--preset"); opt_inq_passwd = has_option (line, "--inq-passwd"); passwd_nonce = option_value (line, "--passwd-nonce"); if (passwd_nonce) { for (pend = passwd_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; passwd_nonce = xtrystrdup (passwd_nonce); *pend = c; if (!passwd_nonce) { rc = gpg_error_from_syserror (); goto leave; } } if ((s=has_option_name (line, "--timestamp"))) { if (*s != '=') { rc = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); goto leave; } opt_timestamp = isotime2epoch (s+1); if (opt_timestamp < 1) { rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value"); goto leave; } } else opt_timestamp = 0; line = skip_options (line); for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; *p = '\0'; if (*line) cache_nonce = xtrystrdup (line); eventcounter.maybe_key_change++; /* First inquire the parameters */ rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_KEYPARAM); if (rc) goto leave; rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM); if (rc) goto leave; init_membuf (&outbuf, 512); /* If requested, ask for the password to be used for the key. If this is not used the regular Pinentry mechanism is used. */ if (opt_inq_passwd && !no_protection) { /* (N is used as a dummy) */ assuan_begin_confidential (ctx); rc = assuan_inquire (ctx, "NEWPASSWD", &newpasswd, &n, 256); assuan_end_confidential (ctx); if (rc) goto leave; if (!*newpasswd) { /* Empty password given - switch to no-protection mode. */ xfree (newpasswd); newpasswd = NULL; no_protection = 1; } } else if (passwd_nonce) newpasswd = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE); rc = agent_genkey (ctrl, cache_nonce, opt_timestamp, (char*)value, valuelen, no_protection, newpasswd, opt_preset, &outbuf); leave: if (newpasswd) { /* Assuan_inquire does not allow us to read into secure memory thus we need to wipe it ourself. */ wipememory (newpasswd, strlen (newpasswd)); xfree (newpasswd); } xfree (value); if (rc) clear_outbuf (&outbuf); else rc = write_and_clear_outbuf (ctx, &outbuf); xfree (cache_nonce); xfree (passwd_nonce); return leave_cmd (ctx, rc); } static const char hlp_readkey[] = "READKEY [--no-data] \n" " --card \n" "\n" "Return the public key for the given keygrip or keyid.\n" "With --card, private key file with card information will be created."; static gpg_error_t cmd_readkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char grip[20]; gcry_sexp_t s_pkey = NULL; unsigned char *pkbuf = NULL; char *serialno = NULL; char *keyidbuf = NULL; size_t pkbuflen; int opt_card, opt_no_data; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); opt_no_data = has_option (line, "--no-data"); opt_card = has_option (line, "--card"); line = skip_options (line); if (opt_card) { const char *keyid = line; rc = agent_card_getattr (ctrl, "SERIALNO", &serialno, NULL); if (rc) { log_error (_("error getting serial number of card: %s\n"), gpg_strerror (rc)); goto leave; } /* Hack to create the shadow key for the OpenPGP standard keys. */ if ((!strcmp (keyid, "$SIGNKEYID") || !strcmp (keyid, "$ENCRKEYID")) && !agent_card_getattr (ctrl, keyid, &keyidbuf, NULL)) keyid = keyidbuf; rc = agent_card_readkey (ctrl, keyid, &pkbuf, NULL); if (rc) goto leave; pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)pkbuf, pkbuflen); if (rc) goto leave; if (!gcry_pk_get_keygrip (s_pkey, grip)) { rc = gcry_pk_testkey (s_pkey); if (rc == 0) rc = gpg_error (GPG_ERR_INTERNAL); goto leave; } if (agent_key_available (grip)) { /* (Shadow)-key is not available in our key storage. */ rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0); if (rc) goto leave; } rc = opt_no_data? 0 : assuan_send_data (ctx, pkbuf, pkbuflen); } else { rc = parse_keygrip (ctx, line, grip); if (rc) goto leave; rc = agent_public_key_from_file (ctrl, grip, &s_pkey); if (!rc) { pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); log_assert (pkbuflen); pkbuf = xtrymalloc (pkbuflen); if (!pkbuf) rc = gpg_error_from_syserror (); else { pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, pkbuf, pkbuflen); rc = opt_no_data? 0 : assuan_send_data (ctx, pkbuf, pkbuflen); } } } leave: xfree (keyidbuf); xfree (serialno); xfree (pkbuf); gcry_sexp_release (s_pkey); return leave_cmd (ctx, rc); } static const char hlp_keyinfo[] = "KEYINFO [--[ssh-]list] [--data] [--ssh-fpr[=algo]] [--with-ssh] \n" "\n" "Return information about the key specified by the KEYGRIP. If the\n" "key is not available GPG_ERR_NOT_FOUND is returned. If the option\n" "--list is given the keygrip is ignored and information about all\n" "available keys are returned. If --ssh-list is given information\n" "about all keys listed in the sshcontrol are returned. With --with-ssh\n" "information from sshcontrol is always added to the info. Unless --data\n" "is given, the information is returned as a status line using the format:\n" "\n" " KEYINFO \n" "\n" "KEYGRIP is the keygrip.\n" "\n" "TYPE is describes the type of the key:\n" " 'D' - Regular key stored on disk,\n" " 'T' - Key is stored on a smartcard (token),\n" " 'X' - Unknown type,\n" " '-' - Key is missing.\n" "\n" "SERIALNO is an ASCII string with the serial number of the\n" " smartcard. If the serial number is not known a single\n" " dash '-' is used instead.\n" "\n" "IDSTR is the IDSTR used to distinguish keys on a smartcard. If it\n" " is not known a dash is used instead.\n" "\n" "CACHED is 1 if the passphrase for the key was found in the key cache.\n" " If not, a '-' is used instead.\n" "\n" "PROTECTION describes the key protection type:\n" " 'P' - The key is protected with a passphrase,\n" " 'C' - The key is not protected,\n" " '-' - Unknown protection.\n" "\n" "FPR returns the formatted ssh-style fingerprint of the key. It is only\n" " printed if the option --ssh-fpr has been used. If ALGO is not given\n" " to that option the default ssh fingerprint algo is used. Without the\n" " option a '-' is printed.\n" "\n" "TTL is the TTL in seconds for that key or '-' if n/a.\n" "\n" "FLAGS is a word consisting of one-letter flags:\n" " 'D' - The key has been disabled,\n" " 'S' - The key is listed in sshcontrol (requires --with-ssh),\n" " 'c' - Use of the key needs to be confirmed,\n" " 'A' - The key is available on card,\n" " '-' - No flags given.\n" "\n" "More information may be added in the future."; static gpg_error_t do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, int data, int with_ssh_fpr, int in_ssh, int ttl, int disabled, int confirm, int on_card) { gpg_error_t err; char hexgrip[40+1]; char *fpr = NULL; int keytype; unsigned char *shadow_info = NULL; unsigned char *shadow_info_type = NULL; char *serialno = NULL; char *idstr = NULL; const char *keytypestr; const char *cached; const char *protectionstr; char *pw; int missing_key = 0; char ttlbuf[20]; char flagsbuf[5]; err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info, &shadow_info_type); if (err) { if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND) missing_key = 1; else goto leave; } /* Reformat the grip so that we use uppercase as good style. */ bin2hex (grip, 20, hexgrip); if (ttl > 0) snprintf (ttlbuf, sizeof ttlbuf, "%d", ttl); else strcpy (ttlbuf, "-"); *flagsbuf = 0; if (disabled) strcat (flagsbuf, "D"); if (in_ssh) strcat (flagsbuf, "S"); if (confirm) strcat (flagsbuf, "c"); if (on_card) strcat (flagsbuf, "A"); if (!*flagsbuf) strcpy (flagsbuf, "-"); if (missing_key) { protectionstr = "-"; keytypestr = "-"; } else { switch (keytype) { case PRIVATE_KEY_CLEAR: case PRIVATE_KEY_OPENPGP_NONE: protectionstr = "C"; keytypestr = "D"; break; case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D"; break; case PRIVATE_KEY_SHADOWED: protectionstr = "-"; keytypestr = "T"; break; default: protectionstr = "-"; keytypestr = "X"; break; } } /* Compute the ssh fingerprint if requested. */ if (with_ssh_fpr) { gcry_sexp_t key; if (!agent_raw_key_from_file (ctrl, grip, &key)) { ssh_get_fingerprint_string (key, with_ssh_fpr, &fpr); gcry_sexp_release (key); } } /* Here we have a little race by doing the cache check separately from the retrieval function. Given that the cache flag is only a hint, it should not really matter. */ pw = agent_get_cache (ctrl, hexgrip, CACHE_MODE_NORMAL); cached = pw ? "1" : "-"; xfree (pw); if (shadow_info) { if (strcmp (shadow_info_type, "t1-v1") == 0) { err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL); if (err) goto leave; } else if (strcmp (shadow_info_type, "tpm2-v1") == 0) { serialno = xstrdup("TPM-Protected"); idstr = NULL; } else { log_error ("unrecognised shadow key type %s\n", shadow_info_type); err = GPG_ERR_BAD_KEY; goto leave; } } if (!data) err = agent_write_status (ctrl, "KEYINFO", hexgrip, keytypestr, serialno? serialno : "-", idstr? idstr : "-", cached, protectionstr, fpr? fpr : "-", ttlbuf, flagsbuf, NULL); else { char *string; string = xtryasprintf ("%s %s %s %s %s %s %s %s %s\n", hexgrip, keytypestr, serialno? serialno : "-", idstr? idstr : "-", cached, protectionstr, fpr? fpr : "-", ttlbuf, flagsbuf); if (!string) err = gpg_error_from_syserror (); else err = assuan_send_data (ctx, string, strlen(string)); xfree (string); } leave: xfree (fpr); xfree (shadow_info_type); xfree (shadow_info); xfree (serialno); xfree (idstr); return err; } /* Entry into the command KEYINFO. This function handles the * command option processing. For details see hlp_keyinfo above. */ static gpg_error_t cmd_keyinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int err; unsigned char grip[20]; gnupg_dir_t dir = NULL; int list_mode; int opt_data, opt_ssh_fpr, opt_with_ssh; ssh_control_file_t cf = NULL; char hexgrip[41]; int disabled, ttl, confirm, is_ssh; struct card_key_info_s *keyinfo_on_cards; struct card_key_info_s *l; int on_card; if (has_option (line, "--ssh-list")) list_mode = 2; else list_mode = has_option (line, "--list"); opt_data = has_option (line, "--data"); if (has_option_name (line, "--ssh-fpr")) { if (has_option (line, "--ssh-fpr=md5")) opt_ssh_fpr = GCRY_MD_MD5; else if (has_option (line, "--ssh-fpr=sha1")) opt_ssh_fpr = GCRY_MD_SHA1; else if (has_option (line, "--ssh-fpr=sha256")) opt_ssh_fpr = GCRY_MD_SHA256; else opt_ssh_fpr = opt.ssh_fingerprint_digest; } else opt_ssh_fpr = 0; opt_with_ssh = has_option (line, "--with-ssh"); line = skip_options (line); if (opt_with_ssh || list_mode == 2) cf = ssh_open_control_file (); keyinfo_on_cards = get_keyinfo_on_cards (ctrl); if (list_mode == 2) { if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (cf) { while (!ssh_read_control_file (cf, hexgrip, &disabled, &ttl, &confirm)) { if (hex2bin (hexgrip, grip, 20) < 0 ) continue; /* Bad hex string. */ on_card = 0; for (l = keyinfo_on_cards; l; l = l->next) if (!memcmp (l->keygrip, hexgrip, 40)) on_card = 1; err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1, ttl, disabled, confirm, on_card); if (err) goto leave; } } err = 0; } else if (list_mode) { char *dirname; gnupg_dirent_t dir_entry; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); dirname = make_filename_try (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, NULL); if (!dirname) { err = gpg_error_from_syserror (); goto leave; } dir = gnupg_opendir (dirname); if (!dir) { err = gpg_error_from_syserror (); xfree (dirname); goto leave; } xfree (dirname); while ( (dir_entry = gnupg_readdir (dir)) ) { if (strlen (dir_entry->d_name) != 44 || strcmp (dir_entry->d_name + 40, ".key")) continue; strncpy (hexgrip, dir_entry->d_name, 40); hexgrip[40] = 0; if ( hex2bin (hexgrip, grip, 20) < 0 ) continue; /* Bad hex string. */ disabled = ttl = confirm = is_ssh = 0; if (opt_with_ssh) { err = ssh_search_control_file (cf, hexgrip, &disabled, &ttl, &confirm); if (!err) is_ssh = 1; else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) goto leave; } on_card = 0; for (l = keyinfo_on_cards; l; l = l->next) if (!memcmp (l->keygrip, hexgrip, 40)) on_card = 1; err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh, ttl, disabled, confirm, on_card); if (err) goto leave; } err = 0; } else { err = parse_keygrip (ctx, line, grip); if (err) goto leave; disabled = ttl = confirm = is_ssh = 0; if (opt_with_ssh) { err = ssh_search_control_file (cf, line, &disabled, &ttl, &confirm); if (!err) is_ssh = 1; else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) goto leave; } on_card = 0; for (l = keyinfo_on_cards; l; l = l->next) if (!memcmp (l->keygrip, line, 40)) on_card = 1; err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh, ttl, disabled, confirm, on_card); } leave: ssh_close_control_file (cf); gnupg_closedir (dir); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) leave_cmd (ctx, err); return err; } /* Helper for cmd_get_passphrase. */ static int send_back_passphrase (assuan_context_t ctx, int via_data, const char *pw) { size_t n; int rc; assuan_begin_confidential (ctx); n = strlen (pw); if (via_data) rc = assuan_send_data (ctx, pw, n); else { char *p = xtrymalloc_secure (n*2+1); if (!p) rc = gpg_error_from_syserror (); else { bin2hex (pw, n, p); rc = assuan_set_okay_line (ctx, p); xfree (p); } } return rc; } /* Callback function to compare the first entered PIN with the one currently being entered. */ static gpg_error_t reenter_passphrase_cmp_cb (struct pin_entry_info_s *pi) { const char *pin1 = pi->check_cb_arg; if (!strcmp (pin1, pi->pin)) return 0; /* okay */ return gpg_error (GPG_ERR_BAD_PASSPHRASE); } static const char hlp_get_passphrase[] = "GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]]\n" " [--qualitybar] [--newsymkey] \n" " [ ]\n" "\n" "This function is usually used to ask for a passphrase to be used\n" "for conventional encryption, but may also be used by programs which\n" "need specal handling of passphrases. This command uses a syntax\n" "which helps clients to use the agent with minimum effort. The\n" "agent either returns with an error or with a OK followed by the hex\n" "encoded passphrase. Note that the length of the strings is\n" "implicitly limited by the maximum length of a command.\n" "\n" "If the option \"--data\" is used the passphrase is returned by usual\n" "data lines and not on the okay line.\n" "\n" "If the option \"--check\" is used the passphrase constraints checks as\n" "implemented by gpg-agent are applied. A check is not done if the\n" "passphrase has been found in the cache.\n" "\n" "If the option \"--no-ask\" is used and the passphrase is not in the\n" "cache the user will not be asked to enter a passphrase but the error\n" "code GPG_ERR_NO_DATA is returned. \n" "\n" "If the option\"--newsymkey\" is used the agent asks for a new passphrase\n" "to be used in symmetric-only encryption. This must not be empty.\n" "\n" "If the option \"--qualitybar\" is used a visual indication of the\n" "entered passphrase quality is shown. (Unless no minimum passphrase\n" "length has been configured.)"; static gpg_error_t cmd_get_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *pw; char *response = NULL; char *response2 = NULL; char *cacheid = NULL; /* May point into LINE. */ char *desc = NULL; /* Ditto */ char *prompt = NULL; /* Ditto */ char *errtext = NULL; /* Ditto */ const char *desc2 = _("Please re-enter this passphrase"); char *p; int opt_data, opt_check, opt_no_ask, opt_qualbar, opt_newsymkey; int opt_repeat = 0; char *entry_errtext = NULL; struct pin_entry_info_s *pi = NULL; struct pin_entry_info_s *pi2 = NULL; + int is_generated; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); opt_data = has_option (line, "--data"); opt_check = has_option (line, "--check"); opt_no_ask = has_option (line, "--no-ask"); if (has_option_name (line, "--repeat")) { p = option_value (line, "--repeat"); if (p) opt_repeat = atoi (p); else opt_repeat = 1; } opt_qualbar = has_option (line, "--qualitybar"); opt_newsymkey = has_option (line, "--newsymkey"); line = skip_options (line); cacheid = line; p = strchr (cacheid, ' '); if (p) { *p++ = 0; while (*p == ' ') p++; errtext = p; p = strchr (errtext, ' '); if (p) { *p++ = 0; while (*p == ' ') p++; prompt = p; p = strchr (prompt, ' '); if (p) { *p++ = 0; while (*p == ' ') p++; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* Ignore trailing garbage. */ } } } if (!*cacheid || strlen (cacheid) > 50) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID"); if (!desc) return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); if (!strcmp (cacheid, "X")) cacheid = NULL; if (!strcmp (errtext, "X")) errtext = NULL; if (!strcmp (prompt, "X")) prompt = NULL; if (!strcmp (desc, "X")) desc = NULL; pw = cacheid ? agent_get_cache (ctrl, cacheid, CACHE_MODE_USER) : NULL; if (pw) { rc = send_back_passphrase (ctx, opt_data, pw); xfree (pw); goto leave; } else if (opt_no_ask) { rc = gpg_error (GPG_ERR_NO_DATA); goto leave; } /* Note, that we only need to replace the + characters and should * leave the other escaping in place because the escaped string is * send verbatim to the pinentry which does the unescaping (but not * the + replacing) */ if (errtext) plus_to_blank (errtext); if (prompt) plus_to_blank (prompt); if (desc) plus_to_blank (desc); /* If opt_repeat is 2 or higher we can't use our pin_entry_info_s * based method but fallback to the old simple method. It is * anyway questionable whether this extra repeat count makes any * real sense. */ if (opt_newsymkey && opt_repeat < 2) { /* We do not want to break any existing usage of this command * and thus we introduced the option --newsymkey to make this * command more useful to query the passphrase for symmetric * encryption. */ pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); if (!pi) { rc = gpg_error_from_syserror (); goto leave; } pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1); if (!pi2) { rc = gpg_error_from_syserror (); goto leave; } pi->max_length = MAX_PASSPHRASE_LEN + 1; pi->max_tries = 3; pi->with_qualitybar = opt_qualbar; pi->with_repeat = opt_repeat; pi->constraints_flags = (CHECK_CONSTRAINTS_NOT_EMPTY | CHECK_CONSTRAINTS_NEW_SYMKEY); pi2->max_length = MAX_PASSPHRASE_LEN + 1; pi2->max_tries = 3; pi2->check_cb = reenter_passphrase_cmp_cb; pi2->check_cb_arg = pi->pin; for (;;) /* (degenerated for-loop) */ { xfree (response); response = NULL; rc = agent_get_passphrase (ctrl, &response, desc, prompt, entry_errtext? entry_errtext:errtext, opt_qualbar, cacheid, CACHE_MODE_USER, pi); if (rc) goto leave; xfree (entry_errtext); entry_errtext = NULL; + is_generated = !!(pi->status & PINENTRY_STATUS_PASSWORD_GENERATED); + /* We don't allow an empty passpharse in this mode. */ - if (check_passphrase_constraints (ctrl, pi->pin, - pi->constraints_flags, - &entry_errtext)) + if (!is_generated + && check_passphrase_constraints (ctrl, pi->pin, + pi->constraints_flags, + &entry_errtext)) { pi->failed_tries = 0; pi2->failed_tries = 0; continue; } if (*pi->pin && !pi->repeat_okay && ctrl->pinentry_mode != PINENTRY_MODE_LOOPBACK && opt_repeat) { /* The passphrase is empty and the pinentry did not * already run the repetition check, do it here. This * is only called when using an old and simple pinentry. * It is neither called in loopback mode because the * caller does any passphrase repetition by herself nor if * no repetition was requested. */ xfree (response); response = NULL; rc = agent_get_passphrase (ctrl, &response, L_("Please re-enter this passphrase"), prompt, entry_errtext? entry_errtext:errtext, opt_qualbar, cacheid, CACHE_MODE_USER, pi2); if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) { /* The re-entered passphrase one did not match and * the user did not hit cancel. */ entry_errtext = xtrystrdup (L_("does not match - try again")); if (!entry_errtext) { rc = gpg_error_from_syserror (); goto leave; } continue; } } break; } if (!rc && *pi->pin) { /* Return the passphrase. */ if (cacheid) agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, pi->pin, 0); rc = send_back_passphrase (ctx, opt_data, pi->pin); } } else { next_try: xfree (response); response = NULL; rc = agent_get_passphrase (ctrl, &response, desc, prompt, entry_errtext? entry_errtext:errtext, opt_qualbar, cacheid, CACHE_MODE_USER, NULL); xfree (entry_errtext); entry_errtext = NULL; + is_generated = !!(pi->status & PINENTRY_STATUS_PASSWORD_GENERATED); + if (!rc) { int i; if (opt_check + && !is_generated && check_passphrase_constraints (ctrl, response, (opt_newsymkey? CHECK_CONSTRAINTS_NEW_SYMKEY:0), &entry_errtext)) { goto next_try; } for (i = 0; i < opt_repeat; i++) { if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) break; xfree (response2); response2 = NULL; rc = agent_get_passphrase (ctrl, &response2, desc2, prompt, errtext, 0, cacheid, CACHE_MODE_USER, NULL); if (rc) break; if (strcmp (response2, response)) { entry_errtext = try_percent_escape (_("does not match - try again"), NULL); if (!entry_errtext) { rc = gpg_error_from_syserror (); break; } goto next_try; } } if (!rc) { if (cacheid) agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, response, 0); rc = send_back_passphrase (ctx, opt_data, response); } } } leave: xfree (response); xfree (response2); xfree (entry_errtext); xfree (pi2); xfree (pi); return leave_cmd (ctx, rc); } static const char hlp_clear_passphrase[] = "CLEAR_PASSPHRASE [--mode=normal] \n" "\n" "may be used to invalidate the cache entry for a passphrase. The\n" "function returns with OK even when there is no cached passphrase.\n" "The --mode=normal option is used to clear an entry for a cacheid\n" "added by the agent. The --mode=ssh option is used for a cacheid\n" "added for ssh.\n"; static gpg_error_t cmd_clear_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *cacheid = NULL; char *p; cache_mode_t cache_mode = CACHE_MODE_USER; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (has_option (line, "--mode=normal")) cache_mode = CACHE_MODE_NORMAL; else if (has_option (line, "--mode=ssh")) cache_mode = CACHE_MODE_SSH; line = skip_options (line); /* parse the stuff */ for (p=line; *p == ' '; p++) ; cacheid = p; p = strchr (cacheid, ' '); if (p) *p = 0; /* ignore garbage */ if (!*cacheid || strlen (cacheid) > 50) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID"); agent_put_cache (ctrl, cacheid, cache_mode, NULL, 0); agent_clear_passphrase (ctrl, cacheid, cache_mode); return 0; } static const char hlp_get_confirmation[] = "GET_CONFIRMATION \n" "\n" "This command may be used to ask for a simple confirmation.\n" "DESCRIPTION is displayed along with a Okay and Cancel button. This\n" "command uses a syntax which helps clients to use the agent with\n" "minimum effort. The agent either returns with an error or with a\n" "OK. Note, that the length of DESCRIPTION is implicitly limited by\n" "the maximum length of a command. DESCRIPTION should not contain\n" "any spaces, those must be encoded either percent escaped or simply\n" "as '+'."; static gpg_error_t cmd_get_confirmation (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *desc = NULL; char *p; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); /* parse the stuff */ for (p=line; *p == ' '; p++) ; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* We ignore any garbage -may be later used for other args. */ if (!*desc) return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); if (!strcmp (desc, "X")) desc = NULL; /* Note, that we only need to replace the + characters and should leave the other escaping in place because the escaped string is send verbatim to the pinentry which does the unescaping (but not the + replacing) */ if (desc) plus_to_blank (desc); rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0); return leave_cmd (ctx, rc); } static const char hlp_learn[] = "LEARN [--send] [--sendinfo] [--force]\n" "\n" "Learn something about the currently inserted smartcard. With\n" "--sendinfo information about the card is returned; with --send\n" "the available certificates are returned as D lines; with --force\n" "private key storage will be updated by the result."; static gpg_error_t cmd_learn (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int send, sendinfo, force; send = has_option (line, "--send"); sendinfo = send? 1 : has_option (line, "--sendinfo"); force = has_option (line, "--force"); if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force); return leave_cmd (ctx, err); } static const char hlp_passwd[] = "PASSWD [--cache-nonce=] [--passwd-nonce=] [--preset]\n" " [--verify] \n" "\n" "Change the passphrase/PIN for the key identified by keygrip in LINE. If\n" "--preset is used then the new passphrase will be added to the cache.\n" "If --verify is used the command asks for the passphrase and verifies\n" "that the passphrase valid.\n"; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int c; char *cache_nonce = NULL; char *passwd_nonce = NULL; unsigned char grip[20]; gcry_sexp_t s_skey = NULL; unsigned char *shadow_info = NULL; char *passphrase = NULL; char *pend; int opt_preset, opt_verify; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); opt_preset = has_option (line, "--preset"); cache_nonce = option_value (line, "--cache-nonce"); opt_verify = has_option (line, "--verify"); if (cache_nonce) { for (pend = cache_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; cache_nonce = xtrystrdup (cache_nonce); *pend = c; if (!cache_nonce) { err = gpg_error_from_syserror (); goto leave; } } passwd_nonce = option_value (line, "--passwd-nonce"); if (passwd_nonce) { for (pend = passwd_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; passwd_nonce = xtrystrdup (passwd_nonce); *pend = c; if (!passwd_nonce) { err = gpg_error_from_syserror (); goto leave; } } line = skip_options (line); err = parse_keygrip (ctx, line, grip); if (err) goto leave; ctrl->in_passwd++; err = agent_key_from_file (ctrl, opt_verify? NULL : cache_nonce, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, &passphrase); if (err) ; else if (shadow_info) { log_error ("changing a smartcard PIN is not yet supported\n"); err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); } else if (opt_verify) { /* All done. */ if (passphrase) { if (!passwd_nonce) { char buf[12]; gcry_create_nonce (buf, 12); passwd_nonce = bin2hex (buf, 12, NULL); } if (passwd_nonce && !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce); xfree (ctrl->server_local->last_passwd_nonce); ctrl->server_local->last_passwd_nonce = passwd_nonce; passwd_nonce = NULL; } } } else { char *newpass = NULL; if (passwd_nonce) newpass = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE); err = agent_protect_and_store (ctrl, s_skey, &newpass); if (!err && passphrase) { /* A passphrase existed on the old key and the change was successful. Return a nonce for that old passphrase to let the caller try to unprotect the other subkeys with the same key. */ if (!cache_nonce) { char buf[12]; gcry_create_nonce (buf, 12); cache_nonce = bin2hex (buf, 12, NULL); } if (cache_nonce && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); xfree (ctrl->server_local->last_cache_nonce); ctrl->server_local->last_cache_nonce = cache_nonce; cache_nonce = NULL; } if (newpass) { /* If we have a new passphrase (which might be empty) we store it under a passwd nonce so that the caller may send that nonce again to use it for another key. */ if (!passwd_nonce) { char buf[12]; gcry_create_nonce (buf, 12); passwd_nonce = bin2hex (buf, 12, NULL); } if (passwd_nonce && !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE, newpass, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce); xfree (ctrl->server_local->last_passwd_nonce); ctrl->server_local->last_passwd_nonce = passwd_nonce; passwd_nonce = NULL; } } } if (!err && opt_preset) { char hexgrip[40+1]; bin2hex(grip, 20, hexgrip); err = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, newpass, ctrl->cache_ttl_opt_preset); } xfree (newpass); } ctrl->in_passwd--; xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; leave: xfree (passphrase); gcry_sexp_release (s_skey); xfree (shadow_info); xfree (cache_nonce); xfree (passwd_nonce); return leave_cmd (ctx, err); } static const char hlp_preset_passphrase[] = "PRESET_PASSPHRASE [--inquire] []\n" "\n" "Set the cached passphrase/PIN for the key identified by the keygrip\n" "to passwd for the given time, where -1 means infinite and 0 means\n" "the default (currently only a timeout of -1 is allowed, which means\n" "to never expire it). If passwd is not provided, ask for it via the\n" "pinentry module unless --inquire is passed in which case the passphrase\n" "is retrieved from the client via a server inquire.\n"; static gpg_error_t cmd_preset_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *grip_clear = NULL; unsigned char *passphrase = NULL; int ttl; size_t len; int opt_inquire; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (!opt.allow_preset_passphrase) return set_error (GPG_ERR_NOT_SUPPORTED, "no --allow-preset-passphrase"); opt_inquire = has_option (line, "--inquire"); line = skip_options (line); grip_clear = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) return gpg_error (GPG_ERR_MISSING_VALUE); *line = '\0'; line++; while (*line && (*line == ' ' || *line == '\t')) line++; /* Currently, only infinite timeouts are allowed. */ ttl = -1; if (line[0] != '-' || line[1] != '1') return gpg_error (GPG_ERR_NOT_IMPLEMENTED); line++; line++; while (!(*line != ' ' && *line != '\t')) line++; /* Syntax check the hexstring. */ len = 0; rc = parse_hexstring (ctx, line, &len); if (rc) return rc; line[len] = '\0'; /* If there is a passphrase, use it. Currently, a passphrase is required. */ if (*line) { if (opt_inquire) { rc = set_error (GPG_ERR_ASS_PARAMETER, "both --inquire and passphrase specified"); goto leave; } /* Do in-place conversion. */ passphrase = line; if (!hex2str (passphrase, passphrase, strlen (passphrase)+1, NULL)) rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring"); } else if (opt_inquire) { /* Note that the passphrase will be truncated at any null byte and the * limit is 480 characters. */ size_t maxlen = 480; rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen); if (!rc) rc = assuan_inquire (ctx, "PASSPHRASE", &passphrase, &len, maxlen); } else rc = set_error (GPG_ERR_NOT_IMPLEMENTED, "passphrase is required"); if (!rc) { rc = agent_put_cache (ctrl, grip_clear, CACHE_MODE_ANY, passphrase, ttl); if (opt_inquire) xfree (passphrase); } leave: return leave_cmd (ctx, rc); } static const char hlp_scd[] = "SCD \n" " \n" "This is a general quote command to redirect everything to the\n" "SCdaemon."; static gpg_error_t cmd_scd (assuan_context_t ctx, char *line) { int rc; #ifdef BUILD_WITH_SCDAEMON ctrl_t ctrl = assuan_get_pointer (ctx); if (ctrl->restricted) { const char *argv[5]; int argc; char *l; l = xtrystrdup (line); if (!l) return gpg_error_from_syserror (); argc = split_fields (l, argv, DIM (argv)); /* These commands are allowed. */ if ((argc == 1 && !strcmp (argv[0], "SERIALNO")) || (argc == 2 && !strcmp (argv[0], "GETINFO") && !strcmp (argv[1], "version")) || (argc == 2 && !strcmp (argv[0], "GETATTR") && !strcmp (argv[1], "KEY-FPR")) || (argc == 2 && !strcmp (argv[0], "KEYINFO") && !strcmp (argv[1], "--list=encr"))) xfree (l); else { xfree (l); return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); } } /* All SCD prefixed commands may change a key. */ eventcounter.maybe_key_change++; rc = divert_generic_cmd (ctrl, line, ctx); #else (void)ctx; (void)line; rc = gpg_error (GPG_ERR_NOT_SUPPORTED); #endif return rc; } static const char hlp_keywrap_key[] = "KEYWRAP_KEY [--clear] \n" "\n" "Return a key to wrap another key. For now the key is returned\n" "verbatim and thus makes not much sense because an eavesdropper on\n" "the gpg-agent connection will see the key as well as the wrapped key.\n" "However, this function may either be equipped with a public key\n" "mechanism or not used at all if the key is a pre-shared key. In any\n" "case wrapping the import and export of keys is a requirement for\n" "certain cryptographic validations and thus useful. The key persists\n" "until a RESET command but may be cleared using the option --clear.\n" "\n" "Supported modes are:\n" " --import - Return a key to import a key into gpg-agent\n" " --export - Return a key to export a key from gpg-agent"; static gpg_error_t cmd_keywrap_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int clearopt = has_option (line, "--clear"); if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); assuan_begin_confidential (ctx); if (has_option (line, "--import")) { xfree (ctrl->server_local->import_key); if (clearopt) ctrl->server_local->import_key = NULL; else if (!(ctrl->server_local->import_key = gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM))) err = gpg_error_from_syserror (); else err = assuan_send_data (ctx, ctrl->server_local->import_key, KEYWRAP_KEYSIZE); } else if (has_option (line, "--export")) { xfree (ctrl->server_local->export_key); if (clearopt) ctrl->server_local->export_key = NULL; else if (!(ctrl->server_local->export_key = gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM))) err = gpg_error_from_syserror (); else err = assuan_send_data (ctx, ctrl->server_local->export_key, KEYWRAP_KEYSIZE); } else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for MODE"); assuan_end_confidential (ctx); return leave_cmd (ctx, err); } static const char hlp_import_key[] = "IMPORT_KEY [--unattended] [--force] [--timestamp=]\n" " []\n" "\n" "Import a secret key into the key store. The key is expected to be\n" "encrypted using the current session's key wrapping key (cf. command\n" "KEYWRAP_KEY) using the AESWRAP-128 algorithm. This function takes\n" "no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n" "key data. The unwrapped key must be a canonical S-expression. The\n" "option --unattended tries to import the key as-is without any\n" "re-encryption. An existing key can be overwritten with --force.\n" "If --timestamp is given its value is recorded as the key's creation\n" "time; the value is expected in ISO format (e.g. \"20030316T120000\")."; static gpg_error_t cmd_import_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int opt_unattended; time_t opt_timestamp; int force; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; gcry_cipher_hd_t cipherhd = NULL; unsigned char *key = NULL; size_t keylen, realkeylen; char *passphrase = NULL; unsigned char *finalkey = NULL; size_t finalkeylen; unsigned char grip[20]; gcry_sexp_t openpgp_sexp = NULL; char *cache_nonce = NULL; char *p; const char *s; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (!ctrl->server_local->import_key) { err = gpg_error (GPG_ERR_MISSING_KEY); goto leave; } opt_unattended = has_option (line, "--unattended"); force = has_option (line, "--force"); if ((s=has_option_name (line, "--timestamp"))) { if (*s != '=') { err = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); goto leave; } opt_timestamp = isotime2epoch (s+1); if (opt_timestamp < 1) { err = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value"); goto leave; } } else opt_timestamp = 0; line = skip_options (line); for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; *p = '\0'; if (*line) cache_nonce = xtrystrdup (line); eventcounter.maybe_key_change++; assuan_begin_confidential (ctx); err = assuan_inquire (ctx, "KEYDATA", &wrappedkey, &wrappedkeylen, MAXLEN_KEYDATA); assuan_end_confidential (ctx); if (err) goto leave; if (wrappedkeylen < 24) { err = gpg_error (GPG_ERR_INV_LENGTH); goto leave; } keylen = wrappedkeylen - 8; key = xtrymalloc_secure (keylen); if (!key) { err = gpg_error_from_syserror (); goto leave; } err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, 0); if (err) goto leave; err = gcry_cipher_setkey (cipherhd, ctrl->server_local->import_key, KEYWRAP_KEYSIZE); if (err) goto leave; err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen); if (err) goto leave; gcry_cipher_close (cipherhd); cipherhd = NULL; xfree (wrappedkey); wrappedkey = NULL; realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err); if (!realkeylen) goto leave; /* Invalid canonical encoded S-expression. */ err = keygrip_from_canon_sexp (key, realkeylen, grip); if (err) { /* This might be due to an unsupported S-expression format. Check whether this is openpgp-private-key and trigger that import code. */ if (!gcry_sexp_sscan (&openpgp_sexp, NULL, key, realkeylen)) { const char *tag; size_t taglen; tag = gcry_sexp_nth_data (openpgp_sexp, 0, &taglen); if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19)) ; else { gcry_sexp_release (openpgp_sexp); openpgp_sexp = NULL; } } if (!openpgp_sexp) goto leave; /* Note that ERR is still set. */ } if (openpgp_sexp) { /* In most cases the key is encrypted and thus the conversion function from the OpenPGP format to our internal format will ask for a passphrase. That passphrase will be returned and used to protect the key using the same code as for regular key import. */ xfree (key); key = NULL; err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip, ctrl->server_local->keydesc, cache_nonce, &key, opt_unattended? NULL : &passphrase); if (err) goto leave; realkeylen = gcry_sexp_canon_len (key, 0, NULL, &err); if (!realkeylen) goto leave; /* Invalid canonical encoded S-expression. */ if (passphrase) { log_assert (!opt_unattended); if (!cache_nonce) { char buf[12]; gcry_create_nonce (buf, 12); cache_nonce = bin2hex (buf, 12, NULL); } if (cache_nonce && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); } } else if (opt_unattended) { err = set_error (GPG_ERR_ASS_PARAMETER, "\"--unattended\" may only be used with OpenPGP keys"); goto leave; } else { if (!force && !agent_key_available (grip)) err = gpg_error (GPG_ERR_EEXIST); else { char *prompt = xtryasprintf (_("Please enter the passphrase to protect the " "imported object within the %s system."), GNUPG_NAME); if (!prompt) err = gpg_error_from_syserror (); else err = agent_ask_new_passphrase (ctrl, prompt, &passphrase); xfree (prompt); } if (err) goto leave; } if (passphrase) { err = agent_protect (key, passphrase, &finalkey, &finalkeylen, ctrl->s2k_count, -1); if (!err) err = agent_write_private_key (grip, finalkey, finalkeylen, force, NULL, NULL, opt_timestamp); } else err = agent_write_private_key (grip, key, realkeylen, force, NULL, NULL, opt_timestamp); leave: gcry_sexp_release (openpgp_sexp); xfree (finalkey); xfree (passphrase); xfree (key); gcry_cipher_close (cipherhd); xfree (wrappedkey); xfree (cache_nonce); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, err); } static const char hlp_export_key[] = "EXPORT_KEY [--cache-nonce=] [--openpgp] \n" "\n" "Export a secret key from the key store. The key will be encrypted\n" "using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n" "using the AESWRAP-128 algorithm. The caller needs to retrieve that key\n" "prior to using this command. The function takes the keygrip as argument.\n" "\n" "If --openpgp is used, the secret key material will be exported in RFC 4880\n" "compatible passphrase-protected form. Without --openpgp, the secret key\n" "material will be exported in the clear (after prompting the user to unlock\n" "it, if needed).\n"; static gpg_error_t cmd_export_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char grip[20]; gcry_sexp_t s_skey = NULL; unsigned char *key = NULL; size_t keylen; gcry_cipher_hd_t cipherhd = NULL; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; int openpgp; char *cache_nonce; char *passphrase = NULL; unsigned char *shadow_info = NULL; char *pend; int c; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); openpgp = has_option (line, "--openpgp"); cache_nonce = option_value (line, "--cache-nonce"); if (cache_nonce) { for (pend = cache_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; cache_nonce = xtrystrdup (cache_nonce); *pend = c; if (!cache_nonce) { err = gpg_error_from_syserror (); goto leave; } } line = skip_options (line); if (!ctrl->server_local->export_key) { err = set_error (GPG_ERR_MISSING_KEY, "did you run KEYWRAP_KEY ?"); goto leave; } err = parse_keygrip (ctx, line, grip); if (err) goto leave; if (agent_key_available (grip)) { err = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } /* Get the key from the file. With the openpgp flag we also ask for the passphrase so that we can use it to re-encrypt it. */ err = agent_key_from_file (ctrl, cache_nonce, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, openpgp ? &passphrase : NULL); if (err) goto leave; if (shadow_info) { /* Key is on a smartcard. */ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); goto leave; } if (openpgp) { /* The openpgp option changes the key format into the OpenPGP key transfer format. The result is already a padded canonical S-expression. */ if (!passphrase) { err = agent_ask_new_passphrase (ctrl, _("This key (or subkey) is not protected with a passphrase." " Please enter a new passphrase to export it."), &passphrase); if (err) goto leave; } err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen); if (!err && passphrase) { if (!cache_nonce) { char buf[12]; gcry_create_nonce (buf, 12); cache_nonce = bin2hex (buf, 12, NULL); } if (cache_nonce && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); xfree (ctrl->server_local->last_cache_nonce); ctrl->server_local->last_cache_nonce = cache_nonce; cache_nonce = NULL; } } } else { /* Convert into a canonical S-expression and wrap that. */ err = make_canon_sexp_pad (s_skey, 1, &key, &keylen); } if (err) goto leave; gcry_sexp_release (s_skey); s_skey = NULL; err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, 0); if (err) goto leave; err = gcry_cipher_setkey (cipherhd, ctrl->server_local->export_key, KEYWRAP_KEYSIZE); if (err) goto leave; wrappedkeylen = keylen + 8; wrappedkey = xtrymalloc (wrappedkeylen); if (!wrappedkey) { err = gpg_error_from_syserror (); goto leave; } err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen); if (err) goto leave; xfree (key); key = NULL; gcry_cipher_close (cipherhd); cipherhd = NULL; assuan_begin_confidential (ctx); err = assuan_send_data (ctx, wrappedkey, wrappedkeylen); assuan_end_confidential (ctx); leave: xfree (cache_nonce); xfree (passphrase); xfree (wrappedkey); gcry_cipher_close (cipherhd); xfree (key); gcry_sexp_release (s_skey); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; xfree (shadow_info); return leave_cmd (ctx, err); } static const char hlp_delete_key[] = "DELETE_KEY [--force|--stub-only] \n" "\n" "Delete a secret key from the key store. If --force is used\n" "and a loopback pinentry is allowed, the agent will not ask\n" "the user for confirmation. If --stub-only is used the key will\n" "only be deleted if it is a reference to a token."; static gpg_error_t cmd_delete_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int force, stub_only; unsigned char grip[20]; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); force = has_option (line, "--force"); stub_only = has_option (line, "--stub-only"); line = skip_options (line); eventcounter.maybe_key_change++; /* If the use of a loopback pinentry has been disabled, we assume * that a silent deletion of keys shall also not be allowed. */ if (!opt.allow_loopback_pinentry) force = 0; err = parse_keygrip (ctx, line, grip); if (err) goto leave; err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip, force, stub_only); if (err) goto leave; leave: xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, err); } #if SIZEOF_TIME_T > SIZEOF_UNSIGNED_LONG #define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010llu))" #else #define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010lu))" #endif static const char hlp_keytocard[] = "KEYTOCARD [--force] []\n" "\n" "TIMESTAMP is required for OpenPGP and defaults to the Epoch. The\n" "SERIALNO is used for checking; use \"-\" to disable the check."; static gpg_error_t cmd_keytocard (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int force; gpg_error_t err = 0; const char *argv[5]; int argc; unsigned char grip[20]; const char *serialno, *timestamp_str, *keyref; gcry_sexp_t s_skey = NULL; unsigned char *keydata; size_t keydatalen; unsigned char *shadow_info = NULL; time_t timestamp; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); force = has_option (line, "--force"); line = skip_options (line); argc = split_fields (line, argv, DIM (argv)); if (argc < 3) { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } err = parse_keygrip (ctx, argv[0], grip); if (err) goto leave; if (agent_key_available (grip)) { err =gpg_error (GPG_ERR_NO_SECKEY); goto leave; } /* Note that checking of the s/n is currently not implemented but we * want to provide a clean interface if we ever implement it. */ serialno = argv[1]; if (!strcmp (serialno, "-")) serialno = NULL; keyref = argv[2]; /* FIXME: Default to the creation time as stored in the private * key. The parameter is here so that gpg can make sure that the * timestamp as used for key creation (and thus the openPGP * fingerprint) is used. */ timestamp_str = argc > 3? argv[3] : "19700101T000000"; if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1)) { err = gpg_error (GPG_ERR_INV_TIME); goto leave; } err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, NULL); if (err) goto leave; if (shadow_info) { /* Key is already on a smartcard - we can't extract it. */ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); goto leave; } /* Note: We can't use make_canon_sexp because we need to allocate a * few extra bytes for our hack below. */ keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); keydata = xtrymalloc_secure (keydatalen + 30); if (keydata == NULL) { err = gpg_error_from_syserror (); goto leave; } gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen); gcry_sexp_release (s_skey); s_skey = NULL; keydatalen--; /* Decrement for last '\0'. */ /* Hack to insert the timestamp "created-at" into the private key. */ snprintf (keydata+keydatalen-1, 30, KEYTOCARD_TIMESTAMP_FORMAT, timestamp); keydatalen += 10 + 19 - 1; err = divert_writekey (ctrl, force, serialno, keyref, keydata, keydatalen); xfree (keydata); leave: gcry_sexp_release (s_skey); xfree (shadow_info); return leave_cmd (ctx, err); } static const char hlp_get_secret[] = "GET_SECRET \n" "\n" "Return the secret value stored under KEY\n"; static gpg_error_t cmd_get_secret (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; char *p, *key; char *value = NULL; size_t valuelen; /* For now we allow this only for local connections. */ if (ctrl->restricted) { err = gpg_error (GPG_ERR_FORBIDDEN); goto leave; } line = skip_options (line); for (p=line; *p == ' '; p++) ; key = p; p = strchr (key, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) { err = set_error (GPG_ERR_ASS_PARAMETER, "too many arguments"); goto leave; } } if (!*key) { err = set_error (GPG_ERR_ASS_PARAMETER, "no key given"); goto leave; } value = agent_get_cache (ctrl, key, CACHE_MODE_DATA); if (!value) { err = gpg_error (GPG_ERR_NO_DATA); goto leave; } valuelen = percent_unescape_inplace (value, 0); err = assuan_send_data (ctx, value, valuelen); wipememory (value, valuelen); leave: xfree (value); return leave_cmd (ctx, err); } static const char hlp_put_secret[] = "PUT_SECRET [--clear] []\n" "\n" "This commands stores a secret under KEY in gpg-agent's in-memory\n" "cache. The TTL must be explicitly given by TTL and the options\n" "from the configuration file are not used. The value is either given\n" "percent-escaped as 3rd argument or if not given inquired by gpg-agent\n" "using the keyword \"SECRET\".\n" "The option --clear removes the secret from the cache." ""; static gpg_error_t cmd_put_secret (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int opt_clear; unsigned char *value = NULL; size_t valuelen = 0; size_t n; char *p, *key, *ttlstr; unsigned char *valstr; int ttl; char *string = NULL; /* For now we allow this only for local connections. */ if (ctrl->restricted) { err = gpg_error (GPG_ERR_FORBIDDEN); goto leave; } opt_clear = has_option (line, "--clear"); line = skip_options (line); for (p=line; *p == ' '; p++) ; key = p; ttlstr = NULL; valstr = NULL; p = strchr (key, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) { ttlstr = p; p = strchr (ttlstr, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) valstr = p; } } } if (!*key) { err = set_error (GPG_ERR_ASS_PARAMETER, "no key given"); goto leave; } if (!ttlstr || !*ttlstr || !(n = parse_ttl (ttlstr, &ttl))) { err = set_error (GPG_ERR_ASS_PARAMETER, "no or invalid TTL given"); goto leave; } if (valstr && opt_clear) { err = set_error (GPG_ERR_ASS_PARAMETER, "value not expected with --clear"); goto leave; } if (valstr) { valuelen = percent_unescape_inplace (valstr, 0); value = NULL; } else /* Inquire the value to store */ { err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u",MAXLEN_PUT_SECRET); if (!err) err = assuan_inquire (ctx, "SECRET", &value, &valuelen, MAXLEN_PUT_SECRET); if (err) goto leave; } /* Our cache expects strings and thus we need to turn the buffer * into a string. Instead of resorting to base64 encoding we use a * special percent escaping which only quoted the Nul and the * percent character. */ string = percent_data_escape (0, NULL, value? value : valstr, valuelen); if (!string) { err = gpg_error_from_syserror (); goto leave; } err = agent_put_cache (ctrl, key, CACHE_MODE_DATA, string, ttl); leave: if (string) { wipememory (string, strlen (string)); xfree (string); } if (value) { wipememory (value, valuelen); xfree (value); } return leave_cmd (ctx, err); } static const char hlp_keytotpm[] = "KEYTOTPM \n" "\n"; static gpg_error_t cmd_keytotpm (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; unsigned char grip[20]; gcry_sexp_t s_skey; unsigned char *shadow_info = NULL; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); err = parse_keygrip (ctx, line, grip); if (err) goto leave; if (agent_key_available (grip)) { err =gpg_error (GPG_ERR_NO_SECKEY); goto leave; } err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, NULL); if (err) { xfree (shadow_info); goto leave; } if (shadow_info) { /* Key is on a TPM or smartcard already. */ xfree (shadow_info); gcry_sexp_release (s_skey); err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); goto leave; } err = divert_tpm2_writekey (ctrl, grip, s_skey); gcry_sexp_release (s_skey); leave: return leave_cmd (ctx, err); } static const char hlp_getval[] = "GETVAL \n" "\n" "Return the value for KEY from the special environment as created by\n" "PUTVAL."; static gpg_error_t cmd_getval (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; char *key = NULL; char *p; struct putval_item_s *vl; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); for (p=line; *p == ' '; p++) ; key = p; p = strchr (key, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) return set_error (GPG_ERR_ASS_PARAMETER, "too many arguments"); } if (!*key) return set_error (GPG_ERR_ASS_PARAMETER, "no key given"); for (vl=putval_list; vl; vl = vl->next) if ( !strcmp (vl->d, key) ) break; if (vl) /* Got an entry. */ rc = assuan_send_data (ctx, vl->d+vl->off, vl->len); else return gpg_error (GPG_ERR_NO_DATA); return leave_cmd (ctx, rc); } static const char hlp_putval[] = "PUTVAL []\n" "\n" "The gpg-agent maintains a kind of environment which may be used to\n" "store key/value pairs in it, so that they can be retrieved later.\n" "This may be used by helper daemons to daemonize themself on\n" "invocation and register them with gpg-agent. Callers of the\n" "daemon's service may now first try connect to get the information\n" "for that service from gpg-agent through the GETVAL command and then\n" "try to connect to that daemon. Only if that fails they may start\n" "an own instance of the service daemon. \n" "\n" "KEY is an arbitrary symbol with the same syntax rules as keys\n" "for shell environment variables. PERCENT_ESCAPED_VALUE is the\n" "corresponding value; they should be similar to the values of\n" "envronment variables but gpg-agent does not enforce any\n" "restrictions. If that value is not given any value under that KEY\n" "is removed from this special environment."; static gpg_error_t cmd_putval (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; char *key = NULL; char *value = NULL; size_t valuelen = 0; char *p; struct putval_item_s *vl, *vlprev; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); for (p=line; *p == ' '; p++) ; key = p; p = strchr (key, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) { value = p; p = strchr (value, ' '); if (p) *p = 0; valuelen = percent_plus_unescape_inplace (value, 0); } } if (!*key) return set_error (GPG_ERR_ASS_PARAMETER, "no key given"); for (vl=putval_list,vlprev=NULL; vl; vlprev=vl, vl = vl->next) if ( !strcmp (vl->d, key) ) break; if (vl) /* Delete old entry. */ { if (vlprev) vlprev->next = vl->next; else putval_list = vl->next; xfree (vl); } if (valuelen) /* Add entry. */ { vl = xtrymalloc (sizeof *vl + strlen (key) + valuelen); if (!vl) rc = gpg_error_from_syserror (); else { vl->len = valuelen; vl->off = strlen (key) + 1; strcpy (vl->d, key); memcpy (vl->d + vl->off, value, valuelen); vl->next = putval_list; putval_list = vl; } } return leave_cmd (ctx, rc); } static const char hlp_updatestartuptty[] = "UPDATESTARTUPTTY\n" "\n" "Set startup TTY and X11 DISPLAY variables to the values of this\n" "session. This command is useful to pull future pinentries to\n" "another screen. It is only required because there is no way in the\n" "ssh-agent protocol to convey this information."; static gpg_error_t cmd_updatestartuptty (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; session_env_t se; char *lc_ctype = NULL; char *lc_messages = NULL; int iterator; const char *name; (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); se = session_env_new (); if (!se) err = gpg_error_from_syserror (); iterator = 0; while (!err && (name = session_env_list_stdenvnames (&iterator, NULL))) { const char *value = session_env_getenv (ctrl->session_env, name); if (value) err = session_env_setenv (se, name, value); } if (!err && ctrl->lc_ctype) if (!(lc_ctype = xtrystrdup (ctrl->lc_ctype))) err = gpg_error_from_syserror (); if (!err && ctrl->lc_messages) if (!(lc_messages = xtrystrdup (ctrl->lc_messages))) err = gpg_error_from_syserror (); if (err) { session_env_release (se); xfree (lc_ctype); xfree (lc_messages); } else { session_env_release (opt.startup_env); opt.startup_env = se; xfree (opt.startup_lc_ctype); opt.startup_lc_ctype = lc_ctype; xfree (opt.startup_lc_messages); opt.startup_lc_messages = lc_messages; } return err; } static const char hlp_killagent[] = "KILLAGENT\n" "\n" "Stop the agent."; static gpg_error_t cmd_killagent (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); ctrl->server_local->stopme = 1; assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return 0; } static const char hlp_reloadagent[] = "RELOADAGENT\n" "\n" "This command is an alternative to SIGHUP\n" "to reload the configuration."; static gpg_error_t cmd_reloadagent (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); agent_sighup_action (); return 0; } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multipurpose function to return a variety of information.\n" "Supported values for WHAT are:\n" "\n" " version - Return the version of the program.\n" " pid - Return the process id of the server.\n" " socket_name - Return the name of the socket.\n" " ssh_socket_name - Return the name of the ssh socket.\n" " scd_running - Return OK if the SCdaemon is already running.\n" " s2k_time - Return the time in milliseconds required for S2K.\n" " s2k_count - Return the standard S2K count.\n" " s2k_count_cal - Return the calibrated S2K count.\n" " std_env_names - List the names of the standard environment.\n" " std_session_env - List the standard session environment.\n" " std_startup_env - List the standard startup environment.\n" " getenv NAME - Return value of envvar NAME.\n" " connections - Return number of active connections.\n" " jent_active - Returns OK if Libgcrypt's JENT is active.\n" " restricted - Returns OK if the connection is in restricted mode.\n" " cmd_has_option CMD OPT\n" " - Returns OK if command CMD has option OPT.\n"; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; if (!strcmp (line, "version")) { const char *s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strncmp (line, "cmd_has_option", 14) && (line[14] == ' ' || line[14] == '\t' || !line[14])) { char *cmd, *cmdopt; line += 14; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmd = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { *line++ = 0; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmdopt = line; if (!command_has_option (cmd, cmdopt)) rc = gpg_error (GPG_ERR_FALSE); } } } } else if (!strcmp (line, "s2k_count")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "restricted")) { rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_FALSE); } else if (ctrl->restricted) { rc = gpg_error (GPG_ERR_FORBIDDEN); } /* All sub-commands below are not allowed in restricted mode. */ else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "socket_name")) { const char *s = get_agent_socket_name (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); } else if (!strcmp (line, "ssh_socket_name")) { const char *s = get_agent_ssh_socket_name (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); } else if (!strcmp (line, "scd_running")) { rc = agent_daemon_check_running (DAEMON_SCD)? 0:gpg_error (GPG_ERR_FALSE); } else if (!strcmp (line, "std_env_names")) { int iterator; const char *name; iterator = 0; while ((name = session_env_list_stdenvnames (&iterator, NULL))) { rc = assuan_send_data (ctx, name, strlen (name)+1); if (!rc) rc = assuan_send_data (ctx, NULL, 0); if (rc) break; } } else if (!strcmp (line, "std_session_env") || !strcmp (line, "std_startup_env")) { int iterator; const char *name, *value; char *string; iterator = 0; while ((name = session_env_list_stdenvnames (&iterator, NULL))) { value = session_env_getenv_or_default (line[5] == 't'? opt.startup_env:ctrl->session_env, name, NULL); if (value) { string = xtryasprintf ("%s=%s", name, value); if (!string) rc = gpg_error_from_syserror (); else { rc = assuan_send_data (ctx, string, strlen (string)+1); if (!rc) rc = assuan_send_data (ctx, NULL, 0); } if (rc) break; } } } else if (!strncmp (line, "getenv", 6) && (line[6] == ' ' || line[6] == '\t' || !line[6])) { line += 6; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { const char *s = getenv (line); if (!s) rc = set_error (GPG_ERR_NOT_FOUND, "No such envvar"); else rc = assuan_send_data (ctx, s, strlen (s)); } } else if (!strcmp (line, "connections")) { char numbuf[20]; snprintf (numbuf, sizeof numbuf, "%d", get_agent_active_connection_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "jent_active")) { char *buf; const char *fields[5]; buf = gcry_get_config (0, "rng-type"); if (buf && split_fields_colon (buf, fields, DIM (fields)) >= 5 && atoi (fields[4]) > 0) rc = 0; else rc = gpg_error (GPG_ERR_FALSE); gcry_free (buf); } else if (!strcmp (line, "s2k_count_cal")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", get_calibrated_s2k_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "s2k_time")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_time ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return rc; } /* This function is called by Libassuan to parse the OPTION command. It has been registered similar to the other Assuan commands. */ static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "agent-awareness")) { /* The value is a version string telling us of which agent version the caller is aware of. */ ctrl->server_local->allow_fully_canceled = gnupg_compare_version (value, "2.1.0"); } else if (ctrl->restricted) { err = gpg_error (GPG_ERR_FORBIDDEN); } /* All options below are not allowed in restricted mode. */ else if (!strcmp (key, "putenv")) { /* Change the session's environment to be used for the Pinentry. Valid values are: Delete envvar NAME = Set envvar NAME to the empty string = Set envvar NAME to VALUE */ err = session_env_putenv (ctrl->session_env, value); } else if (!strcmp (key, "display")) { err = session_env_setenv (ctrl->session_env, "DISPLAY", value); } else if (!strcmp (key, "ttyname")) { if (!opt.keep_tty) err = session_env_setenv (ctrl->session_env, "GPG_TTY", value); } else if (!strcmp (key, "ttytype")) { if (!opt.keep_tty) err = session_env_setenv (ctrl->session_env, "TERM", value); } else if (!strcmp (key, "lc-ctype")) { if (ctrl->lc_ctype) xfree (ctrl->lc_ctype); ctrl->lc_ctype = xtrystrdup (value); if (!ctrl->lc_ctype) return out_of_core (); } else if (!strcmp (key, "lc-messages")) { if (ctrl->lc_messages) xfree (ctrl->lc_messages); ctrl->lc_messages = xtrystrdup (value); if (!ctrl->lc_messages) return out_of_core (); } else if (!strcmp (key, "xauthority")) { err = session_env_setenv (ctrl->session_env, "XAUTHORITY", value); } else if (!strcmp (key, "pinentry-user-data")) { err = session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", value); } else if (!strcmp (key, "use-cache-for-signing")) ctrl->server_local->use_cache_for_signing = *value? !!atoi (value) : 0; else if (!strcmp (key, "allow-pinentry-notify")) ctrl->server_local->allow_pinentry_notify = 1; else if (!strcmp (key, "pinentry-mode")) { int tmp = parse_pinentry_mode (value); if (tmp == -1) err = gpg_error (GPG_ERR_INV_VALUE); else if (tmp == PINENTRY_MODE_LOOPBACK && !opt.allow_loopback_pinentry) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else ctrl->pinentry_mode = tmp; } else if (!strcmp (key, "cache-ttl-opt-preset")) { ctrl->cache_ttl_opt_preset = *value? atoi (value) : 0; } else if (!strcmp (key, "s2k-count")) { ctrl->s2k_count = *value? strtoul(value, NULL, 10) : 0; if (ctrl->s2k_count && ctrl->s2k_count < 65536) { ctrl->s2k_count = 0; } } else if (!strcmp (key, "pretend-request-origin")) { log_assert (!ctrl->restricted); switch (parse_request_origin (value)) { case REQUEST_ORIGIN_LOCAL: ctrl->restricted = 0; break; case REQUEST_ORIGIN_REMOTE: ctrl->restricted = 1; break; case REQUEST_ORIGIN_BROWSER: ctrl->restricted = 2; break; default: err = gpg_error (GPG_ERR_INV_VALUE); /* Better pretend to be remote in case of a bad value. */ ctrl->restricted = 1; break; } } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } /* Called by libassuan after all commands. ERR is the error from the last assuan operation and not the one returned from the command. */ static void post_cmd_notify (assuan_context_t ctx, gpg_error_t err) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)err; /* Switch off any I/O monitor controlled logging pausing. */ ctrl->server_local->pause_io_logging = 0; } /* This function is called by libassuan for all I/O. We use it here to disable logging for the GETEVENTCOUNTER commands. This is so that the debug output won't get cluttered by this primitive command. */ static unsigned int io_monitor (assuan_context_t ctx, void *hook, int direction, const char *line, size_t linelen) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) hook; /* We want to suppress all Assuan log messages for connections from * self. However, assuan_get_pid works only after * assuan_accept. Now, assuan_accept already logs a line ending with * the process id. We use this hack here to get the peers pid so * that we can compare it to our pid. We should add an assuan * function to return the pid for a file descriptor and use that to * detect connections to self. */ if (ctx && !ctrl->server_local->greeting_seen && direction == ASSUAN_IO_TO_PEER) { ctrl->server_local->greeting_seen = 1; if (linelen > 32 && !strncmp (line, "OK Pleased to meet you, process ", 32) && strtoul (line+32, NULL, 10) == getpid ()) return ASSUAN_IO_MONITOR_NOLOG; } /* Do not log self-connections. This makes the log cleaner because * we won't see the check-our-own-socket calls. */ if (ctx && ctrl->server_local->connect_from_self) return ASSUAN_IO_MONITOR_NOLOG; /* Note that we only check for the uppercase name. This allows the user to see the logging for debugging if using a non-upercase command name. */ if (ctx && direction == ASSUAN_IO_FROM_PEER && linelen >= 15 && !strncmp (line, "GETEVENTCOUNTER", 15) && (linelen == 15 || spacep (line+15))) { ctrl->server_local->pause_io_logging = 1; } return ctrl->server_local->pause_io_logging? ASSUAN_IO_MONITOR_NOLOG : 0; } /* Return true if the command CMD implements the option OPT. */ static int command_has_option (const char *cmd, const char *cmdopt) { if (!strcmp (cmd, "GET_PASSPHRASE")) { if (!strcmp (cmdopt, "repeat")) return 1; if (!strcmp (cmdopt, "newsymkey")) return 1; } return 0; } /* Tell Libassuan about our commands. Also register the other Assuan handlers. */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "GETEVENTCOUNTER",cmd_geteventcounter, hlp_geteventcounter }, { "ISTRUSTED", cmd_istrusted, hlp_istrusted }, { "HAVEKEY", cmd_havekey, hlp_havekey }, { "KEYINFO", cmd_keyinfo, hlp_keyinfo }, { "SIGKEY", cmd_sigkey, hlp_sigkey }, { "SETKEY", cmd_sigkey, hlp_sigkey }, { "SETKEYDESC", cmd_setkeydesc,hlp_setkeydesc }, { "SETHASH", cmd_sethash, hlp_sethash }, { "PKSIGN", cmd_pksign, hlp_pksign }, { "PKDECRYPT", cmd_pkdecrypt, hlp_pkdecrypt }, { "GENKEY", cmd_genkey, hlp_genkey }, { "READKEY", cmd_readkey, hlp_readkey }, { "GET_PASSPHRASE", cmd_get_passphrase, hlp_get_passphrase }, { "PRESET_PASSPHRASE", cmd_preset_passphrase, hlp_preset_passphrase }, { "CLEAR_PASSPHRASE", cmd_clear_passphrase, hlp_clear_passphrase }, { "GET_CONFIRMATION", cmd_get_confirmation, hlp_get_confirmation }, { "LISTTRUSTED", cmd_listtrusted, hlp_listtrusted }, { "MARKTRUSTED", cmd_marktrusted, hlp_martrusted }, { "LEARN", cmd_learn, hlp_learn }, { "PASSWD", cmd_passwd, hlp_passwd }, { "INPUT", NULL }, { "OUTPUT", NULL }, { "SCD", cmd_scd, hlp_scd }, { "KEYWRAP_KEY", cmd_keywrap_key, hlp_keywrap_key }, { "IMPORT_KEY", cmd_import_key, hlp_import_key }, { "EXPORT_KEY", cmd_export_key, hlp_export_key }, { "DELETE_KEY", cmd_delete_key, hlp_delete_key }, { "GET_SECRET", cmd_get_secret, hlp_get_secret }, { "PUT_SECRET", cmd_put_secret, hlp_put_secret }, { "GETVAL", cmd_getval, hlp_getval }, { "PUTVAL", cmd_putval, hlp_putval }, { "UPDATESTARTUPTTY", cmd_updatestartuptty, hlp_updatestartuptty }, { "KILLAGENT", cmd_killagent, hlp_killagent }, { "RELOADAGENT", cmd_reloadagent,hlp_reloadagent }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "KEYTOCARD", cmd_keytocard, hlp_keytocard }, { "KEYTOTPM", cmd_keytotpm, hlp_keytotpm }, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } assuan_register_post_cmd_notify (ctx, post_cmd_notify); assuan_register_reset_notify (ctx, reset_notify); assuan_register_option_handler (ctx, option_handler); return 0; } /* Startup the server. If LISTEN_FD and FD is given as -1, this is a simple piper server, otherwise it is a regular server. CTRL is the control structure for this connection; it has only the basic initialization. */ void start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd) { int rc; assuan_context_t ctx = NULL; if (ctrl->restricted) { if (agent_copy_startup_env (ctrl)) return; } rc = assuan_new (&ctx); if (rc) { log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc)); agent_exit (2); } if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD) { assuan_fd_t filedes[2]; filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); rc = assuan_init_pipe_server (ctx, filedes); } else if (listen_fd != GNUPG_INVALID_FD) { rc = assuan_init_socket_server (ctx, listen_fd, 0); /* FIXME: Need to call assuan_sock_set_nonce for Windows. But this branch is currently not used. */ } else { rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED); } if (rc) { log_error ("failed to initialize the server: %s\n", gpg_strerror(rc)); agent_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to register commands with Assuan: %s\n", gpg_strerror(rc)); agent_exit (2); } assuan_set_pointer (ctx, ctrl); ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local); ctrl->server_local->assuan_ctx = ctx; ctrl->server_local->use_cache_for_signing = 1; ctrl->digest.data = NULL; ctrl->digest.raw_value = 0; ctrl->digest.is_pss = 0; assuan_set_io_monitor (ctx, io_monitor, NULL); agent_set_progress_cb (progress_cb, ctrl); for (;;) { assuan_peercred_t client_creds; /* Note: Points into CTX. */ pid_t pid; rc = assuan_accept (ctx); if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1) { break; } else if (rc) { log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); break; } rc = assuan_get_peercred (ctx, &client_creds); if (rc) { /* Note that on Windows we don't get the peer credentials * and thus we silence the error. */ if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD) ; #ifdef HAVE_W32_SYSTEM else if (gpg_err_code (rc) == GPG_ERR_ASS_GENERAL) ; #endif else log_info ("Assuan get_peercred failed: %s\n", gpg_strerror (rc)); pid = assuan_get_pid (ctx); ctrl->client_uid = -1; } else { #ifdef HAVE_W32_SYSTEM pid = assuan_get_pid (ctx); ctrl->client_uid = -1; #else pid = client_creds->pid; ctrl->client_uid = client_creds->uid; #endif } ctrl->client_pid = (pid == ASSUAN_INVALID_PID)? 0 : (unsigned long)pid; ctrl->server_local->connect_from_self = (pid == getpid ()); rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); continue; } } /* Clear the keyinfo cache. */ agent_card_free_keyinfo (ctrl->server_local->last_card_keyinfo.ki); /* Reset the nonce caches. */ clear_nonce_cache (ctrl); /* Reset the SCD if needed. */ agent_reset_daemon (ctrl); /* Reset the pinentry (in case of popup messages). */ agent_reset_query (ctrl); /* Cleanup. */ assuan_release (ctx); xfree (ctrl->server_local->keydesc); xfree (ctrl->server_local->import_key); xfree (ctrl->server_local->export_key); if (ctrl->server_local->stopme) agent_exit (0); xfree (ctrl->server_local); ctrl->server_local = NULL; } /* Helper for the pinentry loopback mode. It merely passes the parameters on to the client. */ gpg_error_t pinentry_loopback(ctrl_t ctrl, const char *keyword, unsigned char **buffer, size_t *size, size_t max_length) { gpg_error_t rc; assuan_context_t ctx = ctrl->server_local->assuan_ctx; rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", max_length); if (rc) return rc; assuan_begin_confidential (ctx); rc = assuan_inquire (ctx, keyword, buffer, size, max_length); assuan_end_confidential (ctx); return rc; } /* Helper for the pinentry loopback mode to ask confirmation or just to show message. */ gpg_error_t pinentry_loopback_confirm (ctrl_t ctrl, const char *desc, int ask_confirmation, const char *ok, const char *notok) { gpg_error_t err = 0; assuan_context_t ctx = ctrl->server_local->assuan_ctx; if (desc) err = print_assuan_status (ctx, "SETDESC", "%s", desc); if (!err && ok) err = print_assuan_status (ctx, "SETOK", "%s", ok); if (!err && notok) err = print_assuan_status (ctx, "SETNOTOK", "%s", notok); if (!err) err = assuan_inquire (ctx, ask_confirmation ? "CONFIRM 1" : "CONFIRM 0", NULL, NULL, 0); return err; } diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index 5413a88ac..b50767060 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -1,1620 +1,1624 @@ @c Copyright (C) 2002 Free Software Foundation, Inc. @c This is part of the GnuPG manual. @c For copying conditions, see the file gnupg.texi. @include defs.inc @node Invoking GPG-AGENT @chapter Invoking GPG-AGENT @cindex GPG-AGENT command options @cindex command options @cindex options, GPG-AGENT command @manpage gpg-agent.1 @ifset manverb .B gpg-agent \- Secret key management for GnuPG @end ifset @mansect synopsis @ifset manverb .B gpg-agent .RB [ \-\-homedir .IR dir ] .RB [ \-\-options .IR file ] .RI [ options ] .br .B gpg-agent .RB [ \-\-homedir .IR dir ] .RB [ \-\-options .IR file ] .RI [ options ] .B \-\-server .br .B gpg-agent .RB [ \-\-homedir .IR dir ] .RB [ \-\-options .IR file ] .RI [ options ] .B \-\-daemon .RI [ command_line ] @end ifset @mansect description @command{gpg-agent} is a daemon to manage secret (private) keys independently from any protocol. It is used as a backend for @command{gpg} and @command{gpgsm} as well as for a couple of other utilities. The agent is automatically started on demand by @command{gpg}, @command{gpgsm}, @command{gpgconf}, or @command{gpg-connect-agent}. Thus there is no reason to start it manually. In case you want to use the included Secure Shell Agent you may start the agent using: @c From dkg on gnupg-devel on 2016-04-21: @c @c Here's an attempt at writing a short description of the goals of an @c isolated cryptographic agent: @c @c A cryptographic agent should control access to secret key material. @c The agent permits use of the secret key material by a supplicant @c without providing a copy of the secret key material to the supplicant. @c @c An isolated cryptographic agent separates the request for use of @c secret key material from permission for use of secret key material. @c That is, the system or process requesting use of the key (the @c "supplicant") can be denied use of the key by the owner/operator of @c the agent (the "owner"), which the supplicant has no control over. @c @c One way of enforcing this split is a per-key or per-session @c passphrase, known only by the owner, which must be supplied to the @c agent to permit the use of the secret key material. Another way is @c with an out-of-band permission mechanism (e.g. a button or GUI @c interface that the owner has access to, but the supplicant does not). @c @c The rationale for this separation is that it allows access to the @c secret key to be tightly controlled and audited, and it doesn't permit @c the supplicant to either copy the key or to override the owner's @c intentions. @example gpg-connect-agent /bye @end example @noindent If you want to manually terminate the currently-running agent, you can safely do so with: @example gpgconf --kill gpg-agent @end example @noindent @efindex GPG_TTY You should always add the following lines to your @code{.bashrc} or whatever initialization file is used for all shell invocations: @smallexample GPG_TTY=$(tty) export GPG_TTY @end smallexample @noindent It is important that this environment variable always reflects the output of the @code{tty} command. For W32 systems this option is not required. Please make sure that a proper pinentry program has been installed under the default filename (which is system dependent) or use the option @option{pinentry-program} to specify the full name of that program. It is often useful to install a symbolic link from the actual used pinentry (e.g. @file{@value{BINDIR}/pinentry-gtk}) to the expected one (e.g. @file{@value{BINDIR}/pinentry}). @manpause @noindent @xref{Option Index}, for an index to @command{GPG-AGENT}'s commands and options. @mancont @menu * Agent Commands:: List of all commands. * Agent Options:: List of all options. * Agent Configuration:: Configuration files. * Agent Signals:: Use of some signals. * Agent Examples:: Some usage examples. * Agent Protocol:: The protocol the agent uses. @end menu @mansect commands @node Agent Commands @section Commands Commands are not distinguished from options except for the fact that only one command is allowed. @table @gnupgtabopt @item --version @opindex version Print the program version and licensing information. Note that you cannot abbreviate this command. @item --help @itemx -h @opindex help Print a usage message summarizing the most useful command-line options. Note that you cannot abbreviate this command. @item --dump-options @opindex dump-options Print a list of all available options and commands. Note that you cannot abbreviate this command. @item --server @opindex server Run in server mode and wait for commands on the @code{stdin}. The default mode is to create a socket and listen for commands there. @item --daemon [@var{command line}] @opindex daemon Start the gpg-agent as a daemon; that is, detach it from the console and run it in the background. As an alternative you may create a new process as a child of gpg-agent: @code{gpg-agent --daemon /bin/sh}. This way you get a new shell with the environment setup properly; after you exit from this shell, gpg-agent terminates within a few seconds. @item --supervised @opindex supervised Run in the foreground, sending logs by default to stderr, and listening on provided file descriptors, which must already be bound to listening sockets. This command is useful when running under systemd or other similar process supervision schemes. This option is not supported on Windows. In --supervised mode, different file descriptors can be provided for use as different socket types (e.g. ssh, extra) as long as they are identified in the environment variable @code{LISTEN_FDNAMES} (see sd_listen_fds(3) on some Linux distributions for more information on this convention). @end table @mansect options @node Agent Options @section Option Summary Options may either be used on the command line or, after stripping off the two leading dashes, in the configuration file. @table @gnupgtabopt @anchor{option --options} @item --options @var{file} @opindex options Reads configuration from @var{file} instead of from the default per-user configuration file. The default configuration file is named @file{gpg-agent.conf} and expected in the @file{.gnupg} directory directly below the home directory of the user. This option is ignored if used in an options file. @anchor{option --homedir} @include opt-homedir.texi @item -v @itemx --verbose @opindex verbose Outputs additional information while running. You can increase the verbosity by giving several verbose commands to @command{gpg-agent}, such as @samp{-vv}. @item -q @itemx --quiet @opindex quiet Try to be as quiet as possible. @item --batch @opindex batch Don't invoke a pinentry or do any other thing requiring human interaction. @item --faked-system-time @var{epoch} @opindex faked-system-time This option is only useful for testing; it sets the system time back or forth to @var{epoch} which is the number of seconds elapsed since the year 1970. @item --debug-level @var{level} @opindex debug-level Select the debug level for investigating problems. @var{level} may be a numeric value or a keyword: @table @code @item none No debugging at all. A value of less than 1 may be used instead of the keyword. @item basic Some basic debug messages. A value between 1 and 2 may be used instead of the keyword. @item advanced More verbose debug messages. A value between 3 and 5 may be used instead of the keyword. @item expert Even more detailed messages. A value between 6 and 8 may be used instead of the keyword. @item guru All of the debug messages you can get. A value greater than 8 may be used instead of the keyword. The creation of hash tracing files is only enabled if the keyword is used. @end table How these messages are mapped to the actual debugging flags is not specified and may change with newer releases of this program. They are however carefully selected to best aid in debugging. @item --debug @var{flags} @opindex debug Set debug flags. All flags are or-ed and @var{flags} may be given in C syntax (e.g. 0x0042) or as a comma separated list of flag names. To get a list of all supported flags the single word "help" can be used. This option is only useful for debugging and the behavior may change at any time without notice. @item --debug-all @opindex debug-all Same as @code{--debug=0xffffffff} @item --debug-wait @var{n} @opindex debug-wait When running in server mode, wait @var{n} seconds before entering the actual processing loop and print the pid. This gives time to attach a debugger. @item --debug-quick-random @opindex debug-quick-random This option inhibits the use of the very secure random quality level (Libgcrypt’s @code{GCRY_VERY_STRONG_RANDOM}) and degrades all request down to standard random quality. It is only used for testing and should not be used for any production quality keys. This option is only effective when given on the command line. On GNU/Linux, another way to quickly generate insecure keys is to use @command{rngd} to fill the kernel's entropy pool with lower quality random data. @command{rngd} is typically provided by the @command{rng-tools} package. It can be run as follows: @samp{sudo rngd -f -r /dev/urandom}. @item --debug-pinentry @opindex debug-pinentry This option enables extra debug information pertaining to the Pinentry. As of now it is only useful when used along with @code{--debug 1024}. @item --no-detach @opindex no-detach Don't detach the process from the console. This is mainly useful for debugging. @item -s @itemx --sh @itemx -c @itemx --csh @opindex sh @opindex csh @efindex SHELL Format the info output in daemon mode for use with the standard Bourne shell or the C-shell respectively. The default is to guess it based on the environment variable @code{SHELL} which is correct in almost all cases. @item --grab @itemx --no-grab @opindex grab @opindex no-grab Tell the pinentry to grab the keyboard and mouse. This option should be used on X-Servers to avoid X-sniffing attacks. Any use of the option @option{--grab} overrides an used option @option{--no-grab}. The default is @option{--no-grab}. @anchor{option --log-file} @item --log-file @var{file} @opindex log-file @efindex HKCU\Software\GNU\GnuPG:DefaultLogFile Append all logging output to @var{file}. This is very helpful in seeing what the agent actually does. Use @file{socket://} to log to socket. If neither a log file nor a log file descriptor has been set on a Windows platform, the Registry entry @code{HKCU\Software\GNU\GnuPG:DefaultLogFile}, if set, is used to specify the logging output. @anchor{option --no-allow-mark-trusted} @item --no-allow-mark-trusted @opindex no-allow-mark-trusted Do not allow clients to mark keys as trusted, i.e. put them into the @file{trustlist.txt} file. This makes it harder for users to inadvertently accept Root-CA keys. @anchor{option --allow-preset-passphrase} @item --allow-preset-passphrase @opindex allow-preset-passphrase This option allows the use of @command{gpg-preset-passphrase} to seed the internal cache of @command{gpg-agent} with passphrases. @anchor{option --no-allow-loopback-pinentry} @item --no-allow-loopback-pinentry @item --allow-loopback-pinentry @opindex no-allow-loopback-pinentry @opindex allow-loopback-pinentry Disallow or allow clients to use the loopback pinentry features; see the option @option{pinentry-mode} for details. Allow is the default. The @option{--force} option of the Assuan command @command{DELETE_KEY} is also controlled by this option: The option is ignored if a loopback pinentry is disallowed. @item --no-allow-external-cache @opindex no-allow-external-cache Tell Pinentry not to enable features which use an external cache for passphrases. Some desktop environments prefer to unlock all credentials with one master password and may have installed a Pinentry which employs an additional external cache to implement such a policy. By using this option the Pinentry is advised not to make use of such a cache and instead always ask the user for the requested passphrase. @item --allow-emacs-pinentry @opindex allow-emacs-pinentry Tell Pinentry to allow features to divert the passphrase entry to a running Emacs instance. How this is exactly handled depends on the version of the used Pinentry. @item --ignore-cache-for-signing @opindex ignore-cache-for-signing This option will let @command{gpg-agent} bypass the passphrase cache for all signing operation. Note that there is also a per-session option to control this behavior but this command line option takes precedence. @item --default-cache-ttl @var{n} @opindex default-cache-ttl Set the time a cache entry is valid to @var{n} seconds. The default is 600 seconds. Each time a cache entry is accessed, the entry's timer is reset. To set an entry's maximum lifetime, use @command{max-cache-ttl}. Note that a cached passphrase may not be evicted immediately from memory if no client requests a cache operation. This is due to an internal housekeeping function which is only run every few seconds. @item --default-cache-ttl-ssh @var{n} @opindex default-cache-ttl Set the time a cache entry used for SSH keys is valid to @var{n} seconds. The default is 1800 seconds. Each time a cache entry is accessed, the entry's timer is reset. To set an entry's maximum lifetime, use @command{max-cache-ttl-ssh}. @item --max-cache-ttl @var{n} @opindex max-cache-ttl Set the maximum time a cache entry is valid to @var{n} seconds. After this time a cache entry will be expired even if it has been accessed recently or has been set using @command{gpg-preset-passphrase}. The default is 2 hours (7200 seconds). @item --max-cache-ttl-ssh @var{n} @opindex max-cache-ttl-ssh Set the maximum time a cache entry used for SSH keys is valid to @var{n} seconds. After this time a cache entry will be expired even if it has been accessed recently or has been set using @command{gpg-preset-passphrase}. The default is 2 hours (7200 seconds). @item --enforce-passphrase-constraints @opindex enforce-passphrase-constraints Enforce the passphrase constraints by not allowing the user to bypass them using the ``Take it anyway'' button. @item --min-passphrase-len @var{n} @opindex min-passphrase-len Set the minimal length of a passphrase. When entering a new passphrase shorter than this value a warning will be displayed. Defaults to 8. @item --min-passphrase-nonalpha @var{n} @opindex min-passphrase-nonalpha Set the minimal number of digits or special characters required in a passphrase. When entering a new passphrase with less than this number of digits or special characters a warning will be displayed. Defaults to 1. @item --check-passphrase-pattern @var{file} @itemx --check-sym-passphrase-pattern @var{file} @opindex check-passphrase-pattern @opindex check-sym-passphrase-pattern Check the passphrase against the pattern given in @var{file}. When entering a new passphrase matching one of these pattern a warning will be displayed. @var{file} should be an absolute filename. The default is not to use any pattern file. The second version of this option is only used when creating a new symmetric key to allow the use of different patterns for such passphrases. Security note: It is known that checking a passphrase against a list of pattern or even against a complete dictionary is not very effective to enforce good passphrases. Users will soon figure up ways to bypass such a policy. A better policy is to educate users on good security behavior and optionally to run a passphrase cracker regularly on all users passphrases to catch the very simple ones. @item --max-passphrase-days @var{n} @opindex max-passphrase-days Ask the user to change the passphrase if @var{n} days have passed since the last change. With @option{--enforce-passphrase-constraints} set the user may not bypass this check. @item --enable-passphrase-history @opindex enable-passphrase-history This option does nothing yet. @item --pinentry-invisible-char @var{char} @opindex pinentry-invisible-char This option asks the Pinentry to use @var{char} for displaying hidden characters. @var{char} must be one character UTF-8 string. A Pinentry may or may not honor this request. @item --pinentry-timeout @var{n} @opindex pinentry-timeout This option asks the Pinentry to timeout after @var{n} seconds with no user input. The default value of 0 does not ask the pinentry to timeout, however a Pinentry may use its own default timeout value in this case. A Pinentry may or may not honor this request. @item --pinentry-formatted-passphrase @opindex pinentry-formatted-passphrase This option asks the Pinentry to enable passphrase formatting when asking the user for a new passphrase and masking of the passphrase is turned off. If passphrase formatting is enabled, then all non-breaking space characters are stripped from the entered passphrase. Passphrase formatting is mostly -useful in combination with passphrases generated with the GENPIN command. +useful in combination with passphrases generated with the GENPIN +feature of some Pinentries. Note that such a generated +passphrase, if not modified by the user, skips all passphrase +constraints checking because such constraints would actually weaken +the generated passphrase. @item --pinentry-program @var{filename} @opindex pinentry-program Use program @var{filename} as the PIN entry. The default is installation dependent. With the default configuration the name of the default pinentry is @file{pinentry}; if that file does not exist but a @file{pinentry-basic} exist the latter is used. On a Windows platform the default is to use the first existing program from this list: @file{bin\pinentry.exe}, @file{..\Gpg4win\bin\pinentry.exe}, @file{..\Gpg4win\pinentry.exe}, @file{..\GNU\GnuPG\pinentry.exe}, @file{..\GNU\bin\pinentry.exe}, @file{bin\pinentry-basic.exe} where the file names are relative to the GnuPG installation directory. @item --pinentry-touch-file @var{filename} @opindex pinentry-touch-file By default the filename of the socket gpg-agent is listening for requests is passed to Pinentry, so that it can touch that file before exiting (it does this only in curses mode). This option changes the file passed to Pinentry to @var{filename}. The special name @code{/dev/null} may be used to completely disable this feature. Note that Pinentry will not create that file, it will only change the modification and access time. @item --scdaemon-program @var{filename} @opindex scdaemon-program Use program @var{filename} as the Smartcard daemon. The default is installation dependent and can be shown with the @command{gpgconf} command. @item --disable-scdaemon @opindex disable-scdaemon Do not make use of the scdaemon tool. This option has the effect of disabling the ability to do smartcard operations. Note, that enabling this option at runtime does not kill an already forked scdaemon. @item --disable-check-own-socket @opindex disable-check-own-socket @command{gpg-agent} employs a periodic self-test to detect a stolen socket. This usually means a second instance of @command{gpg-agent} has taken over the socket and @command{gpg-agent} will then terminate itself. This option may be used to disable this self-test for debugging purposes. @item --use-standard-socket @itemx --no-use-standard-socket @itemx --use-standard-socket-p @opindex use-standard-socket @opindex no-use-standard-socket @opindex use-standard-socket-p Since GnuPG 2.1 the standard socket is always used. These options have no more effect. The command @code{gpg-agent --use-standard-socket-p} will thus always return success. @item --display @var{string} @itemx --ttyname @var{string} @itemx --ttytype @var{string} @itemx --lc-ctype @var{string} @itemx --lc-messages @var{string} @itemx --xauthority @var{string} @opindex display @opindex ttyname @opindex ttytype @opindex lc-ctype @opindex lc-messages @opindex xauthority These options are used with the server mode to pass localization information. @item --keep-tty @itemx --keep-display @opindex keep-tty @opindex keep-display Ignore requests to change the current @code{tty} or X window system's @code{DISPLAY} variable respectively. This is useful to lock the pinentry to pop up at the @code{tty} or display you started the agent. @item --listen-backlog @var{n} @opindex listen-backlog Set the size of the queue for pending connections. The default is 64. @anchor{option --extra-socket} @item --extra-socket @var{name} @opindex extra-socket The extra socket is created by default, you may use this option to change the name of the socket. To disable the creation of the socket use ``none'' or ``/dev/null'' for @var{name}. Also listen on native gpg-agent connections on the given socket. The intended use for this extra socket is to setup a Unix domain socket forwarding from a remote machine to this socket on the local machine. A @command{gpg} running on the remote machine may then connect to the local gpg-agent and use its private keys. This enables decrypting or signing data on a remote machine without exposing the private keys to the remote machine. @item --enable-extended-key-format @itemx --disable-extended-key-format @opindex enable-extended-key-format @opindex disable-extended-key-format Since version 2.3 keys are created in the extended private key format. Changing the passphrase of a key will also convert the key to that new format. This new key format is supported since GnuPG version 2.1.12 and thus there should be no need to disable it. The disable option allows to revert to the old behavior for new keys; be aware that keys are never migrated back to the old format. However if the enable option has been used the disable option won't have an effect. The advantage of the extended private key format is that it is text based and can carry additional meta data. @anchor{option --enable-ssh-support} @item --enable-ssh-support @itemx --enable-putty-support @opindex enable-ssh-support @opindex enable-putty-support The OpenSSH Agent protocol is always enabled, but @command{gpg-agent} will only set the @code{SSH_AUTH_SOCK} variable if this flag is given. In this mode of operation, the agent does not only implement the gpg-agent protocol, but also the agent protocol used by OpenSSH (through a separate socket). Consequently, it should be possible to use the gpg-agent as a drop-in replacement for the well known ssh-agent. SSH Keys, which are to be used through the agent, need to be added to the gpg-agent initially through the ssh-add utility. When a key is added, ssh-add will ask for the password of the provided key file and send the unprotected key material to the agent; this causes the gpg-agent to ask for a passphrase, which is to be used for encrypting the newly received key and storing it in a gpg-agent specific directory. Once a key has been added to the gpg-agent this way, the gpg-agent will be ready to use the key. Note: in case the gpg-agent receives a signature request, the user might need to be prompted for a passphrase, which is necessary for decrypting the stored key. Since the ssh-agent protocol does not contain a mechanism for telling the agent on which display/terminal it is running, gpg-agent's ssh-support will use the TTY or X display where gpg-agent has been started. To switch this display to the current one, the following command may be used: @smallexample gpg-connect-agent updatestartuptty /bye @end smallexample Although all GnuPG components try to start the gpg-agent as needed, this is not possible for the ssh support because ssh does not know about it. Thus if no GnuPG tool which accesses the agent has been run, there is no guarantee that ssh is able to use gpg-agent for authentication. To fix this you may start gpg-agent if needed using this simple command: @smallexample gpg-connect-agent /bye @end smallexample Adding the @option{--verbose} shows the progress of starting the agent. The @option{--enable-putty-support} is only available under Windows and allows the use of gpg-agent with the ssh implementation @command{putty}. This is similar to the regular ssh-agent support but makes use of Windows message queue as required by @command{putty}. @anchor{option --ssh-fingerprint-digest} @item --ssh-fingerprint-digest @opindex ssh-fingerprint-digest Select the digest algorithm used to compute ssh fingerprints that are communicated to the user, e.g. in pinentry dialogs. OpenSSH has transitioned from using MD5 to the more secure SHA256. @item --auto-expand-secmem @var{n} @opindex auto-expand-secmem Allow Libgcrypt to expand its secure memory area as required. The optional value @var{n} is a non-negative integer with a suggested size in bytes of each additionally allocated secure memory area. The value is rounded up to the next 32 KiB; usual C style prefixes are allowed. For an heavy loaded gpg-agent with many concurrent connection this option avoids sign or decrypt errors due to out of secure memory error returns. @item --s2k-calibration @var{milliseconds} @opindex s2k-calibration Change the default calibration time to @var{milliseconds}. The given value is capped at 60 seconds; a value of 0 resets to the compiled-in default. This option is re-read on a SIGHUP (or @code{gpgconf --reload gpg-agent}) and the S2K count is then re-calibrated. @item --s2k-count @var{n} @opindex s2k-count Specify the iteration count used to protect the passphrase. This option can be used to override the auto-calibration done by default. The auto-calibration computes a count which requires by default 100ms to mangle a given passphrase. See also @option{--s2k-calibration}. To view the actually used iteration count and the milliseconds required for an S2K operation use: @example gpg-connect-agent 'GETINFO s2k_count' /bye gpg-connect-agent 'GETINFO s2k_time' /bye @end example To view the auto-calibrated count use: @example gpg-connect-agent 'GETINFO s2k_count_cal' /bye @end example @end table @mansect files @node Agent Configuration @section Configuration There are a few configuration files needed for the operation of the agent. By default they may all be found in the current home directory (@pxref{option --homedir}). @table @file @item gpg-agent.conf @efindex gpg-agent.conf This is the standard configuration file read by @command{gpg-agent} on startup. It may contain any valid long option; the leading two dashes may not be entered and the option may not be abbreviated. This file is also read after a @code{SIGHUP} however only a few options will actually have an effect. This default name may be changed on the command line (@pxref{option --options}). You should backup this file. @item trustlist.txt @efindex trustlist.txt This is the list of trusted keys. You should backup this file. Comment lines, indicated by a leading hash mark, as well as empty lines are ignored. To mark a key as trusted you need to enter its fingerprint followed by a space and a capital letter @code{S}. Colons may optionally be used to separate the bytes of a fingerprint; this enables cutting and pasting the fingerprint from a key listing output. If the line is prefixed with a @code{!} the key is explicitly marked as not trusted. Here is an example where two keys are marked as ultimately trusted and one as not trusted: @cartouche @smallexample # CN=Wurzel ZS 3,O=Intevation GmbH,C=DE A6935DD34EF3087973C706FC311AA2CCF733765B S # CN=PCA-1-Verwaltung-02/O=PKI-1-Verwaltung/C=DE DC:BD:69:25:48:BD:BB:7E:31:6E:BB:80:D3:00:80:35:D4:F8:A6:CD S # CN=Root-CA/O=Schlapphuete/L=Pullach/C=DE !14:56:98:D3:FE:9C:CA:5A:31:6E:BC:81:D3:11:4E:00:90:A3:44:C2 S @end smallexample @end cartouche Before entering a key into this file, you need to ensure its authenticity. How to do this depends on your organisation; your administrator might have already entered those keys which are deemed trustworthy enough into this file. Places where to look for the fingerprint of a root certificate are letters received from the CA or the website of the CA (after making 100% sure that this is indeed the website of that CA). You may want to consider disallowing interactive updates of this file by using the @ref{option --no-allow-mark-trusted}. It might even be advisable to change the permissions to read-only so that this file can't be changed inadvertently. As a special feature a line @code{include-default} will include a global list of trusted certificates (e.g. @file{@value{SYSCONFDIR}/trustlist.txt}). This global list is also used if the local list is not available. It is possible to add further flags after the @code{S} for use by the caller: @table @code @item relax @cindex relax Relax checking of some root certificate requirements. As of now this flag allows the use of root certificates with a missing basicConstraints attribute (despite that it is a MUST for CA certificates) and disables CRL checking for the root certificate. @item cm If validation of a certificate finally issued by a CA with this flag set fails, try again using the chain validation model. @end table @item sshcontrol @efindex sshcontrol This file is used when support for the secure shell agent protocol has been enabled (@pxref{option --enable-ssh-support}). Only keys present in this file are used in the SSH protocol. You should backup this file. The @command{ssh-add} tool may be used to add new entries to this file; you may also add them manually. Comment lines, indicated by a leading hash mark, as well as empty lines are ignored. An entry starts with optional whitespace, followed by the keygrip of the key given as 40 hex digits, optionally followed by the caching TTL in seconds and another optional field for arbitrary flags. A non-zero TTL overrides the global default as set by @option{--default-cache-ttl-ssh}. The only flag support is @code{confirm}. If this flag is found for a key, each use of the key will pop up a pinentry to confirm the use of that key. The flag is automatically set if a new key was loaded into @code{gpg-agent} using the option @option{-c} of the @code{ssh-add} command. The keygrip may be prefixed with a @code{!} to disable an entry. The following example lists exactly one key. Note that keys available through a OpenPGP smartcard in the active smartcard reader are implicitly added to this list; i.e. there is no need to list them. @cartouche @smallexample # Key added on: 2011-07-20 20:38:46 # Fingerprint: 5e:8d:c4:ad:e7:af:6e:27:8a:d6:13:e4:79:ad:0b:81 34B62F25E277CF13D3C6BCEBFD3F85D08F0A864B 0 confirm @end smallexample @end cartouche @item private-keys-v1.d/ @efindex private-keys-v1.d This is the directory where gpg-agent stores the private keys. Each key is stored in a file with the name made up of the keygrip and the suffix @file{key}. You should backup all files in this directory and take great care to keep this backup closed away. @end table Note that on larger installations, it is useful to put predefined files into the directory @file{@value{SYSCONFSKELDIR}} so that newly created users start up with a working configuration. For existing users the a small helper script is provided to create these files (@pxref{addgnupghome}). @c @c Agent Signals @c @mansect signals @node Agent Signals @section Use of some signals A running @command{gpg-agent} may be controlled by signals, i.e. using the @command{kill} command to send a signal to the process. Here is a list of supported signals: @table @gnupgtabopt @item SIGHUP @cpindex SIGHUP This signal flushes all cached passphrases and if the program has been started with a configuration file, the configuration file is read again. Only certain options are honored: @code{quiet}, @code{verbose}, @code{debug}, @code{debug-all}, @code{debug-level}, @code{debug-pinentry}, @code{no-grab}, @code{pinentry-program}, @code{pinentry-invisible-char}, @code{default-cache-ttl}, @code{max-cache-ttl}, @code{ignore-cache-for-signing}, @code{s2k-count}, @code{no-allow-external-cache}, @code{allow-emacs-pinentry}, @code{no-allow-mark-trusted}, @code{disable-scdaemon}, and @code{disable-check-own-socket}. @code{scdaemon-program} is also supported but due to the current implementation, which calls the scdaemon only once, it is not of much use unless you manually kill the scdaemon. @item SIGTERM @cpindex SIGTERM Shuts down the process but waits until all current requests are fulfilled. If the process has received 3 of these signals and requests are still pending, a shutdown is forced. @item SIGINT @cpindex SIGINT Shuts down the process immediately. @item SIGUSR1 @cpindex SIGUSR1 Dump internal information to the log file. @item SIGUSR2 @cpindex SIGUSR2 This signal is used for internal purposes. @end table @c @c Examples @c @mansect examples @node Agent Examples @section Examples It is important to set the environment variable @code{GPG_TTY} in your login shell, for example in the @file{~/.bashrc} init script: @cartouche @example export GPG_TTY=$(tty) @end example @end cartouche If you enabled the Ssh Agent Support, you also need to tell ssh about it by adding this to your init script: @cartouche @example unset SSH_AGENT_PID if [ "$@{gnupg_SSH_AUTH_SOCK_by:-0@}" -ne $$ ]; then export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)" fi @end example @end cartouche @c @c Assuan Protocol @c @manpause @node Agent Protocol @section Agent's Assuan Protocol Note: this section does only document the protocol, which is used by GnuPG components; it does not deal with the ssh-agent protocol. To see the full specification of each command, use @example gpg-connect-agent 'help COMMAND' /bye @end example @noindent or just 'help' to list all available commands. @noindent The @command{gpg-agent} daemon is started on demand by the GnuPG components. To identify a key we use a thing called keygrip which is the SHA-1 hash of an canonical encoded S-Expression of the public key as used in Libgcrypt. For the purpose of this interface the keygrip is given as a hex string. The advantage of using this and not the hash of a certificate is that it will be possible to use the same keypair for different protocols, thereby saving space on the token used to keep the secret keys. The @command{gpg-agent} may send status messages during a command or when returning from a command to inform a client about the progress or result of an operation. For example, the @var{INQUIRE_MAXLEN} status message may be sent during a server inquire to inform the client of the maximum usable length of the inquired data (which should not be exceeded). @menu * Agent PKDECRYPT:: Decrypting a session key * Agent PKSIGN:: Signing a Hash * Agent GENKEY:: Generating a Key * Agent IMPORT:: Importing a Secret Key * Agent EXPORT:: Exporting a Secret Key * Agent ISTRUSTED:: Importing a Root Certificate * Agent GET_PASSPHRASE:: Ask for a passphrase * Agent CLEAR_PASSPHRASE:: Expire a cached passphrase * Agent PRESET_PASSPHRASE:: Set a passphrase for a keygrip * Agent GET_CONFIRMATION:: Ask for confirmation * Agent HAVEKEY:: Check whether a key is available * Agent LEARN:: Register a smartcard * Agent PASSWD:: Change a Passphrase * Agent UPDATESTARTUPTTY:: Change the Standard Display * Agent GETEVENTCOUNTER:: Get the Event Counters * Agent GETINFO:: Return information about the process * Agent OPTION:: Set options for the session @end menu @node Agent PKDECRYPT @subsection Decrypting a session key The client asks the server to decrypt a session key. The encrypted session key should have all information needed to select the appropriate secret key or to delegate it to a smartcard. @example SETKEY @end example Tell the server about the key to be used for decryption. If this is not used, @command{gpg-agent} may try to figure out the key by trying to decrypt the message with each key available. @example PKDECRYPT @end example The agent checks whether this command is allowed and then does an INQUIRY to get the ciphertext the client should then send the cipher text. @example S: INQUIRE CIPHERTEXT C: D (xxxxxx C: D xxxx) C: END @end example Please note that the server may send status info lines while reading the data lines from the client. The data send is a SPKI like S-Exp with this structure: @example (enc-val ( ( ) ... ( ))) @end example Where algo is a string with the name of the algorithm; see the libgcrypt documentation for a list of valid algorithms. The number and names of the parameters depend on the algorithm. The agent does return an error if there is an inconsistency. If the decryption was successful the decrypted data is returned by means of "D" lines. Here is an example session: @cartouche @smallexample C: PKDECRYPT S: INQUIRE CIPHERTEXT C: D (enc-val elg (a 349324324) C: D (b 3F444677CA))) C: END S: # session key follows S: S PADDING 0 S: D (value 1234567890ABCDEF0) S: OK decryption successful @end smallexample @end cartouche The “PADDING” status line is only send if gpg-agent can tell what kind of padding is used. As of now only the value 0 is used to indicate that the padding has been removed. @node Agent PKSIGN @subsection Signing a Hash The client asks the agent to sign a given hash value. A default key will be chosen if no key has been set. To set a key a client first uses: @example SIGKEY @end example This can be used multiple times to create multiple signature, the list of keys is reset with the next PKSIGN command or a RESET. The server tests whether the key is a valid key to sign something and responds with okay. @example SETHASH --hash=| @end example The client can use this command to tell the server about the data (which usually is a hash) to be signed. is the decimal encoded hash algorithm number as used by Libgcrypt. Either or --hash= must be given. Valid names for are: @table @code @item sha1 The SHA-1 hash algorithm @item sha256 The SHA-256 hash algorithm @item rmd160 The RIPE-MD160 hash algorithm @item md5 The old and broken MD5 hash algorithm @item tls-md5sha1 A combined hash algorithm as used by the TLS protocol. @end table @noindent The actual signing is done using @example PKSIGN @end example Options are not yet defined, but may later be used to choose among different algorithms. The agent does then some checks, asks for the passphrase and as a result the server returns the signature as an SPKI like S-expression in "D" lines: @example (sig-val ( ( ) ... ( ))) @end example The operation is affected by the option @example OPTION use-cache-for-signing=0|1 @end example The default of @code{1} uses the cache. Setting this option to @code{0} will lead @command{gpg-agent} to ignore the passphrase cache. Note, that there is also a global command line option for @command{gpg-agent} to globally disable the caching. Here is an example session: @cartouche @smallexample C: SIGKEY S: OK key available C: SIGKEY S: OK key available C: PKSIGN S: # I did ask the user whether he really wants to sign S: # I did ask the user for the passphrase S: INQUIRE HASHVAL C: D ABCDEF012345678901234 C: END S: # signature follows S: D (sig-val rsa (s 45435453654612121212)) S: OK @end smallexample @end cartouche @node Agent GENKEY @subsection Generating a Key This is used to create a new keypair and store the secret key inside the active PSE --- which is in most cases a Soft-PSE. A not-yet-defined option allows choosing the storage location. To get the secret key out of the PSE, a special export tool has to be used. @example GENKEY [--no-protection] [--preset] [] @end example Invokes the key generation process and the server will then inquire on the generation parameters, like: @example S: INQUIRE KEYPARM C: D (genkey (rsa (nbits 1024))) C: END @end example The format of the key parameters which depends on the algorithm is of the form: @example (genkey (algo (parameter_name_1 ....) .... (parameter_name_n ....))) @end example If everything succeeds, the server returns the *public key* in a SPKI like S-Expression like this: @example (public-key (rsa (n ) (e ))) @end example Here is an example session: @cartouche @smallexample C: GENKEY S: INQUIRE KEYPARM C: D (genkey (rsa (nbits 1024))) C: END S: D (public-key S: D (rsa (n 326487324683264) (e 10001))) S OK key created @end smallexample @end cartouche The @option{--no-protection} option may be used to prevent prompting for a passphrase to protect the secret key while leaving the secret key unprotected. The @option{--preset} option may be used to add the passphrase to the cache using the default cache parameters. The @option{--inq-passwd} option may be used to create the key with a supplied passphrase. When used the agent does an inquiry with the keyword @code{NEWPASSWD} to retrieve that passphrase. This option takes precedence over @option{--no-protection}; however if the client sends a empty (zero-length) passphrase, this is identical to @option{--no-protection}. @node Agent IMPORT @subsection Importing a Secret Key This operation is not yet supported by GpgAgent. Specialized tools are to be used for this. There is no actual need because we can expect that secret keys created by a 3rd party are stored on a smartcard. If we have generated the key ourselves, we do not need to import it. @node Agent EXPORT @subsection Export a Secret Key Not implemented. Should be done by an extra tool. @node Agent ISTRUSTED @subsection Importing a Root Certificate Actually we do not import a Root Cert but provide a way to validate any piece of data by storing its Hash along with a description and an identifier in the PSE. Here is the interface description: @example ISTRUSTED @end example Check whether the OpenPGP primary key or the X.509 certificate with the given fingerprint is an ultimately trusted key or a trusted Root CA certificate. The fingerprint should be given as a hexstring (without any blanks or colons or whatever in between) and may be left padded with 00 in case of an MD5 fingerprint. GPGAgent will answer with: @example OK @end example The key is in the table of trusted keys. @example ERR 304 (Not Trusted) @end example The key is not in this table. Gpg needs the entire list of trusted keys to maintain the web of trust; the following command is therefore quite helpful: @example LISTTRUSTED @end example GpgAgent returns a list of trusted keys line by line: @example S: D 000000001234454556565656677878AF2F1ECCFF P S: D 340387563485634856435645634856438576457A P S: D FEDC6532453745367FD83474357495743757435D S S: OK @end example The first item on a line is the hexified fingerprint where MD5 fingerprints are @code{00} padded to the left and the second item is a flag to indicate the type of key (so that gpg is able to only take care of PGP keys). P = OpenPGP, S = S/MIME. A client should ignore the rest of the line, so that we can extend the format in the future. Finally a client should be able to mark a key as trusted: @example MARKTRUSTED @var{fingerprint} "P"|"S" @end example The server will then pop up a window to ask the user whether she really trusts this key. For this it will probably ask for a text to be displayed like this: @example S: INQUIRE TRUSTDESC C: D Do you trust the key with the fingerprint @@FPR@@ C: D bla fasel blurb. C: END S: OK @end example Known sequences with the pattern @@foo@@ are replaced according to this table: @table @code @item @@FPR16@@ Format the fingerprint according to gpg rules for a v3 keys. @item @@FPR20@@ Format the fingerprint according to gpg rules for a v4 keys. @item @@FPR@@ Choose an appropriate format to format the fingerprint. @item @@@@ Replaced by a single @code{@@}. @end table @node Agent GET_PASSPHRASE @subsection Ask for a passphrase This function is usually used to ask for a passphrase to be used for symmetric encryption, but may also be used by programs which need special handling of passphrases. This command uses a syntax which helps clients to use the agent with minimum effort. @example GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]] \ [--qualitybar] @var{cache_id} \ [@var{error_message} @var{prompt} @var{description}] @end example @var{cache_id} is expected to be a string used to identify a cached passphrase. Use a @code{X} to bypass the cache. With no other arguments the agent returns a cached passphrase or an error. By convention either the hexified fingerprint of the key shall be used for @var{cache_id} or an arbitrary string prefixed with the name of the calling application and a colon: Like @code{gpg:somestring}. @var{error_message} is either a single @code{X} for no error message or a string to be shown as an error message like (e.g. "invalid passphrase"). Blanks must be percent escaped or replaced by @code{+}'. @var{prompt} is either a single @code{X} for a default prompt or the text to be shown as the prompt. Blanks must be percent escaped or replaced by @code{+}. @var{description} is a text shown above the entry field. Blanks must be percent escaped or replaced by @code{+}. The agent either returns with an error or with a OK followed by the hex encoded passphrase. Note that the length of the strings is implicitly limited by the maximum length of a command. If the option @option{--data} is used, the passphrase is not returned on the OK line but by regular data lines; this is the preferred method. If the option @option{--check} is used, the standard passphrase constraints checks are applied. A check is not done if the passphrase has been found in the cache. If the option @option{--no-ask} is used and the passphrase is not in the cache the user will not be asked to enter a passphrase but the error code @code{GPG_ERR_NO_DATA} is returned. If the option @option{--qualitybar} is used and a minimum passphrase length has been configured, a visual indication of the entered passphrase quality is shown. @example CLEAR_PASSPHRASE @var{cache_id} @end example may be used to invalidate the cache entry for a passphrase. The function returns with OK even when there is no cached passphrase. @node Agent CLEAR_PASSPHRASE @subsection Remove a cached passphrase Use this command to remove a cached passphrase. @example CLEAR_PASSPHRASE [--mode=normal] @end example The @option{--mode=normal} option can be used to clear a @var{cache_id} that was set by gpg-agent. @node Agent PRESET_PASSPHRASE @subsection Set a passphrase for a keygrip This command adds a passphrase to the cache for the specified @var{keygrip}. @example PRESET_PASSPHRASE [--inquire] [] @end example The passphrase is a hexadecimal string when specified. When not specified, the passphrase will be retrieved from the pinentry module unless the @option{--inquire} option was specified in which case the passphrase will be retrieved from the client. The @var{timeout} parameter keeps the passphrase cached for the specified number of seconds. A value of @code{-1} means infinite while @code{0} means the default (currently only a timeout of -1 is allowed, which means to never expire it). @node Agent GET_CONFIRMATION @subsection Ask for confirmation This command may be used to ask for a simple confirmation by presenting a text and 2 buttons: Okay and Cancel. @example GET_CONFIRMATION @var{description} @end example @var{description}is displayed along with a Okay and Cancel button. Blanks must be percent escaped or replaced by @code{+}. A @code{X} may be used to display confirmation dialog with a default text. The agent either returns with an error or with a OK. Note, that the length of @var{description} is implicitly limited by the maximum length of a command. @node Agent HAVEKEY @subsection Check whether a key is available This can be used to see whether a secret key is available. It does not return any information on whether the key is somehow protected. @example HAVEKEY @var{keygrips} @end example The agent answers either with OK or @code{No_Secret_Key} (208). The caller may want to check for other error codes as well. More than one keygrip may be given. In this case the command returns success if at least one of the keygrips corresponds to an available secret key. @node Agent LEARN @subsection Register a smartcard @example LEARN [--send] @end example This command is used to register a smartcard. With the @option{--send} option given the certificates are sent back. @node Agent PASSWD @subsection Change a Passphrase @example PASSWD [--cache-nonce=] [--passwd-nonce=] [--preset] @var{keygrip} @end example This command is used to interactively change the passphrase of the key identified by the hex string @var{keygrip}. The @option{--preset} option may be used to add the new passphrase to the cache using the default cache parameters. @node Agent UPDATESTARTUPTTY @subsection Change the standard display @example UPDATESTARTUPTTY @end example Set the startup TTY and X-DISPLAY variables to the values of this session. This command is useful to direct future pinentry invocations to another screen. It is only required because there is no way in the ssh-agent protocol to convey this information. @node Agent GETEVENTCOUNTER @subsection Get the Event Counters @example GETEVENTCOUNTER @end example This function return one status line with the current values of the event counters. The event counters are useful to avoid polling by delaying a poll until something has changed. The values are decimal numbers in the range @code{0} to @code{UINT_MAX} and wrapping around to 0. The actual values should not be relied upon; they shall only be used to detect a change. The currently defined counters are: @table @code @item ANY Incremented with any change of any of the other counters. @item KEY Incremented for added or removed private keys. @item CARD Incremented for each change of the card reader's status. @end table @node Agent GETINFO @subsection Return information about the process This is a multipurpose function to return a variety of information. @example GETINFO @var{what} @end example The value of @var{what} specifies the kind of information returned: @table @code @item version Return the version of the program. @item pid Return the process id of the process. @item socket_name Return the name of the socket used to connect the agent. @item ssh_socket_name Return the name of the socket used for SSH connections. If SSH support has not been enabled the error @code{GPG_ERR_NO_DATA} will be returned. @end table @node Agent OPTION @subsection Set options for the session Here is a list of session options which are not yet described with other commands. The general syntax for an Assuan option is: @smallexample OPTION @var{key}=@var{value} @end smallexample @noindent Supported @var{key}s are: @table @code @item agent-awareness This may be used to tell gpg-agent of which gpg-agent version the client is aware of. gpg-agent uses this information to enable features which might break older clients. @item putenv Change the session's environment to be used for the Pinentry. Valid values are: @table @code @item @var{name} Delete envvar @var{name} @item @var{name}= Set envvar @var{name} to the empty string @item @var{name}=@var{value} Set envvar @var{name} to the string @var{value}. @end table @item use-cache-for-signing See Assuan command @code{PKSIGN}. @item allow-pinentry-notify This does not need any value. It is used to enable the PINENTRY_LAUNCHED inquiry. @item pinentry-mode This option is used to change the operation mode of the pinentry. The following values are defined: @table @code @item ask This is the default mode which pops up a pinentry as needed. @item cancel Instead of popping up a pinentry, return the error code @code{GPG_ERR_CANCELED}. @item error Instead of popping up a pinentry, return the error code @code{GPG_ERR_NO_PIN_ENTRY}. @item loopback Use a loopback pinentry. This fakes a pinentry by using inquiries back to the caller to ask for a passphrase. This option may only be set if the agent has been configured for that. To disable this feature use @ref{option --no-allow-loopback-pinentry}. @end table @item cache-ttl-opt-preset This option sets the cache TTL for new entries created by GENKEY and PASSWD commands when using the @option{--preset} option. It is not used a default value is used. @item s2k-count Instead of using the standard S2K count (which is computed on the fly), the given S2K count is used for new keys or when changing the passphrase of a key. Values below 65536 are considered to be 0. This option is valid for the entire session or until reset to 0. This option is useful if the key is later used on boxes which are either much slower or faster than the actual box. @item pretend-request-origin This option switches the connection into a restricted mode which handles all further commands in the same way as they would be handled when originating from the extra or browser socket. Note that this option is not available in the restricted mode. Valid values for this option are: @table @code @item none @itemx local This is a NOP and leaves the connection in the standard way. @item remote Pretend to come from a remote origin in the same way as connections from the @option{--extra-socket}. @item browser Pretend to come from a local web browser in the same way as connections from the @option{--browser-socket}. @end table @end table @mansect see also @ifset isman @command{@gpgname}(1), @command{gpgsm}(1), @command{gpgconf}(1), @command{gpg-connect-agent}(1), @command{scdaemon}(1) @end ifset @include see-also-note.texi