diff --git a/agent/agent.h b/agent/agent.h index c713944d8..1cbc432af 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -1,648 +1,647 @@ /* 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 "../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 /* 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 smartcard tasks. */ const char *scdaemon_program; int disable_scdaemon; /* Never use the SCdaemon. */ 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; /* 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. */ const char *check_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 scd_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 SCdaemon (call-scd.c). */ struct scd_local_s *scd_local; /* 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 { int algo; unsigned char value[MAX_DIGEST_LEN]; int valuelen; int raw_value: 1; } 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 }; /* 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. */ 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_scd_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); 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_is_dsa_key (gcry_sexp_t s_key); int agent_is_eddsa_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); 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); 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 --*/ int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, const unsigned char *ciphertext, size_t ciphertextlen, membuf_t *outbuf, int *r_padding); /*-- genkey.c --*/ int check_passphrase_constraints (ctrl_t ctrl, const char *pw, 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, 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); gpg_error_t agent_get_shadow_info (const unsigned char *shadowkey, unsigned char const **shadow_info); 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-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-scd.c --*/ void initialize_module_call_scd (void); void agent_scd_dump_state (void); int agent_scd_check_running (void); int agent_reset_scd (ctrl_t ctrl); 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); void agent_card_killscd (void); /*-- 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 --*/ -size_t fixup_when_ecc_private_key (unsigned char *buf, size_t buflen); - +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/findkey.c b/agent/findkey.c index be5e1ea95..0fe7b4621 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -1,1742 +1,1742 @@ /* findkey.c - Locate the secret key * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, * 2010, 2011 Free Software Foundation, Inc. * Copyright (C) 2014, 2019 Werner Koch * * 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 #include #include /* (we use pth_sleep) */ #include "agent.h" #include "../common/i18n.h" #include "../common/ssh-utils.h" #include "../common/name-value.h" #ifndef O_BINARY #define O_BINARY 0 #endif /* Helper to pass data to the check callback of the unprotect function. */ struct try_unprotect_arg_s { ctrl_t ctrl; const unsigned char *protected_key; unsigned char *unprotected_key; int change_required; /* Set by the callback to indicate that the user should change the passphrase. */ }; /* Repalce all linefeeds in STRING by "%0A" and return a new malloced * string. May return NULL on memory error. */ static char * linefeed_to_percent0A (const char *string) { const char *s; size_t n; char *buf, *p; for (n=0, s=string; *s; s++) if (*s == '\n') n += 3; else n++; p = buf = xtrymalloc (n+1); if (!buf) return NULL; for (s=string; *s; s++) if (*s == '\n') { memcpy (p, "%0A", 3); p += 3; } else *p++ = *s; *p = 0; return buf; } /* Note: Ownership of FNAME and FP are moved to this function. */ static gpg_error_t write_extended_private_key (char *fname, estream_t fp, int update, const void *buf, size_t len, const char *serialno, const char *keyref) { gpg_error_t err; nvc_t pk = NULL; gcry_sexp_t key = NULL; int remove = 0; char *token = NULL; if (update) { int line; err = nvc_parse_private_key (&pk, &line, fp); if (err && gpg_err_code (err) != GPG_ERR_ENOENT) { log_error ("error parsing '%s' line %d: %s\n", fname, line, gpg_strerror (err)); goto leave; } } else { pk = nvc_new_private_key (); if (!pk) { err = gpg_error_from_syserror (); goto leave; } } es_clearerr (fp); err = gcry_sexp_sscan (&key, NULL, buf, len); if (err) goto leave; err = nvc_set_private_key (pk, key); if (err) goto leave; /* If requested write a Token line. */ if (serialno && keyref) { nve_t item; const char *s; token = strconcat (serialno, " ", keyref, NULL); if (!token) { err = gpg_error_from_syserror (); goto leave; } /* fixme: the strcmp should compare only the first two strings. */ for (item = nvc_lookup (pk, "Token:"); item; item = nve_next_value (item, "Token:")) if ((s = nve_value (item)) && !strcmp (s, token)) break; if (!item) { /* No token or no token with that value exists. Add a new * one so that keys which have been stored on several cards * are well supported. */ err = nvc_add (pk, "Token:", token); if (err) goto leave; } } err = es_fseek (fp, 0, SEEK_SET); if (err) goto leave; err = nvc_write (pk, fp); if (err) { log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); remove = 1; goto leave; } if (ftruncate (es_fileno (fp), es_ftello (fp))) { err = gpg_error_from_syserror (); log_error ("error truncating '%s': %s\n", fname, gpg_strerror (err)); remove = 1; goto leave; } if (es_fclose (fp)) { err = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); remove = 1; goto leave; } else fp = NULL; bump_key_eventcounter (); leave: es_fclose (fp); if (remove) gnupg_remove (fname); xfree (fname); gcry_sexp_release (key); nvc_release (pk); xfree (token); return err; } /* Write an S-expression formatted key to our key storage. With FORCE * passed as true an existing key with the given GRIP will get * overwritten. If SERIALNO and KEYREF are given a Token line is added to * the key if the extended format is used. */ int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force, const char *serialno, const char *keyref) { char *fname; estream_t fp; char hexgrip[40+4+1]; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); /* FIXME: Write to a temp file first so that write failures during key updates won't lead to a key loss. */ if (!force && !access (fname, F_OK)) { log_error ("secret key file '%s' already exists\n", fname); xfree (fname); return gpg_error (GPG_ERR_EEXIST); } fp = es_fopen (fname, force? "rb+,mode=-rw" : "wbx,mode=-rw"); if (!fp) { gpg_error_t tmperr = gpg_error_from_syserror (); if (force && gpg_err_code (tmperr) == GPG_ERR_ENOENT) { fp = es_fopen (fname, "wbx,mode=-rw"); if (!fp) tmperr = gpg_error_from_syserror (); } if (!fp) { log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr)); xfree (fname); return tmperr; } } else if (force) { gpg_error_t rc; char first; /* See if an existing key is in extended format. */ if (es_fread (&first, 1, 1, fp) != 1) { rc = gpg_error_from_syserror (); log_error ("error reading first byte from '%s': %s\n", fname, strerror (errno)); xfree (fname); es_fclose (fp); return rc; } rc = es_fseek (fp, 0, SEEK_SET); if (rc) { log_error ("error seeking in '%s': %s\n", fname, strerror (errno)); xfree (fname); es_fclose (fp); return rc; } if (first != '(') { /* Key is already in the extended format. */ return write_extended_private_key (fname, fp, 1, buffer, length, serialno, keyref); } if (first == '(' && opt.enable_extended_key_format) { /* Key is in the old format - but we want the extended format. */ return write_extended_private_key (fname, fp, 0, buffer, length, serialno, keyref); } } if (opt.enable_extended_key_format) return write_extended_private_key (fname, fp, 0, buffer, length, serialno, keyref); if (es_fwrite (buffer, length, 1, fp) != 1) { gpg_error_t tmperr = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", fname, gpg_strerror (tmperr)); es_fclose (fp); gnupg_remove (fname); xfree (fname); return tmperr; } /* When force is given, the file might have to be truncated. */ if (force && ftruncate (es_fileno (fp), es_ftello (fp))) { gpg_error_t tmperr = gpg_error_from_syserror (); log_error ("error truncating '%s': %s\n", fname, gpg_strerror (tmperr)); es_fclose (fp); gnupg_remove (fname); xfree (fname); return tmperr; } if (es_fclose (fp)) { gpg_error_t tmperr = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", fname, gpg_strerror (tmperr)); gnupg_remove (fname); xfree (fname); return tmperr; } bump_key_eventcounter (); xfree (fname); return 0; } /* Callback function to try the unprotection from the passphrase query code. */ static gpg_error_t try_unprotect_cb (struct pin_entry_info_s *pi) { struct try_unprotect_arg_s *arg = pi->check_cb_arg; ctrl_t ctrl = arg->ctrl; size_t dummy; gpg_error_t err; gnupg_isotime_t now, protected_at, tmptime; char *desc = NULL; log_assert (!arg->unprotected_key); arg->change_required = 0; err = agent_unprotect (ctrl, arg->protected_key, pi->pin, protected_at, &arg->unprotected_key, &dummy); if (err) return err; if (!opt.max_passphrase_days || ctrl->in_passwd) return 0; /* No regular passphrase change required. */ if (!*protected_at) { /* No protection date known - must force passphrase change. */ desc = xtrystrdup (L_("Note: This passphrase has never been changed.%0A" "Please change it now.")); if (!desc) return gpg_error_from_syserror (); } else { gnupg_get_isotime (now); gnupg_copy_time (tmptime, protected_at); err = add_days_to_isotime (tmptime, opt.max_passphrase_days); if (err) return err; if (strcmp (now, tmptime) > 0 ) { /* Passphrase "expired". */ desc = xtryasprintf (L_("This passphrase has not been changed%%0A" "since %.4s-%.2s-%.2s. Please change it now."), protected_at, protected_at+4, protected_at+6); if (!desc) return gpg_error_from_syserror (); } } if (desc) { /* Change required. */ if (opt.enforce_passphrase_constraints) { err = agent_get_confirmation (ctrl, desc, L_("Change passphrase"), NULL, 0); if (!err) arg->change_required = 1; } else { err = agent_get_confirmation (ctrl, desc, L_("Change passphrase"), L_("I'll change it later"), 0); if (!err) arg->change_required = 1; else if (gpg_err_code (err) == GPG_ERR_CANCELED || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) err = 0; } xfree (desc); } return err; } /* Return true if the STRING has an %C or %c expando. */ static int has_comment_expando (const char *string) { const char *s; int percent = 0; if (!string) return 0; for (s = string; *s; s++) { if (percent) { if (*s == 'c' || *s == 'C') return 1; percent = 0; } else if (*s == '%') percent = 1; } return 0; } /* Modify a Key description, replacing certain special format characters. List of currently supported replacements: %% - Replaced by a single % %c - Replaced by the content of COMMENT. %C - Same as %c but put into parentheses. %F - Replaced by an ssh style fingerprint computed from KEY. The functions returns 0 on success or an error code. On success a newly allocated string is stored at the address of RESULT. */ gpg_error_t agent_modify_description (const char *in, const char *comment, const gcry_sexp_t key, char **result) { size_t comment_length; size_t in_len; size_t out_len; char *out; size_t i; int special, pass; char *ssh_fpr = NULL; char *p; *result = NULL; if (!comment) comment = ""; comment_length = strlen (comment); in_len = strlen (in); /* First pass calculates the length, second pass does the actual copying. */ /* FIXME: This can be simplified by using es_fopenmem. */ out = NULL; out_len = 0; for (pass=0; pass < 2; pass++) { special = 0; for (i = 0; i < in_len; i++) { if (special) { special = 0; switch (in[i]) { case '%': if (out) *out++ = '%'; else out_len++; break; case 'c': /* Comment. */ if (out) { memcpy (out, comment, comment_length); out += comment_length; } else out_len += comment_length; break; case 'C': /* Comment. */ if (!comment_length) ; else if (out) { *out++ = '('; memcpy (out, comment, comment_length); out += comment_length; *out++ = ')'; } else out_len += comment_length + 2; break; case 'F': /* SSH style fingerprint. */ if (!ssh_fpr && key) ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &ssh_fpr); if (ssh_fpr) { if (out) out = stpcpy (out, ssh_fpr); else out_len += strlen (ssh_fpr); } break; default: /* Invalid special sequences are kept as they are. */ if (out) { *out++ = '%'; *out++ = in[i]; } else out_len+=2; break; } } else if (in[i] == '%') special = 1; else { if (out) *out++ = in[i]; else out_len++; } } if (!pass) { *result = out = xtrymalloc (out_len + 1); if (!out) { xfree (ssh_fpr); return gpg_error_from_syserror (); } } } *out = 0; log_assert (*result + out_len == out); xfree (ssh_fpr); /* The ssh prompt may sometimes end in * "...%0A ()" * The empty parentheses doesn't look very good. We use this hack * here to remove them as well as the indentation spaces. */ p = *result; i = strlen (p); if (i > 2 && !strcmp (p + i - 2, "()")) { p += i - 2; *p-- = 0; while (p > *result && spacep (p)) *p-- = 0; } return 0; } /* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP should be the hex encoded keygrip of that key to be used with the caching mechanism. DESC_TEXT may be set to override the default description used for the pinentry. If LOOKUP_TTL is given this function is used to lookup the default ttl. If R_PASSPHRASE is not NULL, the function succeeded and the key was protected the used passphrase (entered or from the cache) is stored there; if not NULL will be stored. The caller needs to free the returned passphrase. */ static gpg_error_t unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, unsigned char **keybuf, const unsigned char *grip, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, char **r_passphrase) { struct pin_entry_info_s *pi; struct try_unprotect_arg_s arg; int rc; unsigned char *result; size_t resultlen; char hexgrip[40+1]; if (r_passphrase) *r_passphrase = NULL; bin2hex (grip, 20, hexgrip); /* Initially try to get it using a cache nonce. */ if (cache_nonce) { char *pw; pw = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE); if (pw) { rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen); if (!rc) { if (r_passphrase) *r_passphrase = pw; else xfree (pw); xfree (*keybuf); *keybuf = result; return 0; } xfree (pw); } } /* First try to get it from the cache - if there is none or we can't unprotect it, we fall back to ask the user */ if (cache_mode != CACHE_MODE_IGNORE) { char *pw; retry: pw = agent_get_cache (ctrl, hexgrip, cache_mode); if (pw) { rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen); if (!rc) { if (cache_mode == CACHE_MODE_NORMAL) agent_store_cache_hit (hexgrip); if (r_passphrase) *r_passphrase = pw; else xfree (pw); xfree (*keybuf); *keybuf = result; return 0; } xfree (pw); } else if (cache_mode == CACHE_MODE_NORMAL) { /* The standard use of GPG keys is to have a signing and an encryption subkey. Commonly both use the same passphrase. We try to help the user to enter the passphrase only once by silently trying the last correctly entered passphrase. Checking one additional passphrase should be acceptable; despite the S2K introduced delays. The assumed workflow is: 1. Read encrypted message in a MUA and thus enter a passphrase for the encryption subkey. 2. Reply to that mail with an encrypted and signed mail, thus entering the passphrase for the signing subkey. We can often avoid the passphrase entry in the second step. We do this only in normal mode, so not to interfere with unrelated cache entries. */ pw = agent_get_cache (ctrl, NULL, cache_mode); if (pw) { rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen); if (!rc) { if (r_passphrase) *r_passphrase = pw; else xfree (pw); xfree (*keybuf); *keybuf = result; return 0; } xfree (pw); } } /* If the pinentry is currently in use, we wait up to 60 seconds for it to close and check the cache again. This solves a common situation where several requests for unprotecting a key have been made but the user is still entering the passphrase for the first request. Because all requests to agent_askpin are serialized they would then pop up one after the other to request the passphrase - despite that the user has already entered it and is then available in the cache. This implementation is not race free but in the worst case the user has to enter the passphrase only once more. */ if (pinentry_active_p (ctrl, 0)) { /* Active - wait */ if (!pinentry_active_p (ctrl, 60)) { /* We need to give the other thread a chance to actually put it into the cache. */ npth_sleep (1); goto retry; } /* Timeout - better call pinentry now the plain way. */ } } pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); if (!pi) return gpg_error_from_syserror (); pi->max_length = MAX_PASSPHRASE_LEN + 1; pi->min_digits = 0; /* we want a real passphrase */ pi->max_digits = 16; pi->max_tries = 3; pi->check_cb = try_unprotect_cb; arg.ctrl = ctrl; arg.protected_key = *keybuf; arg.unprotected_key = NULL; arg.change_required = 0; pi->check_cb_arg = &arg; rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, hexgrip, cache_mode); if (rc) { if ((pi->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) { log_error ("Clearing pinentry cache which caused error %s\n", gpg_strerror (rc)); agent_clear_passphrase (ctrl, hexgrip, cache_mode); } } else { log_assert (arg.unprotected_key); if (arg.change_required) { /* The callback told as that the user should change their passphrase. Present the dialog to do. */ size_t canlen, erroff; gcry_sexp_t s_skey; log_assert (arg.unprotected_key); canlen = gcry_sexp_canon_len (arg.unprotected_key, 0, NULL, NULL); rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)arg.unprotected_key, canlen); if (rc) { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); wipememory (arg.unprotected_key, canlen); xfree (arg.unprotected_key); xfree (pi); return rc; } rc = agent_protect_and_store (ctrl, s_skey, NULL); gcry_sexp_release (s_skey); if (rc) { log_error ("changing the passphrase failed: %s\n", gpg_strerror (rc)); wipememory (arg.unprotected_key, canlen); xfree (arg.unprotected_key); xfree (pi); return rc; } } else { /* Passphrase is fine. */ agent_put_cache (ctrl, hexgrip, cache_mode, pi->pin, lookup_ttl? lookup_ttl (hexgrip) : 0); agent_store_cache_hit (hexgrip); if (r_passphrase && *pi->pin) *r_passphrase = xtrystrdup (pi->pin); } xfree (*keybuf); *keybuf = arg.unprotected_key; } xfree (pi); return rc; } /* Read the key identified by GRIP from the private key directory and * return it as an gcrypt S-expression object in RESULT. If R_KEYMETA * is not NULl and the extended key format is used, the meta data * items are stored there. However the "Key:" item is removed from * it. On failure returns an error code and stores NULL at RESULT and * R_KEYMETA. */ static gpg_error_t read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta) { gpg_error_t err; char *fname; estream_t fp; struct stat st; unsigned char *buf; size_t buflen, erroff; gcry_sexp_t s_skey; char hexgrip[40+4+1]; char first; *result = NULL; if (r_keymeta) *r_keymeta = NULL; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); if (gpg_err_code (err) != GPG_ERR_ENOENT) log_error ("can't open '%s': %s\n", fname, gpg_strerror (err)); xfree (fname); return err; } if (es_fread (&first, 1, 1, fp) != 1) { err = gpg_error_from_syserror (); log_error ("error reading first byte from '%s': %s\n", fname, gpg_strerror (err)); xfree (fname); es_fclose (fp); return err; } if (es_fseek (fp, 0, SEEK_SET)) { err = gpg_error_from_syserror (); log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err)); xfree (fname); es_fclose (fp); return err; } if (first != '(') { /* Key is in extended format. */ nvc_t pk = NULL; int line; err = nvc_parse_private_key (&pk, &line, fp); es_fclose (fp); if (err) log_error ("error parsing '%s' line %d: %s\n", fname, line, gpg_strerror (err)); else { err = nvc_get_private_key (pk, result); if (err) log_error ("error getting private key from '%s': %s\n", fname, gpg_strerror (err)); else nvc_delete_named (pk, "Key:"); } if (!err && r_keymeta) *r_keymeta = pk; else nvc_release (pk); xfree (fname); return err; } if (fstat (es_fileno (fp), &st)) { err = gpg_error_from_syserror (); log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err)); xfree (fname); es_fclose (fp); return err; } buflen = st.st_size; buf = xtrymalloc (buflen+1); if (!buf) { err = gpg_error_from_syserror (); log_error ("error allocating %zu bytes for '%s': %s\n", buflen, fname, gpg_strerror (err)); xfree (fname); es_fclose (fp); xfree (buf); return err; } if (es_fread (buf, buflen, 1, fp) != 1) { err = gpg_error_from_syserror (); log_error ("error reading %zu bytes from '%s': %s\n", buflen, fname, gpg_strerror (err)); xfree (fname); es_fclose (fp); xfree (buf); return err; } /* Convert the file into a gcrypt S-expression object. */ err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); xfree (fname); es_fclose (fp); xfree (buf); if (err) { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (err)); return err; } *result = s_skey; return 0; } /* Remove the key identified by GRIP from the private key directory. */ static gpg_error_t remove_key_file (const unsigned char *grip) { gpg_error_t err = 0; char *fname; char hexgrip[40+4+1]; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); if (gnupg_remove (fname)) err = gpg_error_from_syserror (); xfree (fname); return err; } /* Return the secret key as an S-Exp in RESULT after locating it using the GRIP. If the operation shall be diverted to a token, an allocated S-expression with the shadow_info part from the file is stored at SHADOW_INFO; if not NULL will be stored at SHADOW_INFO. CACHE_MODE defines now the cache shall be used. DESC_TEXT may be set to present a custom description for the pinentry. LOOKUP_TTL is an optional function to convey a TTL to the cache manager; we do not simply pass the TTL value because the value is only needed if an unprotect action was needed and looking up the TTL may have some overhead (e.g. scanning the sshcontrol file). If a CACHE_NONCE is given that cache item is first tried to get a passphrase. If R_PASSPHRASE is not NULL, the function succeeded and the key was protected the used passphrase (entered or from the cache) is stored there; if not NULL will be stored. The caller needs to free the returned passphrase. */ 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 err; unsigned char *buf; - size_t len, buflen, erroff; + size_t len, erroff; gcry_sexp_t s_skey; nvc_t keymeta = NULL; char *desc_text_buffer = NULL; /* Used in case we extend DESC_TEXT. */ *result = NULL; if (shadow_info) *shadow_info = NULL; if (r_passphrase) *r_passphrase = NULL; err = read_key_file (grip, &s_skey, &keymeta); if (err) { if (gpg_err_code (err) == GPG_ERR_ENOENT) err = gpg_error (GPG_ERR_NO_SECKEY); return err; } /* For use with the protection functions we also need the key as an canonical encoded S-expression in a buffer. Create this buffer now. */ err = make_canon_sexp (s_skey, &buf, &len); if (err) { nvc_release (keymeta); xfree (desc_text_buffer); return err; } switch (agent_private_key_type (buf)) { case PRIVATE_KEY_CLEAR: break; /* no unprotection needed */ case PRIVATE_KEY_OPENPGP_NONE: { unsigned char *buf_new; size_t buf_newlen; err = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen); if (err) log_error ("failed to convert unprotected openpgp key: %s\n", gpg_strerror (err)); else { xfree (buf); buf = buf_new; } } break; case PRIVATE_KEY_PROTECTED: { char *desc_text_final; char *comment_buffer = NULL; const char *comment = NULL; /* Note, that we will take the comment as a C string for * display purposes; i.e. all stuff beyond a Nul character is * ignored. If a "Label" entry is available in the meta data * this is used instead of the s-ecpression comment. */ if (keymeta && (comment = nvc_get_string (keymeta, "Label:"))) { if (strchr (comment, '\n') && (comment_buffer = linefeed_to_percent0A (comment))) comment = comment_buffer; /* In case DESC_TEXT has no escape pattern for a comment * we append one. */ if (desc_text && !has_comment_expando (desc_text)) { desc_text_buffer = strconcat (desc_text, "%0A%C", NULL); if (desc_text_buffer) desc_text = desc_text_buffer; } } else { gcry_sexp_t comment_sexp; comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); if (comment_sexp) comment_buffer = gcry_sexp_nth_string (comment_sexp, 1); gcry_sexp_release (comment_sexp); comment = comment_buffer; } desc_text_final = NULL; if (desc_text) err = agent_modify_description (desc_text, comment, s_skey, &desc_text_final); gcry_free (comment_buffer); if (!err) { err = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip, cache_mode, lookup_ttl, r_passphrase); if (err) log_error ("failed to unprotect the secret key: %s\n", gpg_strerror (err)); } xfree (desc_text_final); } break; case PRIVATE_KEY_SHADOWED: if (shadow_info) { const unsigned char *s; size_t n; err = agent_get_shadow_info (buf, &s); if (!err) { n = gcry_sexp_canon_len (s, 0, NULL,NULL); log_assert (n); *shadow_info = xtrymalloc (n); if (!*shadow_info) err = out_of_core (); else { memcpy (*shadow_info, s, n); err = 0; } } if (err) log_error ("get_shadow_info failed: %s\n", gpg_strerror (err)); } else err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); break; default: log_error ("invalid private key format\n"); err = gpg_error (GPG_ERR_BAD_SECKEY); break; } gcry_sexp_release (s_skey); s_skey = NULL; if (err) { xfree (buf); if (r_passphrase) { xfree (*r_passphrase); *r_passphrase = NULL; } nvc_release (keymeta); xfree (desc_text_buffer); return err; } err = sexp_sscan_private_key (result, &erroff, buf); xfree (buf); nvc_release (keymeta); xfree (desc_text_buffer); if (err) { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (err)); if (r_passphrase) { xfree (*r_passphrase); *r_passphrase = NULL; } } return err; } /* Return the string name from the S-expression S_KEY as well as a string describing the names of the parameters. ALGONAMESIZE and ELEMSSIZE give the allocated size of the provided buffers. The buffers may be NULL if not required. If R_LIST is not NULL the top level list will be stored there; the caller needs to release it in this case. */ static gpg_error_t key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list, char *r_algoname, size_t algonamesize, char *r_elems, size_t elemssize) { gcry_sexp_t list, l2; const char *name, *algoname, *elems; size_t n; if (r_list) *r_list = NULL; list = gcry_sexp_find_token (s_key, "shadowed-private-key", 0 ); if (!list) list = gcry_sexp_find_token (s_key, "protected-private-key", 0 ); if (!list) list = gcry_sexp_find_token (s_key, "private-key", 0 ); if (!list) list = gcry_sexp_find_token (s_key, "public-key", 0 ); if (!list) { log_error ("invalid private key format\n"); return gpg_error (GPG_ERR_BAD_SECKEY); } l2 = gcry_sexp_cadr (list); gcry_sexp_release (list); list = l2; name = gcry_sexp_nth_data (list, 0, &n); if (n==3 && !memcmp (name, "rsa", 3)) { algoname = "rsa"; elems = "ne"; } else if (n==3 && !memcmp (name, "dsa", 3)) { algoname = "dsa"; elems = "pqgy"; } else if (n==3 && !memcmp (name, "ecc", 3)) { algoname = "ecc"; elems = "pabgnq"; } else if (n==5 && !memcmp (name, "ecdsa", 5)) { algoname = "ecdsa"; elems = "pabgnq"; } else if (n==4 && !memcmp (name, "ecdh", 4)) { algoname = "ecdh"; elems = "pabgnq"; } else if (n==3 && !memcmp (name, "elg", 3)) { algoname = "elg"; elems = "pgy"; } else { log_error ("unknown private key algorithm\n"); gcry_sexp_release (list); return gpg_error (GPG_ERR_BAD_SECKEY); } if (r_algoname) { if (strlen (algoname) >= algonamesize) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); strcpy (r_algoname, algoname); } if (r_elems) { if (strlen (elems) >= elemssize) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); strcpy (r_elems, elems); } if (r_list) *r_list = list; else gcry_sexp_release (list); return 0; } /* Return true if KEYPARMS holds an EdDSA key. */ static int is_eddsa (gcry_sexp_t keyparms) { int result = 0; gcry_sexp_t list; const char *s; size_t n; int i; list = gcry_sexp_find_token (keyparms, "flags", 0); for (i = list ? gcry_sexp_length (list)-1 : 0; i > 0; i--) { s = gcry_sexp_nth_data (list, i, &n); if (!s) continue; /* Not a data element. */ if (n == 5 && !memcmp (s, "eddsa", 5)) { result = 1; break; } } gcry_sexp_release (list); return result; } /* Return the public key algorithm number if S_KEY is a DSA style key. If it is not a DSA style key, return 0. */ int agent_is_dsa_key (gcry_sexp_t s_key) { int result; gcry_sexp_t list; char algoname[6]; if (!s_key) return 0; if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0)) return 0; /* Error - assume it is not an DSA key. */ if (!strcmp (algoname, "dsa")) result = GCRY_PK_DSA; else if (!strcmp (algoname, "ecc")) { if (is_eddsa (list)) result = 0; else result = GCRY_PK_ECDSA; } else if (!strcmp (algoname, "ecdsa")) result = GCRY_PK_ECDSA; else result = 0; gcry_sexp_release (list); return result; } /* Return true if S_KEY is an EdDSA key as used with curve Ed25519. */ int agent_is_eddsa_key (gcry_sexp_t s_key) { int result; gcry_sexp_t list; char algoname[6]; if (!s_key) return 0; if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0)) return 0; /* Error - assume it is not an EdDSA key. */ if (!strcmp (algoname, "ecc") && is_eddsa (list)) result = 1; else if (!strcmp (algoname, "eddsa")) /* backward compatibility. */ result = 1; else result = 0; gcry_sexp_release (list); return result; } /* Return the key for the keygrip GRIP. The result is stored at RESULT. This function extracts the key from the private key database and returns it as an S-expression object as it is. On failure an error code is returned and NULL stored at RESULT. */ gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result) { gpg_error_t err; gcry_sexp_t s_skey; (void)ctrl; *result = NULL; err = read_key_file (grip, &s_skey, NULL); if (!err) *result = s_skey; return err; } /* Return the public key for the keygrip GRIP. The result is stored at RESULT. This function extracts the public key from the private key database. On failure an error code is returned and NULL stored at RESULT. */ gpg_error_t agent_public_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result) { gpg_error_t err; int i, idx; gcry_sexp_t s_skey; const char *algoname, *elems; int npkey; gcry_mpi_t array[10]; gcry_sexp_t curve = NULL; gcry_sexp_t flags = NULL; gcry_sexp_t uri_sexp, comment_sexp; const char *uri, *comment; size_t uri_length, comment_length; int uri_intlen, comment_intlen; char *format, *p; void *args[2+7+2+2+1]; /* Size is 2 + max. # of elements + 2 for uri + 2 for comment + end-of-list. */ int argidx; gcry_sexp_t list = NULL; const char *s; (void)ctrl; *result = NULL; err = read_key_file (grip, &s_skey, NULL); if (err) return err; for (i=0; i < DIM (array); i++) array[i] = NULL; err = extract_private_key (s_skey, 0, &algoname, &npkey, NULL, &elems, array, DIM (array), &curve, &flags); if (err) { gcry_sexp_release (s_skey); return err; } uri = NULL; uri_length = 0; uri_sexp = gcry_sexp_find_token (s_skey, "uri", 0); if (uri_sexp) uri = gcry_sexp_nth_data (uri_sexp, 1, &uri_length); comment = NULL; comment_length = 0; comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); if (comment_sexp) comment = gcry_sexp_nth_data (comment_sexp, 1, &comment_length); gcry_sexp_release (s_skey); s_skey = NULL; /* FIXME: The following thing is pretty ugly code; we should investigate how to make it cleaner. Probably code to handle canonical S-expressions in a memory buffer is better suited for such a task. After all that is what we do in protect.c. Need to find common patterns and write a straightformward API to use them. */ log_assert (sizeof (size_t) <= sizeof (void*)); format = xtrymalloc (15+4+7*npkey+10+15+1+1); if (!format) { err = gpg_error_from_syserror (); for (i=0; array[i]; i++) gcry_mpi_release (array[i]); gcry_sexp_release (curve); gcry_sexp_release (flags); gcry_sexp_release (uri_sexp); gcry_sexp_release (comment_sexp); return err; } argidx = 0; p = stpcpy (stpcpy (format, "(public-key("), algoname); p = stpcpy (p, "%S%S"); /* curve name and flags. */ args[argidx++] = &curve; args[argidx++] = &flags; for (idx=0, s=elems; idx < npkey; idx++) { *p++ = '('; *p++ = *s++; p = stpcpy (p, " %M)"); log_assert (argidx < DIM (args)); args[argidx++] = &array[idx]; } *p++ = ')'; if (uri) { p = stpcpy (p, "(uri %b)"); log_assert (argidx+1 < DIM (args)); uri_intlen = (int)uri_length; args[argidx++] = (void *)&uri_intlen; args[argidx++] = (void *)&uri; } if (comment) { p = stpcpy (p, "(comment %b)"); log_assert (argidx+1 < DIM (args)); comment_intlen = (int)comment_length; args[argidx++] = (void *)&comment_intlen; args[argidx++] = (void*)&comment; } *p++ = ')'; *p = 0; log_assert (argidx < DIM (args)); args[argidx] = NULL; err = gcry_sexp_build_array (&list, NULL, format, args); xfree (format); for (i=0; array[i]; i++) gcry_mpi_release (array[i]); gcry_sexp_release (curve); gcry_sexp_release (flags); gcry_sexp_release (uri_sexp); gcry_sexp_release (comment_sexp); if (!err) *result = list; return err; } /* Check whether the secret key identified by GRIP is available. Returns 0 is the key is available. */ int agent_key_available (const unsigned char *grip) { int result; char *fname; char hexgrip[40+4+1]; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); result = !access (fname, R_OK)? 0 : -1; xfree (fname); return result; } /* Return the information about the secret key specified by the binary keygrip GRIP. If the key is a shadowed one the shadow information will be stored at the address R_SHADOW_INFO as an allocated S-expression. */ gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, int *r_keytype, unsigned char **r_shadow_info) { gpg_error_t err; unsigned char *buf; size_t len; int keytype; (void)ctrl; if (r_keytype) *r_keytype = PRIVATE_KEY_UNKNOWN; if (r_shadow_info) *r_shadow_info = NULL; { gcry_sexp_t sexp; err = read_key_file (grip, &sexp, NULL); if (err) { if (gpg_err_code (err) == GPG_ERR_ENOENT) return gpg_error (GPG_ERR_NOT_FOUND); else return err; } err = make_canon_sexp (sexp, &buf, &len); gcry_sexp_release (sexp); if (err) return err; } keytype = agent_private_key_type (buf); switch (keytype) { case PRIVATE_KEY_CLEAR: case PRIVATE_KEY_OPENPGP_NONE: break; case PRIVATE_KEY_PROTECTED: /* If we ever require it we could retrieve the comment fields from such a key. */ break; case PRIVATE_KEY_SHADOWED: if (r_shadow_info) { const unsigned char *s; size_t n; err = agent_get_shadow_info (buf, &s); if (!err) { n = gcry_sexp_canon_len (s, 0, NULL, NULL); log_assert (n); *r_shadow_info = xtrymalloc (n); if (!*r_shadow_info) err = gpg_error_from_syserror (); else memcpy (*r_shadow_info, s, n); } } break; default: err = gpg_error (GPG_ERR_BAD_SECKEY); break; } if (!err && r_keytype) *r_keytype = keytype; xfree (buf); return err; } /* Delete the key with GRIP from the disk after having asked for * confirmation using DESC_TEXT. If FORCE is set the function won't * require a confirmation via Pinentry or warns if the key is also * used by ssh. If ONLY_STUBS is set only stub keys (references to * smartcards) will be affected. * * Common error codes are: * GPG_ERR_NO_SECKEY * GPG_ERR_KEY_ON_CARD * GPG_ERR_NOT_CONFIRMED * GPG_ERR_FORBIDDEN - Not a stub key and ONLY_STUBS requested. */ gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, int force, int only_stubs) { gpg_error_t err; gcry_sexp_t s_skey = NULL; unsigned char *buf = NULL; size_t len; char *desc_text_final = NULL; char *comment = NULL; ssh_control_file_t cf = NULL; char hexgrip[40+4+1]; char *default_desc = NULL; int key_type; err = read_key_file (grip, &s_skey, NULL); if (gpg_err_code (err) == GPG_ERR_ENOENT) err = gpg_error (GPG_ERR_NO_SECKEY); if (err) goto leave; err = make_canon_sexp (s_skey, &buf, &len); if (err) goto leave; key_type = agent_private_key_type (buf); if (only_stubs && key_type != PRIVATE_KEY_SHADOWED) { err = gpg_error (GPG_ERR_FORBIDDEN); goto leave; } switch (key_type) { case PRIVATE_KEY_CLEAR: case PRIVATE_KEY_OPENPGP_NONE: case PRIVATE_KEY_PROTECTED: bin2hex (grip, 20, hexgrip); if (!force) { if (!desc_text) { default_desc = xtryasprintf (L_("Do you really want to delete the key identified by keygrip%%0A" " %s%%0A %%C%%0A?"), hexgrip); desc_text = default_desc; } /* Note, that we will take the comment as a C string for display purposes; i.e. all stuff beyond a Nul character is ignored. */ { gcry_sexp_t comment_sexp; comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); if (comment_sexp) comment = gcry_sexp_nth_string (comment_sexp, 1); gcry_sexp_release (comment_sexp); } if (desc_text) err = agent_modify_description (desc_text, comment, s_skey, &desc_text_final); if (err) goto leave; err = agent_get_confirmation (ctrl, desc_text_final, L_("Delete key"), L_("No"), 0); if (err) goto leave; cf = ssh_open_control_file (); if (cf) { if (!ssh_search_control_file (cf, hexgrip, NULL, NULL, NULL)) { err = agent_get_confirmation (ctrl, L_("Warning: This key is also listed for use with SSH!\n" "Deleting the key might remove your ability to " "access remote machines."), L_("Delete key"), L_("No"), 0); if (err) goto leave; } } } err = remove_key_file (grip); break; case PRIVATE_KEY_SHADOWED: err = remove_key_file (grip); break; default: log_error ("invalid private key format\n"); err = gpg_error (GPG_ERR_BAD_SECKEY); break; } leave: ssh_close_control_file (cf); gcry_free (comment); xfree (desc_text_final); xfree (default_desc); xfree (buf); gcry_sexp_release (s_skey); return err; } /* Write an S-expression formatted shadow key to our key storage. Shadow key is created by an S-expression public key in PKBUF and card's SERIALNO and the IDSTRING. With FORCE passed as true an existing key with the given GRIP will get overwritten. */ gpg_error_t agent_write_shadow_key (const unsigned char *grip, const char *serialno, const char *keyid, const unsigned char *pkbuf, int force) { gpg_error_t err; unsigned char *shadow_info; unsigned char *shdkey; size_t len; /* Just in case some caller did not parse the stuff correctly, skip * leading spaces. */ while (spacep (serialno)) serialno++; while (spacep (keyid)) keyid++; shadow_info = make_shadow_info (serialno, keyid); if (!shadow_info) return gpg_error_from_syserror (); err = agent_shadow_key (pkbuf, shadow_info, &shdkey); xfree (shadow_info); if (err) { log_error ("shadowing the key failed: %s\n", gpg_strerror (err)); return err; } len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); err = agent_write_private_key (grip, shdkey, len, force, serialno, keyid); xfree (shdkey); if (err) log_error ("error writing key: %s\n", gpg_strerror (err)); return err; } diff --git a/agent/protect-tool.c b/agent/protect-tool.c index 178a20849..a95f418e6 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -1,834 +1,839 @@ /* protect-tool.c - A tool to test the secret key protection * Copyright (C) 2002, 2003, 2004, 2006 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_LANGINFO_CODESET #include #endif #ifdef HAVE_DOSISH_SYSTEM #include /* for setmode() */ #endif #define INCLUDED_BY_MAIN_MODULE 1 #include "agent.h" #include "../common/i18n.h" #include "../common/get-passphrase.h" #include "../common/sysutils.h" #include "../common/init.h" enum cmd_and_opt_values { aNull = 0, oVerbose = 'v', oArmor = 'a', oPassphrase = 'P', oProtect = 'p', oUnprotect = 'u', oNoVerbose = 500, oShadow, oShowShadowInfo, oShowKeygrip, oS2Kcalibration, oCanonical, oStore, oForce, oHaveCert, oNoFailOnExist, oHomedir, oPrompt, oStatusMsg, oDebugUseOCB, oAgentProgram }; struct rsa_secret_key_s { gcry_mpi_t n; /* public modulus */ gcry_mpi_t e; /* public exponent */ gcry_mpi_t d; /* exponent */ gcry_mpi_t p; /* prime p. */ gcry_mpi_t q; /* prime q. */ gcry_mpi_t u; /* inverse of p mod q. */ }; static int opt_armor; static int opt_canonical; static int opt_store; static int opt_force; static int opt_no_fail_on_exist; static int opt_have_cert; static const char *opt_passphrase; static char *opt_prompt; static int opt_status_msg; static const char *opt_agent_program; static int opt_debug_use_ocb; static char *get_passphrase (int promptno); static void release_passphrase (char *pw); static gpgrt_opt_t opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (oProtect, "protect", "protect a private key"), ARGPARSE_c (oUnprotect, "unprotect", "unprotect a private key"), ARGPARSE_c (oShadow, "shadow", "create a shadow entry for a public key"), ARGPARSE_c (oShowShadowInfo, "show-shadow-info", "return the shadow info"), ARGPARSE_c (oShowKeygrip, "show-keygrip", "show the \"keygrip\""), ARGPARSE_c (oS2Kcalibration, "s2k-calibration", "@"), ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", "verbose"), ARGPARSE_s_n (oArmor, "armor", "write output in advanced format"), ARGPARSE_s_n (oCanonical, "canonical", "write output in canonical format"), ARGPARSE_s_s (oPassphrase, "passphrase", "|STRING|use passphrase STRING"), ARGPARSE_s_n (oHaveCert, "have-cert", "certificate to export provided on STDIN"), ARGPARSE_s_n (oStore, "store", "store the created key in the appropriate place"), ARGPARSE_s_n (oForce, "force", "force overwriting"), ARGPARSE_s_n (oNoFailOnExist, "no-fail-on-exist", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oPrompt, "prompt", "|ESCSTRING|use ESCSTRING as prompt in pinentry"), ARGPARSE_s_n (oStatusMsg, "enable-status-msg", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_n (oDebugUseOCB, "debug-use-ocb", "@"), /* For hacking only. */ ARGPARSE_end () }; static const char * my_strusage (int level) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "gpg-protect-tool (" GNUPG_NAME ")"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: gpg-protect-tool [options] (-h for help)\n"); break; case 41: p = _("Syntax: gpg-protect-tool [options] [args]\n" "Secret key maintenance tool\n"); break; default: p = NULL; } return p; } /* static void */ /* print_mpi (const char *text, gcry_mpi_t a) */ /* { */ /* char *buf; */ /* void *bufaddr = &buf; */ /* int rc; */ /* rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, bufaddr, NULL, a); */ /* if (rc) */ /* log_info ("%s: [error printing number: %s]\n", text, gpg_strerror (rc)); */ /* else */ /* { */ /* log_info ("%s: %s\n", text, buf); */ /* gcry_free (buf); */ /* } */ /* } */ static unsigned char * make_canonical (const char *fname, const char *buf, size_t buflen) { int rc; size_t erroff, len; gcry_sexp_t sexp; unsigned char *result; rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen); if (rc) { log_error ("invalid S-Expression in '%s' (off=%u): %s\n", fname, (unsigned int)erroff, gpg_strerror (rc)); return NULL; } len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0); log_assert (len); result = xmalloc (len); len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, result, len); log_assert (len); gcry_sexp_release (sexp); return result; } static char * make_advanced (const unsigned char *buf, size_t buflen) { int rc; size_t erroff, len; gcry_sexp_t sexp; char *result; rc = gcry_sexp_sscan (&sexp, &erroff, (const char*)buf, buflen); if (rc) { log_error ("invalid canonical S-Expression (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); return NULL; } len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0); log_assert (len); result = xmalloc (len); len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len); log_assert (len); gcry_sexp_release (sexp); return result; } static char * read_file (const char *fname, size_t *r_length) { FILE *fp; char *buf; size_t buflen; if (!strcmp (fname, "-")) { size_t nread, bufsize = 0; fp = stdin; #ifdef HAVE_DOSISH_SYSTEM setmode ( fileno(fp) , O_BINARY ); #endif buf = NULL; buflen = 0; #define NCHUNK 8192 do { bufsize += NCHUNK; if (!buf) buf = xmalloc (bufsize); else buf = xrealloc (buf, bufsize); nread = fread (buf+buflen, 1, NCHUNK, fp); if (nread < NCHUNK && ferror (fp)) { log_error ("error reading '[stdin]': %s\n", strerror (errno)); xfree (buf); return NULL; } buflen += nread; } while (nread == NCHUNK); #undef NCHUNK } else { struct stat st; fp = fopen (fname, "rb"); if (!fp) { log_error ("can't open '%s': %s\n", fname, strerror (errno)); return NULL; } if (fstat (fileno(fp), &st)) { log_error ("can't stat '%s': %s\n", fname, strerror (errno)); fclose (fp); return NULL; } buflen = st.st_size; buf = xmalloc (buflen+1); if (fread (buf, buflen, 1, fp) != 1) { log_error ("error reading '%s': %s\n", fname, strerror (errno)); fclose (fp); xfree (buf); return NULL; } fclose (fp); } *r_length = buflen; return buf; } static unsigned char * read_key (const char *fname) { char *buf; size_t buflen; unsigned char *key; buf = read_file (fname, &buflen); if (!buf) return NULL; if (buflen >= 4 && !memcmp (buf, "Key:", 4)) { log_error ("Extended key format is not supported by this tool\n"); return NULL; } key = make_canonical (fname, buf, buflen); xfree (buf); return key; } static void read_and_protect (const char *fname) { int rc; unsigned char *key; unsigned char *result; size_t resultlen; char *pw; key = read_key (fname); if (!key) return; pw = get_passphrase (1); rc = agent_protect (key, pw, &result, &resultlen, 0, opt_debug_use_ocb? 1 : -1); release_passphrase (pw); xfree (key); if (rc) { log_error ("protecting the key failed: %s\n", gpg_strerror (rc)); return; } if (opt_armor) { char *p = make_advanced (result, resultlen); xfree (result); if (!p) return; result = (unsigned char*)p; resultlen = strlen (p); } fwrite (result, resultlen, 1, stdout); xfree (result); } static void read_and_unprotect (ctrl_t ctrl, const char *fname) { - int rc; + gpg_error_t err; unsigned char *key; unsigned char *result; size_t resultlen; char *pw; gnupg_isotime_t protected_at; key = read_key (fname); if (!key) return; - rc = agent_unprotect (ctrl, key, (pw=get_passphrase (1)), - protected_at, &result, &resultlen); + err = agent_unprotect (ctrl, key, (pw=get_passphrase (1)), + protected_at, &result, &resultlen); release_passphrase (pw); xfree (key); - if (rc) + if (err) { if (opt_status_msg) log_info ("[PROTECT-TOOL:] bad-passphrase\n"); - log_error ("unprotecting the key failed: %s\n", gpg_strerror (rc)); + log_error ("unprotecting the key failed: %s\n", gpg_strerror (err)); return; } if (opt.verbose) { if (*protected_at) log_info ("key protection done at %.4s-%.2s-%.2s %.2s:%.2s:%s\n", protected_at, protected_at+4, protected_at+6, protected_at+9, protected_at+11, protected_at+13); else log_info ("key protection done at [unknown]\n"); } - resultlen = fixup_when_ecc_private_key (result, resultlen); + err = fixup_when_ecc_private_key (result, &resultlen); + if (err) + { + log_error ("malformed key: %s\n", gpg_strerror (err)); + return; + } if (opt_armor) { char *p = make_advanced (result, resultlen); xfree (result); if (!p) return; result = (unsigned char*)p; resultlen = strlen (p); } fwrite (result, resultlen, 1, stdout); xfree (result); } static void read_and_shadow (const char *fname) { int rc; unsigned char *key; unsigned char *result; size_t resultlen; unsigned char dummy_info[] = "(8:313233342:43)"; key = read_key (fname); if (!key) return; rc = agent_shadow_key (key, dummy_info, &result); xfree (key); if (rc) { log_error ("shadowing the key failed: %s\n", gpg_strerror (rc)); return; } resultlen = gcry_sexp_canon_len (result, 0, NULL,NULL); log_assert (resultlen); if (opt_armor) { char *p = make_advanced (result, resultlen); xfree (result); if (!p) return; result = (unsigned char*)p; resultlen = strlen (p); } fwrite (result, resultlen, 1, stdout); xfree (result); } static void show_shadow_info (const char *fname) { int rc; unsigned char *key; const unsigned char *info; size_t infolen; key = read_key (fname); if (!key) return; rc = agent_get_shadow_info (key, &info); xfree (key); if (rc) { log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc)); return; } infolen = gcry_sexp_canon_len (info, 0, NULL,NULL); log_assert (infolen); if (opt_armor) { char *p = make_advanced (info, infolen); if (!p) return; fwrite (p, strlen (p), 1, stdout); xfree (p); } else fwrite (info, infolen, 1, stdout); } static void show_file (const char *fname) { unsigned char *key; size_t keylen; char *p; key = read_key (fname); if (!key) return; keylen = gcry_sexp_canon_len (key, 0, NULL,NULL); log_assert (keylen); if (opt_canonical) { fwrite (key, keylen, 1, stdout); } else { p = make_advanced (key, keylen); if (p) { fwrite (p, strlen (p), 1, stdout); xfree (p); } } xfree (key); } static void show_keygrip (const char *fname) { unsigned char *key; gcry_sexp_t private; unsigned char grip[20]; int i; key = read_key (fname); if (!key) return; if (gcry_sexp_new (&private, key, 0, 0)) { log_error ("gcry_sexp_new failed\n"); return; } xfree (key); if (!gcry_pk_get_keygrip (private, grip)) { log_error ("can't calculate keygrip\n"); return; } gcry_sexp_release (private); for (i=0; i < 20; i++) printf ("%02X", grip[i]); putchar ('\n'); } int main (int argc, char **argv ) { gpgrt_argparse_t pargs; int cmd = 0; const char *fname; ctrl_t ctrl; early_system_init (); gpgrt_set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); log_set_prefix ("gpg-protect-tool", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); setup_libgcrypt_logging (); gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); pargs.argc = &argc; pargs.argv = &argv; pargs.flags= ARGPARSE_FLAG_KEEP; while (gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oVerbose: opt.verbose++; break; case oArmor: opt_armor=1; break; case oCanonical: opt_canonical=1; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oAgentProgram: opt_agent_program = pargs.r.ret_str; break; case oProtect: cmd = oProtect; break; case oUnprotect: cmd = oUnprotect; break; case oShadow: cmd = oShadow; break; case oShowShadowInfo: cmd = oShowShadowInfo; break; case oShowKeygrip: cmd = oShowKeygrip; break; case oS2Kcalibration: cmd = oS2Kcalibration; break; case oPassphrase: opt_passphrase = pargs.r.ret_str; break; case oStore: opt_store = 1; break; case oForce: opt_force = 1; break; case oNoFailOnExist: opt_no_fail_on_exist = 1; break; case oHaveCert: opt_have_cert = 1; break; case oPrompt: opt_prompt = pargs.r.ret_str; break; case oStatusMsg: opt_status_msg = 1; break; case oDebugUseOCB: opt_debug_use_ocb = 1; break; default: pargs.err = ARGPARSE_PRINT_ERROR; break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (log_get_errorcount (0)) exit (2); fname = "-"; if (argc == 1) fname = *argv; else if (argc > 1) gpgrt_usage (1); /* Allocate an CTRL object. An empty object should be sufficient. */ ctrl = xtrycalloc (1, sizeof *ctrl); if (!ctrl) { log_error ("error allocating connection control data: %s\n", strerror (errno)); agent_exit (1); } /* Set the information which can't be taken from envvars. */ gnupg_prepare_get_passphrase (GPG_ERR_SOURCE_DEFAULT, opt.verbose, opt_agent_program, NULL, NULL, NULL); if (opt_prompt) opt_prompt = percent_plus_unescape (opt_prompt, 0); if (cmd == oProtect) read_and_protect (fname); else if (cmd == oUnprotect) read_and_unprotect (ctrl, fname); else if (cmd == oShadow) read_and_shadow (fname); else if (cmd == oShowShadowInfo) show_shadow_info (fname); else if (cmd == oShowKeygrip) show_keygrip (fname); else if (cmd == oS2Kcalibration) { if (!opt.verbose) opt.verbose++; /* We need to see something. */ get_standard_s2k_count (); } else show_file (fname); xfree (ctrl); agent_exit (0); return 8; /*NOTREACHED*/ } void agent_exit (int rc) { rc = rc? rc : log_get_errorcount(0)? 2 : 0; exit (rc); } /* Return the passphrase string and ask the agent if it has not been set from the command line PROMPTNO select the prompt to display: 0 = default 1 = taken from the option --prompt 2 = for unprotecting a pkcs#12 object 3 = for protecting a new pkcs#12 object 4 = for protecting an imported pkcs#12 in our system */ static char * get_passphrase (int promptno) { char *pw; int err; const char *desc; char *orig_codeset; int repeat = 0; if (opt_passphrase) return xstrdup (opt_passphrase); orig_codeset = i18n_switchto_utf8 (); if (promptno == 1 && opt_prompt) { desc = opt_prompt; } else if (promptno == 2) { desc = _("Please enter the passphrase to unprotect the " "PKCS#12 object."); } else if (promptno == 3) { desc = _("Please enter the passphrase to protect the " "new PKCS#12 object."); repeat = 1; } else if (promptno == 4) { desc = _("Please enter the passphrase to protect the " "imported object within the GnuPG system."); repeat = 1; } else desc = _("Please enter the passphrase or the PIN\n" "needed to complete this operation."); i18n_switchback (orig_codeset); err = gnupg_get_passphrase (NULL, NULL, _("Passphrase:"), desc, repeat, repeat, 1, &pw); if (err) { if (gpg_err_code (err) == GPG_ERR_CANCELED || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) log_info (_("cancelled\n")); else log_error (_("error while asking for the passphrase: %s\n"), gpg_strerror (err)); agent_exit (0); } log_assert (pw); return pw; } static void release_passphrase (char *pw) { if (pw) { wipememory (pw, strlen (pw)); xfree (pw); } } /* Stub function. */ int agent_key_available (const unsigned char *grip) { (void)grip; return -1; /* Not available. */ } char * agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) { (void)ctrl; (void)key; (void)cache_mode; return NULL; } 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 err; unsigned char *passphrase; size_t size; (void)ctrl; (void)desc_text; (void)prompt_text; (void)initial_errtext; (void)keyinfo; (void)cache_mode; *pininfo->pin = 0; /* Reset the PIN. */ passphrase = get_passphrase (0); size = strlen (passphrase); if (size >= pininfo->max_length) return gpg_error (GPG_ERR_TOO_LARGE); 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; err = pininfo->check_cb (pininfo); } else err = 0; return err; } /* Replacement for the function in findkey.c. Here we write the key * to stdout. */ int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force, const char *serialno, const char *keyref) { char hexgrip[40+4+1]; char *p; (void)force; (void)serialno; (void)keyref; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); p = make_advanced (buffer, length); if (p) { printf ("# Begin dump of %s\n%s%s# End dump of %s\n", hexgrip, p, (*p && p[strlen(p)-1] == '\n')? "":"\n", hexgrip); xfree (p); } return 0; } diff --git a/agent/sexp-secret.c b/agent/sexp-secret.c index 9ed9cd402..bf6bacaaf 100644 --- a/agent/sexp-secret.c +++ b/agent/sexp-secret.c @@ -1,118 +1,122 @@ /* sexp-secret.c - SEXP handling of the secret key * Copyright (C) 2020 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 . */ #include #include "agent.h" #include "../common/sexp-parse.h" /* - * Fixup private key part in the cannonical SEXP. + * When it's for ECC, fixup private key part in the cannonical SEXP + * representation in BUF. */ -size_t -fixup_when_ecc_private_key (unsigned char *buf, size_t buflen) +gpg_error_t +fixup_when_ecc_private_key (unsigned char *buf, size_t *buflen_p) { const unsigned char *s; size_t n; + size_t buflen = *buflen_p; s = buf; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "private-key")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); s++; n = snext (&s); if (!smatch (&s, n, "ecc")) - return buflen; + return 0; /* It's ECC */ while (*s == '(') { s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (n == 1 && *s == 'd') { unsigned char *s0; size_t n0; s += n; s0 = (unsigned char *)s; n = snext (&s); n0 = s - s0; if (!n) return gpg_error (GPG_ERR_INV_SEXP); else if ((n & 1) && !*s) /* Detect wrongly added 0x00. */ /* For all existing curves in libgcrypt-1.9 (so far), the size of private part should be even. */ { size_t numsize; n--; buflen--; numsize = snprintf (s0, s-s0+1, "%u:", (unsigned int)n); memmove (s0+numsize, s+1, buflen - (s - buf)); memset (s0+numsize+buflen - (s - buf), 0, (n0 - numsize) + 1); buflen -= (n0 - numsize); s = s0+numsize+n; + *buflen_p = buflen; } else s += n; } else { s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; } if ( *s != ')' ) return gpg_error (GPG_ERR_INV_SEXP); s++; } if (*s != ')') return gpg_error (GPG_ERR_INV_SEXP); s++; - return buflen; + return 0; } gpg_error_t sexp_sscan_private_key (gcry_sexp_t *result, size_t *r_erroff, unsigned char *buf) { gpg_error_t err; - size_t buflen, buflen1; + size_t buflen, buflen0; - buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL); - buflen1 = fixup_when_ecc_private_key (buf, buflen); - err = gcry_sexp_sscan (result, r_erroff, (char*)buf, buflen1); - wipememory (buf, buflen); + buflen = buflen0 = gcry_sexp_canon_len (buf, 0, NULL, NULL); + err = fixup_when_ecc_private_key (buf, &buflen); + if (!err) + err = gcry_sexp_sscan (result, r_erroff, (char*)buf, buflen0); + wipememory (buf, buflen0); return err; }