diff --git a/agent/agent.h b/agent/agent.h index e72a75068..d32b89215 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -1,642 +1,645 @@ /* 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" #include "../common/name-value.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 contrained 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; /* 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; 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; /* Only use the system trustlist. */ int no_user_trustlist; /* The standard system trustlist is SYSCONFDIR/trustlist.txt. This * option can be used to change the name. */ const char *sys_trustlist_name; /* 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; /* 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, 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 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_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 /*-- 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); #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 void *buffer, size_t length, + int force, int reallyforce, const char *serialno, const char *keyref, const char *dispserialno, 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, uint64_t *r_timestamp); gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result); gpg_error_t agent_keymeta_from_file (ctrl_t ctrl, const unsigned char *grip, nvc_t *r_keymeta); 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, 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 (void); 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 --*/ #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); 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, + int reallyforce, const char *dispserialno); /*-- 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 *digest, size_t digestlen, int algo, const unsigned char *grip, 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 *cipher, const unsigned char *grip, 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); int divert_writekey (ctrl_t ctrl, int force, const char *serialno, const char *id, 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); void agent_scd_check_aliveness (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); int agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, const char *id, 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); gpg_error_t agent_card_cardlist (ctrl_t ctrl, strlist_t *result); 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_killscd (void); /*-- learncard.c --*/ -int agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force); +int agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, + int force, int reallyforce); /*-- 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); #endif /*AGENT_H*/ diff --git a/agent/command-ssh.c b/agent/command-ssh.c index f5ad977e2..bd1a41863 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -1,3870 +1,3870 @@ /* command-ssh.c - gpg-agent's implementation of the ssh-agent protocol. * Copyright (C) 2004-2006, 2009, 2012 Free Software Foundation, Inc. * Copyright (C) 2004-2006, 2009, 2012-2014 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 . */ /* Only v2 of the ssh-agent protocol is implemented. Relevant RFCs are: RFC-4250 - Protocol Assigned Numbers RFC-4251 - Protocol Architecture RFC-4252 - Authentication Protocol RFC-4253 - Transport Layer Protocol RFC-5656 - ECC support The protocol for the agent is defined in: https://tools.ietf.org/html/draft-miller-ssh-agent */ #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #include #endif /*!HAVE_W32_SYSTEM*/ #ifdef HAVE_SYS_UCRED_H #include #endif #ifdef HAVE_UCRED_H #include #endif #include "agent.h" #include "../common/i18n.h" #include "../common/util.h" #include "../common/ssh-utils.h" /* Request types. */ #define SSH_REQUEST_REQUEST_IDENTITIES 11 #define SSH_REQUEST_SIGN_REQUEST 13 #define SSH_REQUEST_ADD_IDENTITY 17 #define SSH_REQUEST_REMOVE_IDENTITY 18 #define SSH_REQUEST_REMOVE_ALL_IDENTITIES 19 #define SSH_REQUEST_LOCK 22 #define SSH_REQUEST_UNLOCK 23 #define SSH_REQUEST_ADD_ID_CONSTRAINED 25 /* Options. */ #define SSH_OPT_CONSTRAIN_LIFETIME 1 #define SSH_OPT_CONSTRAIN_CONFIRM 2 /* Response types. */ #define SSH_RESPONSE_SUCCESS 6 #define SSH_RESPONSE_FAILURE 5 #define SSH_RESPONSE_IDENTITIES_ANSWER 12 #define SSH_RESPONSE_SIGN_RESPONSE 14 /* Other constants. */ #define SSH_DSA_SIGNATURE_PADDING 20 #define SSH_DSA_SIGNATURE_ELEMS 2 #define SSH_AGENT_RSA_SHA2_256 0x02 #define SSH_AGENT_RSA_SHA2_512 0x04 #define SPEC_FLAG_USE_PKCS1V2 (1 << 0) #define SPEC_FLAG_IS_ECDSA (1 << 1) #define SPEC_FLAG_IS_EdDSA (1 << 2) /*(lowercase 'd' on purpose.)*/ #define SPEC_FLAG_WITH_CERT (1 << 7) /* The name of the control file. */ #define SSH_CONTROL_FILE_NAME "sshcontrol" /* The blurb we put into the header of a newly created control file. */ static const char sshcontrolblurb[] = "# List of allowed ssh keys. Only keys present in this file are used\n" "# in the SSH protocol. The ssh-add tool may add new entries to this\n" "# file to enable them; you may also add them manually. Comment\n" "# lines, like this one, as well as empty lines are ignored. Lines do\n" "# have a certain length limit but this is not serious limitation as\n" "# the format of the entries is fixed and checked by gpg-agent. A\n" "# non-comment line starts with optional white spaces, followed by the\n" "# keygrip of the key given as 40 hex digits, optionally followed by a\n" "# caching TTL in seconds, and another optional field for arbitrary\n" "# flags. Prepend the keygrip with an '!' mark to disable it.\n" "\n"; /* Macros. */ /* Return a new uint32 with b0 being the most significant byte and b3 being the least significant byte. */ #define uint32_construct(b0, b1, b2, b3) \ ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) /* * Basic types. */ /* Type for a request handler. */ typedef gpg_error_t (*ssh_request_handler_t) (ctrl_t ctrl, estream_t request, estream_t response); struct ssh_key_type_spec; typedef struct ssh_key_type_spec ssh_key_type_spec_t; /* Type, which is used for associating request handlers with the appropriate request IDs. */ typedef struct ssh_request_spec { unsigned char type; ssh_request_handler_t handler; const char *identifier; unsigned int secret_input; } ssh_request_spec_t; /* Type for "key modifier functions", which are necessary since OpenSSH and GnuPG treat key material slightly different. A key modifier is called right after a new key identity has been received in order to "sanitize" the material. */ typedef gpg_error_t (*ssh_key_modifier_t) (const char *elems, gcry_mpi_t *mpis); /* The encoding of a generated signature is dependent on the algorithm; therefore algorithm specific signature encoding functions are necessary. */ typedef gpg_error_t (*ssh_signature_encoder_t) (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t sig); /* Type, which is used for boundling all the algorithm specific information together in a single object. */ struct ssh_key_type_spec { /* Algorithm identifier as used by OpenSSH. */ const char *ssh_identifier; /* Human readable name of the algorithm. */ const char *name; /* Algorithm identifier as used by GnuPG. */ int algo; /* List of MPI names for secret keys; order matches the one of the agent protocol. */ const char *elems_key_secret; /* List of MPI names for public keys; order matches the one of the agent protocol. */ const char *elems_key_public; /* List of MPI names for signature data. */ const char *elems_signature; /* List of MPI names for secret keys; order matches the one, which is required by gpg-agent's key access layer. */ const char *elems_sexp_order; /* Key modifier function. Key modifier functions are necessary in order to fix any inconsistencies between the representation of keys on the SSH and on the GnuPG side. */ ssh_key_modifier_t key_modifier; /* Signature encoder function. Signature encoder functions are necessary since the encoding of signatures depends on the used algorithm. */ ssh_signature_encoder_t signature_encoder; /* The name of the ECC curve or NULL for non-ECC algos. This is the * canonical name for the curve as specified by RFC-5656. */ const char *curve_name; /* An alias for curve_name or NULL. Actually this is Libcgrypt's * primary name of the curve. */ const char *alt_curve_name; /* The hash algorithm to be used with this key. 0 for using the default. */ int hash_algo; /* Misc flags. */ unsigned int flags; }; /* Definition of an object to access the sshcontrol file. */ struct ssh_control_file_s { char *fname; /* Name of the file. */ estream_t fp; /* This is never NULL. */ int lnr; /* The current line number. */ struct { int valid; /* True if the data of this structure is valid. */ int disabled; /* The item is disabled. */ int ttl; /* The TTL of the item. */ int confirm; /* The confirm flag is set. */ char hexgrip[40+1]; /* The hexgrip of the item (uppercase). */ } item; }; /* Prototypes. */ static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis); static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t signature); static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t signature); static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t signature); static gpg_error_t ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t signature); static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment); /* Global variables. */ /* Associating request types with the corresponding request handlers. */ static const ssh_request_spec_t request_specs[] = { #define REQUEST_SPEC_DEFINE(id, name, secret_input) \ { SSH_REQUEST_##id, ssh_handler_##name, #name, secret_input } REQUEST_SPEC_DEFINE (REQUEST_IDENTITIES, request_identities, 1), REQUEST_SPEC_DEFINE (SIGN_REQUEST, sign_request, 0), REQUEST_SPEC_DEFINE (ADD_IDENTITY, add_identity, 1), REQUEST_SPEC_DEFINE (ADD_ID_CONSTRAINED, add_identity, 1), REQUEST_SPEC_DEFINE (REMOVE_IDENTITY, remove_identity, 0), REQUEST_SPEC_DEFINE (REMOVE_ALL_IDENTITIES, remove_all_identities, 0), REQUEST_SPEC_DEFINE (LOCK, lock, 0), REQUEST_SPEC_DEFINE (UNLOCK, unlock, 0) #undef REQUEST_SPEC_DEFINE }; /* Table holding key type specifications. */ static const ssh_key_type_spec_t ssh_key_types[] = { { "ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_eddsa, "Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA }, { "ssh-rsa", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu", ssh_key_modifier_rsa, ssh_signature_encoder_rsa, NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2 }, { "ssh-dss", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx", NULL, ssh_signature_encoder_dsa, NULL, NULL, 0, 0 }, { "ecdsa-sha2-nistp256", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp256", "NIST P-256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA }, { "ecdsa-sha2-nistp384", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp384", "NIST P-384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA }, { "ecdsa-sha2-nistp521", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp521", "NIST P-521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA }, { "ssh-ed25519-cert-v01@openssh.com", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_eddsa, "Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT }, { "ssh-rsa-cert-v01@openssh.com", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu", ssh_key_modifier_rsa, ssh_signature_encoder_rsa, NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT }, { "ssh-dss-cert-v01@openssh.com", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx", NULL, ssh_signature_encoder_dsa, NULL, NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT }, { "ecdsa-sha2-nistp256-cert-v01@openssh.com", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp256", "NIST P-256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT }, { "ecdsa-sha2-nistp384-cert-v01@openssh.com", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp384", "NIST P-384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT }, { "ecdsa-sha2-nistp521-cert-v01@openssh.com", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp521", "NIST P-521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT } }; /* General utility functions. */ /* A secure realloc, i.e. it makes sure to allocate secure memory if A is NULL. This is required because the standard gcry_realloc does not know whether to allocate secure or normal if NULL is passed as existing buffer. */ static void * realloc_secure (void *a, size_t n) { void *p; if (a) p = gcry_realloc (a, n); else p = gcry_malloc_secure (n); return p; } /* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns * NULL if not found. If found the ssh indetifier is returned and a * pointer to the canonical curve name as specified for ssh is stored * at R_CANON_NAME. */ static const char * ssh_identifier_from_curve_name (const char *curve_name, const char **r_canon_name) { int i; for (i = 0; i < DIM (ssh_key_types); i++) if (ssh_key_types[i].curve_name && (!strcmp (ssh_key_types[i].curve_name, curve_name) || (ssh_key_types[i].alt_curve_name && !strcmp (ssh_key_types[i].alt_curve_name, curve_name)))) { *r_canon_name = ssh_key_types[i].curve_name; return ssh_key_types[i].ssh_identifier; } return NULL; } /* Primitive I/O functions. */ /* Read a byte from STREAM, store it in B. */ static gpg_error_t stream_read_byte (estream_t stream, unsigned char *b) { gpg_error_t err; int ret; ret = es_fgetc (stream); if (ret == EOF) { if (es_ferror (stream)) err = gpg_error_from_syserror (); else err = gpg_error (GPG_ERR_EOF); *b = 0; } else { *b = ret & 0xFF; err = 0; } return err; } /* Write the byte contained in B to STREAM. */ static gpg_error_t stream_write_byte (estream_t stream, unsigned char b) { gpg_error_t err; int ret; ret = es_fputc (b, stream); if (ret == EOF) err = gpg_error_from_syserror (); else err = 0; return err; } /* Read a uint32 from STREAM, store it in UINT32. */ static gpg_error_t stream_read_uint32 (estream_t stream, u32 *uint32) { unsigned char buffer[4]; size_t bytes_read; gpg_error_t err; int ret; ret = es_read (stream, buffer, sizeof (buffer), &bytes_read); if (ret) err = gpg_error_from_syserror (); else { if (bytes_read != sizeof (buffer)) err = gpg_error (GPG_ERR_EOF); else { u32 n; n = uint32_construct (buffer[0], buffer[1], buffer[2], buffer[3]); *uint32 = n; err = 0; } } return err; } /* Write the uint32 contained in UINT32 to STREAM. */ static gpg_error_t stream_write_uint32 (estream_t stream, u32 uint32) { unsigned char buffer[4]; gpg_error_t err; int ret; buffer[0] = uint32 >> 24; buffer[1] = uint32 >> 16; buffer[2] = uint32 >> 8; buffer[3] = uint32 >> 0; ret = es_write (stream, buffer, sizeof (buffer), NULL); if (ret) err = gpg_error_from_syserror (); else err = 0; return err; } /* Read SIZE bytes from STREAM into BUFFER. */ static gpg_error_t stream_read_data (estream_t stream, unsigned char *buffer, size_t size) { gpg_error_t err; size_t bytes_read; int ret; ret = es_read (stream, buffer, size, &bytes_read); if (ret) err = gpg_error_from_syserror (); else { if (bytes_read != size) err = gpg_error (GPG_ERR_EOF); else err = 0; } return err; } /* Skip over SIZE bytes from STREAM. */ static gpg_error_t stream_read_skip (estream_t stream, size_t size) { char buffer[128]; size_t bytes_to_read, bytes_read; int ret; do { bytes_to_read = size; if (bytes_to_read > sizeof buffer) bytes_to_read = sizeof buffer; ret = es_read (stream, buffer, bytes_to_read, &bytes_read); if (ret) return gpg_error_from_syserror (); else if (bytes_read != bytes_to_read) return gpg_error (GPG_ERR_EOF); else size -= bytes_to_read; } while (size); return 0; } /* Write SIZE bytes from BUFFER to STREAM. */ static gpg_error_t stream_write_data (estream_t stream, const unsigned char *buffer, size_t size) { gpg_error_t err; int ret; ret = es_write (stream, buffer, size, NULL); if (ret) err = gpg_error_from_syserror (); else err = 0; return err; } /* Read a binary string from STREAM into STRING, store size of string in STRING_SIZE. Append a hidden nul so that the result may directly be used as a C string. Depending on SECURE use secure memory for STRING. If STRING is NULL do only a dummy read. */ static gpg_error_t stream_read_string (estream_t stream, unsigned int secure, unsigned char **string, u32 *string_size) { gpg_error_t err; unsigned char *buffer = NULL; u32 length = 0; if (string_size) *string_size = 0; /* Read string length. */ err = stream_read_uint32 (stream, &length); if (err) goto out; if (string) { /* Allocate space. */ if (secure) buffer = xtrymalloc_secure (length + 1); else buffer = xtrymalloc (length + 1); if (! buffer) { err = gpg_error_from_syserror (); goto out; } /* Read data. */ err = length? stream_read_data (stream, buffer, length) : 0; if (err) goto out; /* Finalize string object. */ buffer[length] = 0; *string = buffer; } else /* Dummy read requested. */ { err = length? stream_read_skip (stream, length) : 0; if (err) goto out; } if (string_size) *string_size = length; out: if (err) xfree (buffer); return err; } /* Read a binary string from STREAM and store it as an opaque MPI at R_MPI, adding 0x40 (this is the prefix for EdDSA key in OpenPGP). Depending on SECURE use secure memory. If the string is too large for key material return an error. */ static gpg_error_t stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi) { gpg_error_t err; unsigned char *buffer = NULL; u32 length = 0; *r_mpi = NULL; /* Read string length. */ err = stream_read_uint32 (stream, &length); if (err) goto leave; /* To avoid excessive use of secure memory we check that an MPI is not too large. */ if (length > (4096/8) + 8) { log_error (_("ssh keys greater than %d bits are not supported\n"), 4096); err = GPG_ERR_TOO_LARGE; goto leave; } /* Allocate space. */ if (secure) buffer = xtrymalloc_secure (length+1); else buffer = xtrymalloc (length+1); if (!buffer) { err = gpg_error_from_syserror (); goto leave; } /* Read data. */ err = stream_read_data (stream, buffer + 1, length); if (err) goto leave; buffer[0] = 0x40; *r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*(length+1)); buffer = NULL; leave: xfree (buffer); return err; } /* Read a C-string from STREAM, store copy in STRING. */ static gpg_error_t stream_read_cstring (estream_t stream, char **string) { return stream_read_string (stream, 0, (unsigned char **)string, NULL); } /* Write a binary string from STRING of size STRING_N to STREAM. */ static gpg_error_t stream_write_string (estream_t stream, const unsigned char *string, u32 string_n) { gpg_error_t err; err = stream_write_uint32 (stream, string_n); if (err) goto out; err = stream_write_data (stream, string, string_n); out: return err; } /* Write a C-string from STRING to STREAM. */ static gpg_error_t stream_write_cstring (estream_t stream, const char *string) { gpg_error_t err; err = stream_write_string (stream, (const unsigned char *) string, strlen (string)); return err; } /* Read an MPI from STREAM, store it in MPINT. Depending on SECURE use secure memory. */ static gpg_error_t stream_read_mpi (estream_t stream, unsigned int secure, gcry_mpi_t *mpint) { unsigned char *mpi_data; u32 mpi_data_size; gpg_error_t err; gcry_mpi_t mpi; mpi_data = NULL; err = stream_read_string (stream, secure, &mpi_data, &mpi_data_size); if (err) goto out; /* To avoid excessive use of secure memory we check that an MPI is not too large. */ if (mpi_data_size > 520) { log_error (_("ssh keys greater than %d bits are not supported\n"), 4096); err = GPG_ERR_TOO_LARGE; goto out; } err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_STD, mpi_data, mpi_data_size, NULL); if (err) goto out; *mpint = mpi; out: xfree (mpi_data); return err; } /* Write the MPI contained in MPINT to STREAM. */ static gpg_error_t stream_write_mpi (estream_t stream, gcry_mpi_t mpint) { unsigned char *mpi_buffer; size_t mpi_buffer_n; gpg_error_t err; mpi_buffer = NULL; err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &mpi_buffer, &mpi_buffer_n, mpint); if (err) goto out; err = stream_write_string (stream, mpi_buffer, mpi_buffer_n); out: xfree (mpi_buffer); return err; } /* Copy data from SRC to DST until EOF is reached. */ static gpg_error_t stream_copy (estream_t dst, estream_t src) { char buffer[BUFSIZ]; size_t bytes_read; gpg_error_t err; int ret; err = 0; while (1) { ret = es_read (src, buffer, sizeof (buffer), &bytes_read); if (ret || (! bytes_read)) { if (ret) err = gpg_error_from_syserror (); break; } ret = es_write (dst, buffer, bytes_read, NULL); if (ret) { err = gpg_error_from_syserror (); break; } } return err; } /* Open the ssh control file and create it if not available. With APPEND passed as true the file will be opened in append mode, otherwise in read only mode. On success 0 is returned and a new control file object stored at R_CF. On error an error code is returned and NULL is stored at R_CF. */ static gpg_error_t open_control_file (ssh_control_file_t *r_cf, int append) { gpg_error_t err; ssh_control_file_t cf; cf = xtrycalloc (1, sizeof *cf); if (!cf) { err = gpg_error_from_syserror (); goto leave; } /* Note: As soon as we start to use non blocking functions here (i.e. where Pth might switch threads) we need to employ a mutex. */ cf->fname = make_filename_try (gnupg_homedir (), SSH_CONTROL_FILE_NAME, NULL); if (!cf->fname) { err = gpg_error_from_syserror (); goto leave; } /* FIXME: With "a+" we are not able to check whether this will be created and thus the blurb needs to be written first. */ cf->fp = es_fopen (cf->fname, append? "a+":"r"); if (!cf->fp && errno == ENOENT) { estream_t stream = es_fopen (cf->fname, "wx,mode=-rw-r"); if (!stream) { err = gpg_error_from_syserror (); log_error (_("can't create '%s': %s\n"), cf->fname, gpg_strerror (err)); goto leave; } es_fputs (sshcontrolblurb, stream); es_fclose (stream); cf->fp = es_fopen (cf->fname, append? "a+":"r"); } if (!cf->fp) { err = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), cf->fname, gpg_strerror (err)); goto leave; } err = 0; leave: if (err && cf) { if (cf->fp) es_fclose (cf->fp); xfree (cf->fname); xfree (cf); } else *r_cf = cf; return err; } static void rewind_control_file (ssh_control_file_t cf) { es_fseek (cf->fp, 0, SEEK_SET); cf->lnr = 0; es_clearerr (cf->fp); } static void close_control_file (ssh_control_file_t cf) { if (!cf) return; es_fclose (cf->fp); xfree (cf->fname); xfree (cf); } /* Read the next line from the control file and store the data in CF. Returns 0 on success, GPG_ERR_EOF on EOF, or other error codes. */ static gpg_error_t read_control_file_item (ssh_control_file_t cf) { int c, i, n; char *p, *pend, line[256]; long ttl = 0; cf->item.valid = 0; es_clearerr (cf->fp); do { if (!es_fgets (line, DIM(line)-1, cf->fp) ) { if (es_feof (cf->fp)) return gpg_error (GPG_ERR_EOF); return gpg_error_from_syserror (); } cf->lnr++; if (!*line || line[strlen(line)-1] != '\n') { /* Eat until end of line */ while ((c = es_getc (cf->fp)) != EOF && c != '\n') ; return gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); } /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; } while (!*p || *p == '\n' || *p == '#'); cf->item.disabled = 0; if (*p == '!') { cf->item.disabled = 1; for (p++; spacep (p); p++) ; } for (i=0; hexdigitp (p) && i < 40; p++, i++) cf->item.hexgrip[i] = (*p >= 'a'? (*p & 0xdf): *p); cf->item.hexgrip[i] = 0; if (i != 40 || !(spacep (p) || *p == '\n')) { log_error ("%s:%d: invalid formatted line\n", cf->fname, cf->lnr); return gpg_error (GPG_ERR_BAD_DATA); } ttl = strtol (p, &pend, 10); p = pend; if (!(spacep (p) || *p == '\n') || (int)ttl < -1) { log_error ("%s:%d: invalid TTL value; assuming 0\n", cf->fname, cf->lnr); cf->item.ttl = 0; } cf->item.ttl = ttl; /* Now check for key-value pairs of the form NAME[=VALUE]. */ cf->item.confirm = 0; while (*p) { for (; spacep (p) && *p != '\n'; p++) ; if (!*p || *p == '\n') break; n = strcspn (p, "= \t\n"); if (p[n] == '=') { log_error ("%s:%d: assigning a value to a flag is not yet supported; " "flag ignored\n", cf->fname, cf->lnr); p++; } else if (n == 7 && !memcmp (p, "confirm", 7)) { cf->item.confirm = 1; } else log_error ("%s:%d: invalid flag '%.*s'; ignored\n", cf->fname, cf->lnr, n, p); p += n; } /* log_debug ("%s:%d: grip=%s ttl=%d%s%s\n", */ /* cf->fname, cf->lnr, */ /* cf->item.hexgrip, cf->item.ttl, */ /* cf->item.disabled? " disabled":"", */ /* cf->item.confirm? " confirm":""); */ cf->item.valid = 1; return 0; /* Okay: valid entry found. */ } /* Search the control file CF from the beginning until a matching HEXGRIP is found; return success in this case and store true at DISABLED if the found key has been disabled. If R_TTL is not NULL a specified TTL for that key is stored there. If R_CONFIRM is not NULL it is set to 1 if the key has the confirm flag set. */ static gpg_error_t search_control_file (ssh_control_file_t cf, const char *hexgrip, int *r_disabled, int *r_ttl, int *r_confirm) { gpg_error_t err; assert (strlen (hexgrip) == 40 ); if (r_disabled) *r_disabled = 0; if (r_ttl) *r_ttl = 0; if (r_confirm) *r_confirm = 0; rewind_control_file (cf); while (!(err=read_control_file_item (cf))) { if (!cf->item.valid) continue; /* Should not happen. */ if (!strcmp (hexgrip, cf->item.hexgrip)) break; } if (!err) { if (r_disabled) *r_disabled = cf->item.disabled; if (r_ttl) *r_ttl = cf->item.ttl; if (r_confirm) *r_confirm = cf->item.confirm; } return err; } /* Add an entry to the control file to mark the key with the keygrip HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks for it. FMTFPR is the fingerprint string. This function is in general used to add a key received through the ssh-add function. We can assume that the user wants to allow ssh using this key. */ static gpg_error_t add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec, const char *hexgrip, gcry_sexp_t key, int ttl, int confirm) { gpg_error_t err; ssh_control_file_t cf; int disabled; char *fpr_md5 = NULL; char *fpr_sha256 = NULL; (void)ctrl; err = open_control_file (&cf, 1); if (err) return err; err = search_control_file (cf, hexgrip, &disabled, NULL, NULL); if (err && gpg_err_code(err) == GPG_ERR_EOF) { struct tm *tp; time_t atime = time (NULL); err = ssh_get_fingerprint_string (key, GCRY_MD_MD5, &fpr_md5); if (err) goto out; err = ssh_get_fingerprint_string (key, GCRY_MD_SHA256, &fpr_sha256); if (err) goto out; /* Not yet in the file - add it. Because the file has been opened in append mode, we simply need to write to it. */ tp = localtime (&atime); es_fprintf (cf->fp, ("# %s key added on: %04d-%02d-%02d %02d:%02d:%02d\n" "# Fingerprints: %s\n" "# %s\n" "%s %d%s\n"), spec->name, 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec, fpr_md5, fpr_sha256, hexgrip, ttl, confirm? " confirm":""); } out: xfree (fpr_md5); xfree (fpr_sha256); close_control_file (cf); return 0; } /* Scan the sshcontrol file and return the TTL. */ static int ttl_from_sshcontrol (const char *hexgrip) { ssh_control_file_t cf; int disabled, ttl; if (!hexgrip || strlen (hexgrip) != 40) return 0; /* Wrong input: Use global default. */ if (open_control_file (&cf, 0)) return 0; /* Error: Use the global default TTL. */ if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL) || disabled) ttl = 0; /* Use the global default if not found or disabled. */ close_control_file (cf); return ttl; } /* Scan the sshcontrol file and return the confirm flag. */ static int confirm_flag_from_sshcontrol (const char *hexgrip) { ssh_control_file_t cf; int disabled, confirm; if (!hexgrip || strlen (hexgrip) != 40) return 1; /* Wrong input: Better ask for confirmation. */ if (open_control_file (&cf, 0)) return 1; /* Error: Better ask for confirmation. */ if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm) || disabled) confirm = 0; /* If not found or disabled, there is no reason to ask for confirmation. */ close_control_file (cf); return confirm; } /* Open the ssh control file for reading. This is a public version of open_control_file. The caller must use ssh_close_control_file to release the returned handle. */ ssh_control_file_t ssh_open_control_file (void) { ssh_control_file_t cf; /* Then look at all the registered and non-disabled keys. */ if (open_control_file (&cf, 0)) return NULL; return cf; } /* Close an ssh control file handle. This is the public version of close_control_file. CF may be NULL. */ void ssh_close_control_file (ssh_control_file_t cf) { close_control_file (cf); } /* Read the next item from the ssh control file. The function returns 0 if a item was read, GPG_ERR_EOF on eof or another error value. R_HEXGRIP shall either be null or a BUFFER of at least 41 byte. R_DISABLED, R_TTLm and R_CONFIRM return flags from the control file; they are only set on success. */ 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 err; do err = read_control_file_item (cf); while (!err && !cf->item.valid); if (!err) { if (r_hexgrip) strcpy (r_hexgrip, cf->item.hexgrip); if (r_disabled) *r_disabled = cf->item.disabled; if (r_ttl) *r_ttl = cf->item.ttl; if (r_confirm) *r_confirm = cf->item.confirm; } return err; } /* Search for a key with HEXGRIP in sshcontrol and return all info. */ gpg_error_t ssh_search_control_file (ssh_control_file_t cf, const char *hexgrip, int *r_disabled, int *r_ttl, int *r_confirm) { gpg_error_t err; int i; const char *s; char uphexgrip[41]; /* We need to make sure that HEXGRIP is all uppercase. The easiest way to do this and also check its length is by copying to a second buffer. */ for (i=0, s=hexgrip; i < 40 && *s; s++, i++) uphexgrip[i] = *s >= 'a'? (*s & 0xdf): *s; uphexgrip[i] = 0; if (i != 40) err = gpg_error (GPG_ERR_INV_LENGTH); else err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm); if (gpg_err_code (err) == GPG_ERR_EOF) err = gpg_error (GPG_ERR_NOT_FOUND); return err; } /* MPI lists. */ /* Free the list of MPIs MPI_LIST. */ static void mpint_list_free (gcry_mpi_t *mpi_list) { if (mpi_list) { unsigned int i; for (i = 0; mpi_list[i]; i++) gcry_mpi_release (mpi_list[i]); xfree (mpi_list); } } /* Receive key material MPIs from STREAM according to KEY_SPEC; depending on SECRET expect a public key or secret key. CERT is the certificate blob used if KEY_SPEC indicates the certificate format; it needs to be positioned to the end of the nonce. The newly allocated list of MPIs is stored in MPI_LIST. Returns usual error code. */ static gpg_error_t ssh_receive_mpint_list (estream_t stream, int secret, ssh_key_type_spec_t *spec, estream_t cert, gcry_mpi_t **mpi_list) { const char *elems_public; unsigned int elems_n; const char *elems; int elem_is_secret; gcry_mpi_t *mpis = NULL; gpg_error_t err = 0; unsigned int i; if (secret) elems = spec->elems_key_secret; else elems = spec->elems_key_public; elems_n = strlen (elems); elems_public = spec->elems_key_public; /* Check that either both, CERT and the WITH_CERT flag, are given or none of them. */ if (!(!!(spec->flags & SPEC_FLAG_WITH_CERT) ^ !cert)) { err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto out; } mpis = xtrycalloc (elems_n + 1, sizeof *mpis ); if (!mpis) { err = gpg_error_from_syserror (); goto out; } elem_is_secret = 0; for (i = 0; i < elems_n; i++) { if (secret) elem_is_secret = !strchr (elems_public, elems[i]); if (cert && !elem_is_secret) err = stream_read_mpi (cert, elem_is_secret, &mpis[i]); else err = stream_read_mpi (stream, elem_is_secret, &mpis[i]); if (err) goto out; } *mpi_list = mpis; mpis = NULL; out: if (err) mpint_list_free (mpis); return err; } /* Key modifier function for RSA. */ static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis) { gcry_mpi_t p; gcry_mpi_t q; gcry_mpi_t u; if (strcmp (elems, "nedupq")) /* Modifying only necessary for secret keys. */ goto out; u = mpis[3]; p = mpis[4]; q = mpis[5]; if (gcry_mpi_cmp (p, q) > 0) { /* P shall be smaller then Q! Swap primes. iqmp becomes u. */ gcry_mpi_t tmp; tmp = mpis[4]; mpis[4] = mpis[5]; mpis[5] = tmp; } else /* U needs to be recomputed. */ gcry_mpi_invm (u, p, q); out: return 0; } /* Signature encoder function for RSA. */ static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t s_signature) { gpg_error_t err = 0; gcry_sexp_t valuelist = NULL; gcry_sexp_t sublist = NULL; gcry_mpi_t sig_value = NULL; gcry_mpi_t *mpis = NULL; const char *elems; size_t elems_n; int i; unsigned char *data; size_t data_n; gcry_mpi_t s; valuelist = gcry_sexp_nth (s_signature, 1); if (!valuelist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } elems = spec->elems_signature; elems_n = strlen (elems); mpis = xtrycalloc (elems_n + 1, sizeof *mpis); if (!mpis) { err = gpg_error_from_syserror (); goto out; } for (i = 0; i < elems_n; i++) { sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (!sublist) { err = gpg_error (GPG_ERR_INV_SEXP); break; } sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); if (!sig_value) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } gcry_sexp_release (sublist); sublist = NULL; mpis[i] = sig_value; } if (err) goto out; /* RSA specific */ s = mpis[0]; err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, s); if (err) goto out; err = stream_write_string (signature_blob, data, data_n); xfree (data); out: gcry_sexp_release (valuelist); gcry_sexp_release (sublist); mpint_list_free (mpis); return err; } /* Signature encoder function for DSA. */ static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t s_signature) { gpg_error_t err = 0; gcry_sexp_t valuelist = NULL; gcry_sexp_t sublist = NULL; gcry_mpi_t sig_value = NULL; gcry_mpi_t *mpis = NULL; const char *elems; size_t elems_n; int i; unsigned char buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS]; unsigned char *data = NULL; size_t data_n; valuelist = gcry_sexp_nth (s_signature, 1); if (!valuelist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } elems = spec->elems_signature; elems_n = strlen (elems); mpis = xtrycalloc (elems_n + 1, sizeof *mpis); if (!mpis) { err = gpg_error_from_syserror (); goto out; } for (i = 0; i < elems_n; i++) { sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (!sublist) { err = gpg_error (GPG_ERR_INV_SEXP); break; } sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); if (!sig_value) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } gcry_sexp_release (sublist); sublist = NULL; mpis[i] = sig_value; } if (err) goto out; /* DSA specific code. */ /* FIXME: Why this complicated code? Why collecting boths mpis in a buffer instead of writing them out one after the other? */ for (i = 0; i < 2; i++) { err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, mpis[i]); if (err) break; if (data_n > SSH_DSA_SIGNATURE_PADDING) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } memset (buffer + (i * SSH_DSA_SIGNATURE_PADDING), 0, SSH_DSA_SIGNATURE_PADDING - data_n); memcpy (buffer + (i * SSH_DSA_SIGNATURE_PADDING) + (SSH_DSA_SIGNATURE_PADDING - data_n), data, data_n); xfree (data); data = NULL; } if (err) goto out; err = stream_write_string (signature_blob, buffer, sizeof (buffer)); out: xfree (data); gcry_sexp_release (valuelist); gcry_sexp_release (sublist); mpint_list_free (mpis); return err; } /* Signature encoder function for ECDSA. */ static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec, estream_t stream, gcry_sexp_t s_signature) { gpg_error_t err = 0; gcry_sexp_t valuelist = NULL; gcry_sexp_t sublist = NULL; gcry_mpi_t sig_value = NULL; gcry_mpi_t *mpis = NULL; const char *elems; size_t elems_n; int i; unsigned char *data[2] = {NULL, NULL}; size_t data_n[2]; size_t innerlen; valuelist = gcry_sexp_nth (s_signature, 1); if (!valuelist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } elems = spec->elems_signature; elems_n = strlen (elems); mpis = xtrycalloc (elems_n + 1, sizeof *mpis); if (!mpis) { err = gpg_error_from_syserror (); goto out; } for (i = 0; i < elems_n; i++) { sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (!sublist) { err = gpg_error (GPG_ERR_INV_SEXP); break; } sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); if (!sig_value) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } gcry_sexp_release (sublist); sublist = NULL; mpis[i] = sig_value; } if (err) goto out; /* ECDSA specific */ innerlen = 0; for (i = 0; i < DIM(data); i++) { err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &data[i], &data_n[i], mpis[i]); if (err) goto out; innerlen += 4 + data_n[i]; } err = stream_write_uint32 (stream, innerlen); if (err) goto out; for (i = 0; i < DIM(data); i++) { err = stream_write_string (stream, data[i], data_n[i]); if (err) goto out; } out: for (i = 0; i < DIM(data); i++) xfree (data[i]); gcry_sexp_release (valuelist); gcry_sexp_release (sublist); mpint_list_free (mpis); return err; } /* Signature encoder function for EdDSA. */ static gpg_error_t ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec, estream_t stream, gcry_sexp_t s_signature) { gpg_error_t err = 0; gcry_sexp_t valuelist = NULL; gcry_sexp_t sublist = NULL; const char *elems; size_t elems_n; int i; unsigned char *data[2] = {NULL, NULL}; size_t data_n[2]; size_t totallen = 0; valuelist = gcry_sexp_nth (s_signature, 1); if (!valuelist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } elems = spec->elems_signature; elems_n = strlen (elems); if (elems_n != DIM(data)) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } for (i = 0; i < DIM(data); i++) { sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (!sublist) { err = gpg_error (GPG_ERR_INV_SEXP); break; } data[i] = gcry_sexp_nth_buffer (sublist, 1, &data_n[i]); if (!data[i]) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } totallen += data_n[i]; gcry_sexp_release (sublist); sublist = NULL; } if (err) goto out; err = stream_write_uint32 (stream, totallen); if (err) goto out; for (i = 0; i < DIM(data); i++) { err = stream_write_data (stream, data[i], data_n[i]); if (err) goto out; } out: for (i = 0; i < DIM(data); i++) xfree (data[i]); gcry_sexp_release (valuelist); gcry_sexp_release (sublist); return err; } /* S-Expressions. */ /* This function constructs a new S-Expression for the key identified by the KEY_SPEC, SECRET, CURVE_NAME, MPIS, and COMMENT, which is to be stored at R_SEXP. Returns an error code. */ static gpg_error_t sexp_key_construct (gcry_sexp_t *r_sexp, ssh_key_type_spec_t key_spec, int secret, const char *curve_name, gcry_mpi_t *mpis, const char *comment) { gpg_error_t err; gcry_sexp_t sexp_new = NULL; void *formatbuf = NULL; void **arg_list = NULL; estream_t format = NULL; char *algo_name = NULL; /* We can't encode an empty string in an S-expression, thus to keep * the code simple we use "(none)" instead. */ if (!comment || !*comment) comment = "(none)"; if ((key_spec.flags & SPEC_FLAG_IS_EdDSA)) { /* It is much easier and more readable to use a separate code path for EdDSA. */ if (!curve_name) err = gpg_error (GPG_ERR_INV_CURVE); else if (!mpis[0] || !gcry_mpi_get_flag (mpis[0], GCRYMPI_FLAG_OPAQUE)) err = gpg_error (GPG_ERR_BAD_PUBKEY); else if (secret && (!mpis[1] || !gcry_mpi_get_flag (mpis[1], GCRYMPI_FLAG_OPAQUE))) err = gpg_error (GPG_ERR_BAD_SECKEY); else if (secret) err = gcry_sexp_build (&sexp_new, NULL, "(private-key(ecc(curve %s)" "(flags eddsa)(q %m)(d %m))" "(comment%s))", curve_name, mpis[0], mpis[1], comment); else err = gcry_sexp_build (&sexp_new, NULL, "(public-key(ecc(curve %s)" "(flags eddsa)(q %m))" "(comment%s))", curve_name, mpis[0], comment); } else { const char *key_identifier[] = { "public-key", "private-key" }; int arg_idx; const char *elems; size_t elems_n; unsigned int i, j; if (secret) elems = key_spec.elems_sexp_order; else elems = key_spec.elems_key_public; elems_n = strlen (elems); format = es_fopenmem (0, "a+b"); if (!format) { err = gpg_error_from_syserror (); goto out; } /* Key identifier, algorithm identifier, mpis, comment, and a NULL as a safeguard. */ arg_list = xtrymalloc (sizeof (*arg_list) * (2 + 1 + elems_n + 1 + 1)); if (!arg_list) { err = gpg_error_from_syserror (); goto out; } arg_idx = 0; es_fputs ("(%s(%s", format); arg_list[arg_idx++] = &key_identifier[secret]; algo_name = xtrystrdup (gcry_pk_algo_name (key_spec.algo)); if (!algo_name) { err = gpg_error_from_syserror (); goto out; } strlwr (algo_name); arg_list[arg_idx++] = &algo_name; if (curve_name) { es_fputs ("(curve%s)", format); arg_list[arg_idx++] = &curve_name; } for (i = 0; i < elems_n; i++) { es_fprintf (format, "(%c%%m)", elems[i]); if (secret) { for (j = 0; j < elems_n; j++) if (key_spec.elems_key_secret[j] == elems[i]) break; } else j = i; arg_list[arg_idx++] = &mpis[j]; } es_fputs (")(comment%s))", format); arg_list[arg_idx++] = &comment; arg_list[arg_idx] = NULL; es_putc (0, format); if (es_ferror (format)) { err = gpg_error_from_syserror (); goto out; } if (es_fclose_snatch (format, &formatbuf, NULL)) { err = gpg_error_from_syserror (); goto out; } format = NULL; err = gcry_sexp_build_array (&sexp_new, NULL, formatbuf, arg_list); } if (!err) *r_sexp = sexp_new; out: es_fclose (format); xfree (arg_list); xfree (formatbuf); xfree (algo_name); return err; } /* This function extracts the key from the s-expression SEXP according to KEY_SPEC and stores it in ssh format at (R_BLOB, R_BLOBLEN). If WITH_SECRET is true, the secret key parts are also extracted if possible. Returns 0 on success or an error code. Note that data stored at R_BLOB must be freed using es_free! */ static gpg_error_t ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, ssh_key_type_spec_t key_spec, void **r_blob, size_t *r_blob_size) { gpg_error_t err = 0; gcry_sexp_t value_list = NULL; gcry_sexp_t value_pair = NULL; estream_t stream = NULL; void *blob = NULL; size_t blob_size; const char *elems, *p_elems; const char *data; size_t datalen; *r_blob = NULL; *r_blob_size = 0; stream = es_fopenmem (0, "r+b"); if (!stream) { err = gpg_error_from_syserror (); goto out; } /* Get the type of the key expression. */ data = gcry_sexp_nth_data (sexp, 0, &datalen); if (!data) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } if ((datalen == 10 && !strncmp (data, "public-key", 10)) || (datalen == 21 && !strncmp (data, "protected-private-key", 21)) || (datalen == 20 && !strncmp (data, "shadowed-private-key", 20))) elems = key_spec.elems_key_public; else if (datalen == 11 && !strncmp (data, "private-key", 11)) elems = with_secret? key_spec.elems_key_secret : key_spec.elems_key_public; else { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } /* Get key value list. */ value_list = gcry_sexp_cadr (sexp); if (!value_list) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } /* Write the ssh algorithm identifier. */ if ((key_spec.flags & SPEC_FLAG_IS_ECDSA)) { /* Map the curve name to the ssh name. */ const char *name, *sshname, *canon_name; name = gcry_pk_get_curve (sexp, 0, NULL); if (!name) { err = gpg_error (GPG_ERR_INV_CURVE); goto out; } sshname = ssh_identifier_from_curve_name (name, &canon_name); if (!sshname) { err = gpg_error (GPG_ERR_UNKNOWN_CURVE); goto out; } err = stream_write_cstring (stream, sshname); if (err) goto out; err = stream_write_cstring (stream, canon_name); if (err) goto out; } else { /* Note: This is also used for EdDSA. */ err = stream_write_cstring (stream, key_spec.ssh_identifier); if (err) goto out; } /* Write the parameters. */ for (p_elems = elems; *p_elems; p_elems++) { gcry_sexp_release (value_pair); value_pair = gcry_sexp_find_token (value_list, p_elems, 1); if (!value_pair) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } if ((key_spec.flags & SPEC_FLAG_IS_EdDSA)) { data = gcry_sexp_nth_data (value_pair, 1, &datalen); if (!data) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } if (*p_elems == 'q' && datalen) { /* Remove the prefix 0x40. */ data++; datalen--; } err = stream_write_string (stream, data, datalen); if (err) goto out; } else { gcry_mpi_t mpi; /* Note that we need to use STD format; i.e. prepend a 0x00 to indicate a positive number if the high bit is set. */ mpi = gcry_sexp_nth_mpi (value_pair, 1, GCRYMPI_FMT_STD); if (!mpi) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } err = stream_write_mpi (stream, mpi); gcry_mpi_release (mpi); if (err) goto out; } } if (es_fclose_snatch (stream, &blob, &blob_size)) { err = gpg_error_from_syserror (); goto out; } stream = NULL; *r_blob = blob; blob = NULL; *r_blob_size = blob_size; out: gcry_sexp_release (value_list); gcry_sexp_release (value_pair); es_fclose (stream); es_free (blob); return err; } /* Key I/O. */ /* Search for a key specification entry. If SSH_NAME is not NULL, search for an entry whose "ssh_name" is equal to SSH_NAME; otherwise, search for an entry whose algorithm is equal to ALGO. Store found entry in SPEC on success, return error otherwise. */ static gpg_error_t ssh_key_type_lookup (const char *ssh_name, int algo, ssh_key_type_spec_t *spec) { gpg_error_t err; unsigned int i; for (i = 0; i < DIM (ssh_key_types); i++) if ((ssh_name && (! strcmp (ssh_name, ssh_key_types[i].ssh_identifier))) || algo == ssh_key_types[i].algo) break; if (i == DIM (ssh_key_types)) err = gpg_error (GPG_ERR_NOT_FOUND); else { *spec = ssh_key_types[i]; err = 0; } return err; } /* Receive a key from STREAM, according to the key specification given as KEY_SPEC. Depending on SECRET, receive a secret or a public key. If READ_COMMENT is true, receive a comment string as well. Constructs a new S-Expression from received data and stores it in KEY_NEW. Returns zero on success or an error code. */ static gpg_error_t ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, int read_comment, ssh_key_type_spec_t *key_spec) { gpg_error_t err; char *key_type = NULL; char *comment = NULL; estream_t cert = NULL; gcry_sexp_t key = NULL; ssh_key_type_spec_t spec; gcry_mpi_t *mpi_list = NULL; const char *elems; const char *curve_name = NULL; err = stream_read_cstring (stream, &key_type); if (err) goto out; err = ssh_key_type_lookup (key_type, 0, &spec); if (err) goto out; if ((spec.flags & SPEC_FLAG_WITH_CERT)) { /* This is an OpenSSH certificate+private key. The certificate is an SSH string and which we store in an estream object. */ unsigned char *buffer; u32 buflen; char *cert_key_type; err = stream_read_string (stream, 0, &buffer, &buflen); if (err) goto out; cert = es_fopenmem_init (0, "rb", buffer, buflen); xfree (buffer); if (!cert) { err = gpg_error_from_syserror (); goto out; } /* Check that the key type matches. */ err = stream_read_cstring (cert, &cert_key_type); if (err) goto out; if (strcmp (cert_key_type, key_type) ) { xfree (cert_key_type); log_error ("key types in received ssh certificate do not match\n"); err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto out; } xfree (cert_key_type); /* Skip the nonce. */ err = stream_read_string (cert, 0, NULL, NULL); if (err) goto out; } if ((spec.flags & SPEC_FLAG_IS_EdDSA)) { /* The format of an EdDSA key is: * string key_type ("ssh-ed25519") * string public_key * string private_key * * Note that the private key is the concatenation of the private * key with the public key. Thus there's are 64 bytes; however * we only want the real 32 byte private key - Libgcrypt expects * this. */ mpi_list = xtrycalloc (3, sizeof *mpi_list); if (!mpi_list) { err = gpg_error_from_syserror (); goto out; } err = stream_read_blob (cert? cert : stream, 0, &mpi_list[0]); if (err) goto out; if (secret) { u32 len = 0; unsigned char *buffer; /* Read string length. */ err = stream_read_uint32 (stream, &len); if (err) goto out; if (len != 32 && len != 64) { err = gpg_error (GPG_ERR_BAD_SECKEY); goto out; } buffer = xtrymalloc_secure (32); if (!buffer) { err = gpg_error_from_syserror (); goto out; } err = stream_read_data (stream, buffer, 32); if (err) { xfree (buffer); goto out; } mpi_list[1] = gcry_mpi_set_opaque (NULL, buffer, 8*32); buffer = NULL; if (len == 64) { err = stream_read_skip (stream, 32); if (err) goto out; } } } else if ((spec.flags & SPEC_FLAG_IS_ECDSA)) { /* The format of an ECDSA key is: * string key_type ("ecdsa-sha2-nistp256" | * "ecdsa-sha2-nistp384" | * "ecdsa-sha2-nistp521" ) * string ecdsa_curve_name * string ecdsa_public_key * mpint ecdsa_private * * Note that we use the mpint reader instead of the string * reader for ecsa_public_key. For the certificate variante * ecdsa_curve_name+ecdsa_public_key are replaced by the * certificate. */ unsigned char *buffer; err = stream_read_string (cert? cert : stream, 0, &buffer, NULL); if (err) goto out; /* Get the canonical name. Should be the same as the read * string but we use this mapping to validate that name. */ if (!ssh_identifier_from_curve_name (buffer, &curve_name)) { err = gpg_error (GPG_ERR_UNKNOWN_CURVE); xfree (buffer); goto out; } xfree (buffer); err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list); if (err) goto out; } else { err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list); if (err) goto out; } if (read_comment) { err = stream_read_cstring (stream, &comment); if (err) goto out; } if (secret) elems = spec.elems_key_secret; else elems = spec.elems_key_public; if (spec.key_modifier) { err = (*spec.key_modifier) (elems, mpi_list); if (err) goto out; } if ((spec.flags & SPEC_FLAG_IS_EdDSA)) { if (secret) { err = gcry_sexp_build (&key, NULL, "(private-key(ecc(curve \"Ed25519\")" "(flags eddsa)(q %m)(d %m))" "(comment%s))", mpi_list[0], mpi_list[1], comment? comment:""); } else { err = gcry_sexp_build (&key, NULL, "(public-key(ecc(curve \"Ed25519\")" "(flags eddsa)(q %m))" "(comment%s))", mpi_list[0], comment? comment:""); } } else { err = sexp_key_construct (&key, spec, secret, curve_name, mpi_list, comment? comment:""); if (err) goto out; } if (key_spec) *key_spec = spec; *key_new = key; out: es_fclose (cert); mpint_list_free (mpi_list); xfree (key_type); xfree (comment); return err; } /* Write the public key from KEY to STREAM in SSH key format. If OVERRIDE_COMMENT is not NULL, it will be used instead of the comment stored in the key. */ static gpg_error_t ssh_send_key_public (estream_t stream, gcry_sexp_t key, const char *override_comment) { ssh_key_type_spec_t spec; int algo; char *comment = NULL; void *blob = NULL; size_t bloblen; gpg_error_t err = 0; algo = get_pk_algo_from_key (key); if (algo == 0) goto out; err = ssh_key_type_lookup (NULL, algo, &spec); if (err) goto out; err = ssh_key_to_blob (key, 0, spec, &blob, &bloblen); if (err) goto out; err = stream_write_string (stream, blob, bloblen); if (err) goto out; if (override_comment) err = stream_write_cstring (stream, override_comment); else { err = ssh_key_extract_comment (key, &comment); if (err) err = stream_write_cstring (stream, "(none)"); else err = stream_write_cstring (stream, comment); } if (err) goto out; out: xfree (comment); es_free (blob); return err; } /* Read a public key out of BLOB/BLOB_SIZE according to the key specification given as KEY_SPEC, storing the new key in KEY_PUBLIC. Returns zero on success or an error code. */ static gpg_error_t ssh_read_key_public_from_blob (unsigned char *blob, size_t blob_size, gcry_sexp_t *key_public, ssh_key_type_spec_t *key_spec) { gpg_error_t err; estream_t blob_stream; blob_stream = es_fopenmem (0, "r+b"); if (!blob_stream) { err = gpg_error_from_syserror (); goto out; } err = stream_write_data (blob_stream, blob, blob_size); if (err) goto out; err = es_fseek (blob_stream, 0, SEEK_SET); if (err) goto out; err = ssh_receive_key (blob_stream, key_public, 0, 0, key_spec); out: es_fclose (blob_stream); return err; } /* This function calculates the key grip for the key contained in the S-Expression KEY and writes it to BUFFER, which must be large enough to hold it. Returns usual error code. */ static gpg_error_t ssh_key_grip (gcry_sexp_t key, unsigned char *buffer) { if (!gcry_pk_get_keygrip (key, buffer)) { gpg_error_t err = gcry_pk_testkey (key); return err? err : gpg_error (GPG_ERR_INTERNAL); } return 0; } static gpg_error_t card_key_list (ctrl_t ctrl, char **r_serialno, strlist_t *result) { gpg_error_t err; *r_serialno = NULL; *result = NULL; err = agent_card_serialno (ctrl, r_serialno, NULL); if (err) { if (gpg_err_code (err) != GPG_ERR_ENODEV && opt.verbose) log_info (_("error getting serial number of card: %s\n"), gpg_strerror (err)); /* Nothing available. */ return 0; } err = agent_card_cardlist (ctrl, result); if (err) { xfree (*r_serialno); *r_serialno = NULL; } return err; } /* Check whether a smartcard is available and whether it has a usable key. Store a copy of that key at R_PK and return 0. If no key is available store NULL at R_PK and return an error code. If CARDSN is not NULL, a string with the serial number of the card will be a malloced and stored there. */ static gpg_error_t card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk, char **cardsn) { gpg_error_t err; char *authkeyid; char *serialno = NULL; unsigned char *pkbuf; size_t pkbuflen; gcry_sexp_t s_pk; unsigned char grip[20]; *r_pk = NULL; if (cardsn) *cardsn = NULL; /* First see whether a card is available and whether the application is supported. */ err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid); if ( gpg_err_code (err) == GPG_ERR_CARD_REMOVED ) { /* Ask for the serial number to reset the card. */ err = agent_card_serialno (ctrl, &serialno, NULL); if (err) { if (opt.verbose) log_info (_("error getting serial number of card: %s\n"), gpg_strerror (err)); return err; } log_info (_("detected card with S/N: %s\n"), serialno); err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid); } if (err) { log_error (_("no authentication key for ssh on card: %s\n"), gpg_strerror (err)); xfree (serialno); return err; } /* Get the S/N if we don't have it yet. Use the fast getattr method. */ if (!serialno && (err = agent_card_getattr (ctrl, "SERIALNO", &serialno)) ) { log_error (_("error getting serial number of card: %s\n"), gpg_strerror (err)); xfree (authkeyid); return err; } /* Read the public key. */ err = agent_card_readkey (ctrl, authkeyid, &pkbuf); if (err) { if (opt.verbose) log_info (_("no suitable card key found: %s\n"), gpg_strerror (err)); xfree (serialno); xfree (authkeyid); return err; } pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); err = gcry_sexp_sscan (&s_pk, NULL, (char*)pkbuf, pkbuflen); if (err) { log_error ("failed to build S-Exp from received card key: %s\n", gpg_strerror (err)); xfree (pkbuf); xfree (serialno); xfree (authkeyid); return err; } err = ssh_key_grip (s_pk, grip); if (err) { log_debug ("error computing keygrip from received card key: %s\n", gcry_strerror (err)); xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } if ( agent_key_available (grip) ) { char *dispserialno; /* (Shadow)-key is not available in our key storage. */ agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno); - err = agent_write_shadow_key (grip, serialno, authkeyid, pkbuf, 0, + err = agent_write_shadow_key (grip, serialno, authkeyid, pkbuf, 0, 0, dispserialno); xfree (dispserialno); if (err) { xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } } if (cardsn) { char *dispsn; /* If the card handler is able to return a short serialnumber, use that one, else use the complete serialno. */ if (!agent_card_getattr (ctrl, "$DISPSERIALNO", &dispsn)) { *cardsn = xtryasprintf ("cardno:%s", dispsn); xfree (dispsn); } else *cardsn = xtryasprintf ("cardno:%s", serialno); if (!*cardsn) { err = gpg_error_from_syserror (); xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } } xfree (pkbuf); xfree (serialno); xfree (authkeyid); *r_pk = s_pk; return 0; } /* Request handler. Each handler is provided with a CTRL context, a REQUEST object and a RESPONSE object. The actual request is to be read from REQUEST, the response needs to be written to RESPONSE. */ /* Handler for the "request_identities" command. */ static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl, estream_t request, estream_t response) { u32 key_counter; estream_t key_blobs; gcry_sexp_t key_public; gpg_error_t err; int ret; ssh_control_file_t cf = NULL; gpg_error_t ret_err; (void)request; /* Prepare buffer stream. */ key_public = NULL; key_counter = 0; key_blobs = es_fopenmem (0, "r+b"); if (! key_blobs) { err = gpg_error_from_syserror (); goto out; } /* First check whether a key is currently available in the card reader - this should be allowed even without being listed in sshcontrol. */ if (!opt.disable_scdaemon) { char *serialno; strlist_t card_list, sl; err = card_key_list (ctrl, &serialno, &card_list); if (err) { if (opt.verbose) log_info (_("error getting list of cards: %s\n"), gpg_strerror (err)); goto scd_out; } for (sl = card_list; sl; sl = sl->next) { char *serialno0; char *cardsn; err = agent_card_serialno (ctrl, &serialno0, sl->d); if (err) { if (opt.verbose) log_info (_("error getting serial number of card: %s\n"), gpg_strerror (err)); continue; } xfree (serialno0); if (card_key_available (ctrl, &key_public, &cardsn)) continue; err = ssh_send_key_public (key_blobs, key_public, cardsn); gcry_sexp_release (key_public); key_public = NULL; xfree (cardsn); if (err) { if (opt.verbose) gcry_log_debugsxp ("pubkey", key_public); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_CURVE || gpg_err_code (err) == GPG_ERR_INV_CURVE) { /* For example a Brainpool curve or a curve we don't * support at all but a smartcard lists that curve. * We ignore them. */ } else { xfree (serialno); free_strlist (card_list); goto out; } } else key_counter++; } xfree (serialno); free_strlist (card_list); } scd_out: /* Then look at all the registered and non-disabled keys. */ err = open_control_file (&cf, 0); if (err) goto out; while (!read_control_file_item (cf)) { unsigned char grip[20]; if (!cf->item.valid) continue; /* Should not happen. */ if (cf->item.disabled) continue; assert (strlen (cf->item.hexgrip) == 40); hex2bin (cf->item.hexgrip, grip, sizeof (grip)); err = agent_public_key_from_file (ctrl, grip, &key_public); if (err) { log_error ("%s:%d: key '%s' skipped: %s\n", cf->fname, cf->lnr, cf->item.hexgrip, gpg_strerror (err)); continue; } err = ssh_send_key_public (key_blobs, key_public, NULL); if (err) goto out; gcry_sexp_release (key_public); key_public = NULL; key_counter++; } err = 0; ret = es_fseek (key_blobs, 0, SEEK_SET); if (ret) { err = gpg_error_from_syserror (); goto out; } out: /* Send response. */ gcry_sexp_release (key_public); if (!err) { ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER); if (!ret_err) ret_err = stream_write_uint32 (response, key_counter); if (!ret_err) ret_err = stream_copy (response, key_blobs); } else { log_error ("ssh request identities failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); } es_fclose (key_blobs); close_control_file (cf); return ret_err; } /* This function hashes the data contained in DATA of size DATA_N according to the message digest algorithm specified by MD_ALGORITHM and writes the message digest to HASH, which needs to large enough for the digest. */ static gpg_error_t data_hash (unsigned char *data, size_t data_n, int md_algorithm, unsigned char *hash) { gcry_md_hash_buffer (md_algorithm, hash, data, data_n); return 0; } /* This function signs the data described by CTRL. If HASH is not NULL, (HASH,HASHLEN) overrides the hash stored in CTRL. This is to allow the use of signature algorithms that implement the hashing internally (e.g. Ed25519). On success the created signature is stored in ssh format at R_SIG and it's size at R_SIGLEN; the caller must use es_free to releaase this memory. */ static gpg_error_t data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec, const void *hash, size_t hashlen, unsigned char **r_sig, size_t *r_siglen) { gpg_error_t err; gcry_sexp_t signature_sexp = NULL; estream_t stream = NULL; void *blob = NULL; size_t bloblen; char hexgrip[40+1]; *r_sig = NULL; *r_siglen = 0; /* Quick check to see whether we have a valid keygrip and convert it to hex. */ if (!ctrl->have_keygrip) { err = gpg_error (GPG_ERR_NO_SECKEY); goto out; } bin2hex (ctrl->keygrip, 20, hexgrip); /* Ask for confirmation if needed. */ if (confirm_flag_from_sshcontrol (hexgrip)) { gcry_sexp_t key; char *fpr, *prompt; char *comment = NULL; err = agent_raw_key_from_file (ctrl, ctrl->keygrip, &key); if (err) goto out; err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &fpr); if (!err) { gcry_sexp_t tmpsxp = gcry_sexp_find_token (key, "comment", 0); if (tmpsxp) comment = gcry_sexp_nth_string (tmpsxp, 1); gcry_sexp_release (tmpsxp); } gcry_sexp_release (key); if (err) goto out; prompt = xtryasprintf (L_("An ssh process requested the use of key%%0A" " %s%%0A" " (%s)%%0A" "Do you want to allow this?"), fpr, comment? comment:""); xfree (fpr); gcry_free (comment); err = agent_get_confirmation (ctrl, prompt, L_("Allow"), L_("Deny"), 0); xfree (prompt); if (err) goto out; } /* Create signature. */ ctrl->use_auth_call = 1; err = agent_pksign_do (ctrl, NULL, L_("Please enter the passphrase " "for the ssh key%%0A %F%%0A (%c)"), &signature_sexp, CACHE_MODE_SSH, ttl_from_sshcontrol, hash, hashlen); ctrl->use_auth_call = 0; if (err) goto out; stream = es_fopenmem (0, "r+b"); if (!stream) { err = gpg_error_from_syserror (); goto out; } err = stream_write_cstring (stream, spec->ssh_identifier); if (err) goto out; err = spec->signature_encoder (spec, stream, signature_sexp); if (err) goto out; err = es_fclose_snatch (stream, &blob, &bloblen); if (err) goto out; stream = NULL; *r_sig = blob; blob = NULL; *r_siglen = bloblen; out: xfree (blob); es_fclose (stream); gcry_sexp_release (signature_sexp); return err; } /* Handler for the "sign_request" command. */ static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response) { gcry_sexp_t key = NULL; ssh_key_type_spec_t spec; unsigned char hash[MAX_DIGEST_LEN]; unsigned int hash_n; unsigned char key_grip[20]; unsigned char *key_blob = NULL; u32 key_blob_size; unsigned char *data = NULL; unsigned char *sig = NULL; size_t sig_n; u32 data_size; gpg_error_t err; gpg_error_t ret_err; int hash_algo; /* Receive key. */ err = stream_read_string (request, 0, &key_blob, &key_blob_size); if (err) goto out; err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, &spec); if (err) goto out; /* Receive data to sign. */ err = stream_read_string (request, 0, &data, &data_size); if (err) goto out; /* Flag processing. */ { u32 flags; err = stream_read_uint32 (request, &flags); if (err) goto out; if (spec.algo == GCRY_PK_RSA) { if ((flags & SSH_AGENT_RSA_SHA2_512)) { flags &= ~SSH_AGENT_RSA_SHA2_512; spec.ssh_identifier = "rsa-sha2-512"; spec.hash_algo = GCRY_MD_SHA512; } if ((flags & SSH_AGENT_RSA_SHA2_256)) { /* Note: We prefer SHA256 over SHA512. */ flags &= ~SSH_AGENT_RSA_SHA2_256; spec.ssh_identifier = "rsa-sha2-256"; spec.hash_algo = GCRY_MD_SHA256; } } /* Some flag is present that we do not know about. Note that * processed or known flags have been cleared at this point. */ if (flags) { err = gpg_error (GPG_ERR_UNKNOWN_OPTION); goto out; } } hash_algo = spec.hash_algo; if (!hash_algo) hash_algo = GCRY_MD_SHA1; /* Use the default. */ ctrl->digest.algo = hash_algo; if ((spec.flags & SPEC_FLAG_USE_PKCS1V2)) ctrl->digest.raw_value = 0; else ctrl->digest.raw_value = 1; /* Calculate key grip. */ err = ssh_key_grip (key, key_grip); if (err) goto out; ctrl->have_keygrip = 1; memcpy (ctrl->keygrip, key_grip, 20); /* Hash data unless we use EdDSA. */ if ((spec.flags & SPEC_FLAG_IS_EdDSA)) { ctrl->digest.valuelen = 0; } else { hash_n = gcry_md_get_algo_dlen (hash_algo); if (!hash_n) { err = gpg_error (GPG_ERR_INTERNAL); goto out; } err = data_hash (data, data_size, hash_algo, hash); if (err) goto out; memcpy (ctrl->digest.value, hash, hash_n); ctrl->digest.valuelen = hash_n; } /* Sign data. */ if ((spec.flags & SPEC_FLAG_IS_EdDSA)) err = data_sign (ctrl, &spec, data, data_size, &sig, &sig_n); else err = data_sign (ctrl, &spec, NULL, 0, &sig, &sig_n); out: /* Done. */ if (!err) { ret_err = stream_write_byte (response, SSH_RESPONSE_SIGN_RESPONSE); if (ret_err) goto leave; ret_err = stream_write_string (response, sig, sig_n); if (ret_err) goto leave; } else { log_error ("ssh sign request failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); if (ret_err) goto leave; } leave: gcry_sexp_release (key); xfree (key_blob); xfree (data); es_free (sig); return ret_err; } /* This function extracts the comment contained in the key s-expression KEY and stores a copy in COMMENT. Returns usual error code. */ static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **r_comment) { gcry_sexp_t comment_list; *r_comment = NULL; comment_list = gcry_sexp_find_token (key, "comment", 0); if (!comment_list) return gpg_error (GPG_ERR_INV_SEXP); *r_comment = gcry_sexp_nth_string (comment_list, 1); gcry_sexp_release (comment_list); if (!*r_comment) return gpg_error (GPG_ERR_INV_SEXP); return 0; } /* This function converts the key contained in the S-Expression KEY into a buffer, which is protected by the passphrase PASSPHRASE. If PASSPHRASE is the empty passphrase, the key is not protected. Returns usual error code. */ static gpg_error_t ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase, unsigned char **buffer, size_t *buffer_n) { unsigned char *buffer_new; unsigned int buffer_new_n; gpg_error_t err; buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, NULL, 0); buffer_new = xtrymalloc_secure (buffer_new_n); if (! buffer_new) { err = gpg_error_from_syserror (); goto out; } buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, buffer_new, buffer_new_n); if (*passphrase) err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0); else { /* The key derivation function does not support zero length * strings. Store key unprotected if the user wishes so. */ *buffer = buffer_new; *buffer_n = buffer_new_n; buffer_new = NULL; err = 0; } out: xfree (buffer_new); return err; } /* Callback function to compare the first entered PIN with the one currently being entered. */ static gpg_error_t reenter_compare_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); } /* Store the ssh KEY into our local key storage and protect it after asking for a passphrase. Cache that passphrase. TTL is the maximum caching time for that key. If the key already exists in our key storage, don't do anything. When entering a key also add an entry to the sshcontrol file. */ static gpg_error_t ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec, gcry_sexp_t key, int ttl, int confirm) { gpg_error_t err; unsigned char key_grip_raw[20]; char key_grip[41]; unsigned char *buffer = NULL; size_t buffer_n; char *description = NULL; const char *description2 = L_("Please re-enter this passphrase"); char *comment = NULL; char *key_fpr = NULL; const char *initial_errtext = NULL; struct pin_entry_info_s *pi = NULL; struct pin_entry_info_s *pi2 = NULL; err = ssh_key_grip (key, key_grip_raw); if (err) goto out; bin2hex (key_grip_raw, 20, key_grip); err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &key_fpr); if (err) goto out; /* Check whether the key is already in our key storage. Don't do anything then besides (re-)adding it to sshcontrol. */ if ( !agent_key_available (key_grip_raw) ) goto key_exists; /* Yes, key is available. */ err = ssh_key_extract_comment (key, &comment); if (err) goto out; if ( asprintf (&description, L_("Please enter a passphrase to protect" " the received secret key%%0A" " %s%%0A" " %s%%0A" "within gpg-agent's key storage"), key_fpr, comment ? comment : "") < 0) { err = gpg_error_from_syserror (); goto out; } pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); if (!pi) { err = gpg_error_from_syserror (); goto out; } pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1); if (!pi2) { err = gpg_error_from_syserror (); goto out; } pi->max_length = MAX_PASSPHRASE_LEN + 1; pi->max_tries = 1; pi->with_repeat = 1; pi2->max_length = MAX_PASSPHRASE_LEN + 1; pi2->max_tries = 1; pi2->check_cb = reenter_compare_cb; pi2->check_cb_arg = pi->pin; next_try: err = agent_askpin (ctrl, description, NULL, initial_errtext, pi, NULL, 0); initial_errtext = NULL; if (err) goto out; /* Unless the passphrase is empty or the pinentry told us that it already did the repetition check, ask to confirm it. */ if (*pi->pin && !pi->repeat_okay) { err = agent_askpin (ctrl, description2, NULL, NULL, pi2, NULL, 0); if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE) { /* The re-entered one did not match and the user did not hit cancel. */ initial_errtext = L_("does not match - try again"); goto next_try; } } err = ssh_key_to_protected_buffer (key, pi->pin, &buffer, &buffer_n); if (err) goto out; /* Store this key to our key storage. We do not store a creation * timestamp because we simply do not know. */ - err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0, + err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0, 0, NULL, NULL, NULL, 0); if (err) goto out; /* Cache this passphrase. */ err = agent_put_cache (ctrl, key_grip, CACHE_MODE_SSH, pi->pin, ttl); if (err) goto out; key_exists: /* And add an entry to the sshcontrol file. */ err = add_control_entry (ctrl, spec, key_grip, key, ttl, confirm); out: if (pi2 && pi2->max_length) wipememory (pi2->pin, pi2->max_length); xfree (pi2); if (pi && pi->max_length) wipememory (pi->pin, pi->max_length); xfree (pi); xfree (buffer); xfree (comment); xfree (key_fpr); xfree (description); return err; } /* This function removes the key contained in the S-Expression KEY from the local key storage, in case it exists there. Returns usual error code. FIXME: this function is a stub. */ static gpg_error_t ssh_identity_drop (gcry_sexp_t key) { unsigned char key_grip[21] = { 0 }; gpg_error_t err; err = ssh_key_grip (key, key_grip); if (err) goto out; key_grip[sizeof (key_grip) - 1] = 0; /* FIXME: What to do here - forgetting the passphrase or deleting the key from key cache? */ out: return err; } /* Handler for the "add_identity" command. */ static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; ssh_key_type_spec_t spec; gpg_error_t err; gcry_sexp_t key; unsigned char b; int confirm; int ttl; confirm = 0; key = NULL; ttl = 0; /* FIXME? */ err = ssh_receive_key (request, &key, 1, 1, &spec); if (err) goto out; while (1) { err = stream_read_byte (request, &b); if (err) { if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; break; } switch (b) { case SSH_OPT_CONSTRAIN_LIFETIME: { u32 n = 0; err = stream_read_uint32 (request, &n); if (! err) ttl = n; break; } case SSH_OPT_CONSTRAIN_CONFIRM: { confirm = 1; break; } default: /* FIXME: log/bad? */ break; } } if (err) goto out; err = ssh_identity_register (ctrl, &spec, key, ttl, confirm); out: gcry_sexp_release (key); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Handler for the "remove_identity" command. */ static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl, estream_t request, estream_t response) { unsigned char *key_blob; u32 key_blob_size; gcry_sexp_t key; gpg_error_t ret_err; gpg_error_t err; (void)ctrl; /* Receive key. */ key_blob = NULL; key = NULL; err = stream_read_string (request, 0, &key_blob, &key_blob_size); if (err) goto out; err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, NULL); if (err) goto out; err = ssh_identity_drop (key); out: xfree (key_blob); gcry_sexp_release (key); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* FIXME: stub function. Actually useful? */ static gpg_error_t ssh_identities_remove_all (void) { gpg_error_t err; err = 0; /* FIXME: shall we remove _all_ cache entries or only those registered through the ssh-agent protocol? */ return err; } /* Handler for the "remove_all_identities" command. */ static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; gpg_error_t err; (void)ctrl; (void)request; err = ssh_identities_remove_all (); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Lock agent? FIXME: stub function. */ static gpg_error_t ssh_lock (void) { gpg_error_t err; /* FIXME */ log_error ("ssh-agent's lock command is not implemented\n"); err = 0; return err; } /* Unock agent? FIXME: stub function. */ static gpg_error_t ssh_unlock (void) { gpg_error_t err; log_error ("ssh-agent's unlock command is not implemented\n"); err = 0; return err; } /* Handler for the "lock" command. */ static gpg_error_t ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; gpg_error_t err; (void)ctrl; (void)request; err = ssh_lock (); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Handler for the "unlock" command. */ static gpg_error_t ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; gpg_error_t err; (void)ctrl; (void)request; err = ssh_unlock (); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Return the request specification for the request identified by TYPE or NULL in case the requested request specification could not be found. */ static const ssh_request_spec_t * request_spec_lookup (int type) { const ssh_request_spec_t *spec; unsigned int i; for (i = 0; i < DIM (request_specs); i++) if (request_specs[i].type == type) break; if (i == DIM (request_specs)) { if (opt.verbose) log_info ("ssh request %u is not supported\n", type); spec = NULL; } else spec = request_specs + i; return spec; } /* Process a single request. The request is read from and the response is written to STREAM_SOCK. Uses CTRL as context. Returns zero in case of success, non zero in case of failure. */ static int ssh_request_process (ctrl_t ctrl, estream_t stream_sock) { const ssh_request_spec_t *spec; estream_t response = NULL; estream_t request = NULL; unsigned char request_type; gpg_error_t err; int send_err = 0; int ret; unsigned char *request_data = NULL; u32 request_data_size; u32 response_size; /* Create memory streams for request/response data. The entire request will be stored in secure memory, since it might contain secret key material. The response does not have to be stored in secure memory, since we never give out secret keys. Note: we only have little secure memory, but there is NO possibility of DoS here; only trusted clients are allowed to connect to the agent. What could happen is that the agent returns out-of-secure-memory errors on requests in case the agent's owner floods his own agent with many large messages. -moritz */ /* Retrieve request. */ err = stream_read_string (stream_sock, 1, &request_data, &request_data_size); if (err) goto out; if (opt.verbose > 1) log_info ("received ssh request of length %u\n", (unsigned int)request_data_size); if (! request_data_size) { send_err = 1; goto out; /* Broken request; FIXME. */ } request_type = request_data[0]; spec = request_spec_lookup (request_type); if (! spec) { send_err = 1; goto out; /* Unknown request; FIXME. */ } if (spec->secret_input) request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+b"); else request = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+b"); if (! request) { err = gpg_error_from_syserror (); goto out; } ret = es_setvbuf (request, NULL, _IONBF, 0); if (ret) { err = gpg_error_from_syserror (); goto out; } err = stream_write_data (request, request_data + 1, request_data_size - 1); if (err) goto out; es_rewind (request); response = es_fopenmem (0, "r+b"); if (! response) { err = gpg_error_from_syserror (); goto out; } if (opt.verbose) log_info ("ssh request handler for %s (%u) started\n", spec->identifier, spec->type); err = (*spec->handler) (ctrl, request, response); if (opt.verbose) { if (err) log_info ("ssh request handler for %s (%u) failed: %s\n", spec->identifier, spec->type, gpg_strerror (err)); else log_info ("ssh request handler for %s (%u) ready\n", spec->identifier, spec->type); } if (err) { send_err = 1; goto out; } response_size = es_ftell (response); if (opt.verbose > 1) log_info ("sending ssh response of length %u\n", (unsigned int)response_size); err = es_fseek (response, 0, SEEK_SET); if (err) { send_err = 1; goto out; } err = stream_write_uint32 (stream_sock, response_size); if (err) { send_err = 1; goto out; } err = stream_copy (stream_sock, response); if (err) goto out; err = es_fflush (stream_sock); if (err) goto out; out: if (err && es_feof (stream_sock)) log_error ("error occurred while processing request: %s\n", gpg_strerror (err)); if (send_err) { if (opt.verbose > 1) log_info ("sending ssh error response\n"); err = stream_write_uint32 (stream_sock, 1); if (err) goto leave; err = stream_write_byte (stream_sock, SSH_RESPONSE_FAILURE); if (err) goto leave; } leave: es_fclose (request); es_fclose (response); xfree (request_data); return !!err; } /* Return the peer's pid. */ static unsigned long get_client_pid (int fd) { pid_t client_pid = (pid_t)0; #ifdef SO_PEERCRED { #ifdef HAVE_STRUCT_SOCKPEERCRED_PID struct sockpeercred cr; #else struct ucred cr; #endif socklen_t cl = sizeof cr; if (!getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)) { #if defined (HAVE_STRUCT_SOCKPEERCRED_PID) || defined (HAVE_STRUCT_UCRED_PID) client_pid = cr.pid; #elif defined (HAVE_STRUCT_UCRED_CR_PID) client_pid = cr.cr_pid; #else #error "Unknown SO_PEERCRED struct" #endif } } #elif defined (LOCAL_PEERPID) { socklen_t len = sizeof (pid_t); getsockopt (fd, SOL_LOCAL, LOCAL_PEERPID, &client_pid, &len); } #elif defined (LOCAL_PEEREID) { struct unpcbid unp; socklen_t unpl = sizeof unp; if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1) client_pid = unp.unp_pid; } #elif defined (HAVE_GETPEERUCRED) { ucred_t *ucred = NULL; if (getpeerucred (fd, &ucred) != -1) { client_pid= ucred_getpid (ucred); ucred_free (ucred); } } #else (void)fd; #endif return (unsigned long)client_pid; } /* Start serving client on SOCK_CLIENT. */ void start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client) { estream_t stream_sock = NULL; gpg_error_t err; int ret; err = agent_copy_startup_env (ctrl); if (err) goto out; ctrl->client_pid = get_client_pid (FD2INT(sock_client)); /* Create stream from socket. */ stream_sock = es_fdopen (FD2INT(sock_client), "r+"); if (!stream_sock) { err = gpg_error_from_syserror (); log_error (_("failed to create stream from socket: %s\n"), gpg_strerror (err)); goto out; } /* We have to disable the estream buffering, because the estream core doesn't know about secure memory. */ ret = es_setvbuf (stream_sock, NULL, _IONBF, 0); if (ret) { err = gpg_error_from_syserror (); log_error ("failed to disable buffering " "on socket stream: %s\n", gpg_strerror (err)); goto out; } /* Main processing loop. */ while ( !ssh_request_process (ctrl, stream_sock) ) { /* Check whether we have reached EOF before trying to read another request. */ int c; c = es_fgetc (stream_sock); if (c == EOF) break; es_ungetc (c, stream_sock); } /* Reset the SCD in case it has been used. */ agent_reset_scd (ctrl); out: if (stream_sock) es_fclose (stream_sock); } #ifdef HAVE_W32_SYSTEM /* Serve one ssh-agent request. This is used for the Putty support. REQUEST is the mmapped memory which may be accessed up to a length of MAXREQLEN. Returns 0 on success which also indicates that a valid SSH response message is now in REQUEST. */ int serve_mmapped_ssh_request (ctrl_t ctrl, unsigned char *request, size_t maxreqlen) { gpg_error_t err; int send_err = 0; int valid_response = 0; const ssh_request_spec_t *spec; u32 msglen; estream_t request_stream, response_stream; if (agent_copy_startup_env (ctrl)) goto leave; /* Error setting up the environment. */ if (maxreqlen < 5) goto leave; /* Caller error. */ msglen = uint32_construct (request[0], request[1], request[2], request[3]); if (msglen < 1 || msglen > maxreqlen - 4) { log_error ("ssh message len (%u) out of range", (unsigned int)msglen); goto leave; } spec = request_spec_lookup (request[4]); if (!spec) { send_err = 1; /* Unknown request type. */ goto leave; } /* Create a stream object with the data part of the request. */ if (spec->secret_input) request_stream = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+"); else request_stream = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+"); if (!request_stream) { err = gpg_error_from_syserror (); goto leave; } /* We have to disable the estream buffering, because the estream core doesn't know about secure memory. */ if (es_setvbuf (request_stream, NULL, _IONBF, 0)) { err = gpg_error_from_syserror (); goto leave; } /* Copy the request to the stream but omit the request type. */ err = stream_write_data (request_stream, request + 5, msglen - 1); if (err) goto leave; es_rewind (request_stream); response_stream = es_fopenmem (0, "r+b"); if (!response_stream) { err = gpg_error_from_syserror (); goto leave; } if (opt.verbose) log_info ("ssh request handler for %s (%u) started\n", spec->identifier, spec->type); err = (*spec->handler) (ctrl, request_stream, response_stream); if (opt.verbose) { if (err) log_info ("ssh request handler for %s (%u) failed: %s\n", spec->identifier, spec->type, gpg_strerror (err)); else log_info ("ssh request handler for %s (%u) ready\n", spec->identifier, spec->type); } es_fclose (request_stream); request_stream = NULL; if (err) { send_err = 1; goto leave; } /* Put the response back into the mmapped buffer. */ { void *response_data; size_t response_size; /* NB: In contrast to the request-stream, the response stream includes the message type byte. */ if (es_fclose_snatch (response_stream, &response_data, &response_size)) { log_error ("snatching ssh response failed: %s", gpg_strerror (gpg_error_from_syserror ())); send_err = 1; /* Ooops. */ goto leave; } if (opt.verbose > 1) log_info ("sending ssh response of length %u\n", (unsigned int)response_size); if (response_size > maxreqlen - 4) { log_error ("invalid length of the ssh response: %s", gpg_strerror (GPG_ERR_INTERNAL)); es_free (response_data); send_err = 1; goto leave; } request[0] = response_size >> 24; request[1] = response_size >> 16; request[2] = response_size >> 8; request[3] = response_size >> 0; memcpy (request+4, response_data, response_size); es_free (response_data); valid_response = 1; } leave: if (send_err) { request[0] = 0; request[1] = 0; request[2] = 0; request[3] = 1; request[4] = SSH_RESPONSE_FAILURE; valid_response = 1; } /* Reset the SCD in case it has been used. */ agent_reset_scd (ctrl); return valid_response? 0 : -1; } #endif /*HAVE_W32_SYSTEM*/ diff --git a/agent/command.c b/agent/command.c index 4a4d6e81a..2e1d820ba 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1,3677 +1,3679 @@ /* command.c - gpg-agent command handler * Copyright (C) 2001-2011 Free Software Foundation, Inc. * Copyright (C) 2001-2013 Werner Koch * 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 . */ /* 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 #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 /* 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; }; /* 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; } 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->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; } /* 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; } 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" "\n" "Return success if at least one of the secret keys with the given\n" "keygrips is available."; static gpg_error_t cmd_havekey (assuan_context_t ctx, char *line) { gpg_error_t err; unsigned char buf[20]; do { err = parse_keygrip (ctx, line, buf); if (err) return err; if (!agent_key_available (buf)) 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); } 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 implictly 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" "\n" "The client can use this command to tell the server about the data\n" "(which usually is a hash) to be signed."; static gpg_error_t cmd_sethash (assuan_context_t ctx, char *line) { int rc; size_t n; char *p; ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *buf; char *endp; int algo; /* 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 return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm"); } else algo = 0; line = skip_options (line); if (!algo) { /* 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)) return set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL); } ctrl->digest.algo = algo; ctrl->digest.raw_value = 0; /* Parse the hash value. */ n = 0; rc = parse_hexstring (ctx, line, &n); if (rc) return rc; 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) return set_error (GPG_ERR_ASS_PARAMETER, "unsupported length of hash"); if (n > MAX_DIGEST_LEN) return set_error (GPG_ERR_ASS_PARAMETER, "hash value to long"); 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; return 0; } 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 2048)))\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); /* First inquire the parameters */ rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_KEYPARAM); if (!rc) rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM); if (rc) return rc; 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; char *dispserialno = NULL; 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); 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 standard keys. */ if ((!strcmp (keyid, "$SIGNKEYID") || !strcmp (keyid, "$ENCRKEYID") || !strcmp (keyid, "$AUTHKEYID")) && !agent_card_getattr (ctrl, keyid, &keyidbuf)) keyid = keyidbuf; rc = agent_card_readkey (ctrl, keyid, &pkbuf); 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; } agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno); /* Shadow-key is or is not available in our key storage. In * any case we need to check whether we need to update with * a new display-s/n or whatever. */ - rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0, + rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0, 0, dispserialno); 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); xfree (dispserialno); 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" " '-' - 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) { gpg_error_t err; char hexgrip[40+1]; char *fpr = NULL; int keytype; unsigned char *shadow_info = 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); 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 (!*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) { err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL); if (err) 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); 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; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); 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 (); if (list_mode == 2) { if (cf) { while (!ssh_read_control_file (cf, hexgrip, &disabled, &ttl, &confirm)) { if (hex2bin (hexgrip, grip, 20) < 0 ) continue; /* Bad hex string. */ err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1, ttl, disabled, confirm); if (err) goto leave; } } err = 0; } else if (list_mode) { char *dirname; gnupg_dirent_t dir_entry; 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; } err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh, ttl, disabled, confirm); 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; } err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh, ttl, disabled, confirm); } 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 (!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 = 0; 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; + int send, sendinfo, force, reallyforce; send = has_option (line, "--send"); sendinfo = send? 1 : has_option (line, "--sendinfo"); force = has_option (line, "--force"); + reallyforce = has_option (line, "--reallyforce"); if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); - err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force); + err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, + force, reallyforce); 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, NULL); 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) { assuan_begin_confidential (ctx); rc = assuan_inquire (ctx, "PASSPHRASE", &passphrase, &len, maxlen); assuan_end_confidential (ctx); } } 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) { wipememory (passphrase, len); 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) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); 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); 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) { 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); if (!err) - err = agent_write_private_key (grip, finalkey, finalkeylen, force, + err = agent_write_private_key (grip, finalkey, finalkeylen, force, 0, NULL, NULL, NULL, opt_timestamp); } else - err = agent_write_private_key (grip, key, realkeylen, force, + err = agent_write_private_key (grip, key, realkeylen, force, 0, NULL, 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, 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); /* 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); } static const char hlp_keytocard[] = "KEYTOCARD [--force] [ []]\n" "\n" "TIMESTAMP is required for OpenPGP and defaults to the Epoch.\n" "ECDH are the hexified ECDH parameters for OpenPGP.\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; char *argv[5]; int argc; unsigned char grip[20]; const char *serialno, *keyref; gcry_sexp_t s_skey = NULL; unsigned char *keydata; size_t keydatalen; unsigned char *shadow_info = NULL; uint64_t timestamp; char *ecdh_params = NULL; unsigned int ecdh_params_len; unsigned int extralen1, extralen2; 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]; err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, NULL, ×tamp); if (err) goto leave; if (shadow_info) { /* Key is already on a smartcard - wer can't extract it. */ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); goto leave; } /* Default to the creation time as stored in the private key. The * parameter is here so that gpg can make sure that the timestamp is * used. It is also important for OpenPGP cards to allow computing * of the fingerprint. Same goes for the ECDH params. */ if (argc > 3) { timestamp = isotime2epoch_u64 (argv[3]); if (argc > 4) { size_t n; err = parse_hexstring (ctx, argv[4], &n); if (err) goto leave; /* Badly formatted ecdh params. */ n /= 2; if (n < 4) { err = set_error (GPG_ERR_ASS_PARAMETER, "ecdh param too short"); goto leave; } ecdh_params_len = n; ecdh_params = xtrymalloc (ecdh_params_len); if (!ecdh_params) { err = gpg_error_from_syserror (); goto leave; } if (hex2bin (argv[4], ecdh_params, ecdh_params_len) < 0) { err = set_error (GPG_ERR_BUG, "hex2bin"); goto leave; } } } else if (timestamp == (uint64_t)(-1)) timestamp = isotime2epoch_u64 ("19700101T000000"); if (timestamp == (uint64_t)(-1)) { err = gpg_error (GPG_ERR_INV_TIME); goto leave; } /* Note: We can't use make_canon_sexp because we need to allocate a * few extra bytes for our hack below. The 20 for extralen2 * accounts for the sexp length of ecdh_params. */ keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); extralen1 = 30; extralen2 = ecdh_params? (20+20+ecdh_params_len) : 0; keydata = xtrymalloc_secure (keydatalen + extralen1 + extralen2); 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, extralen1, "(10:created-at10:%010llu))", (unsigned long long)timestamp); keydatalen += 10 + 19 - 1; /* Hack to insert the timestamp "ecdh-params" into the private key. */ if (ecdh_params) { snprintf (keydata+keydatalen-1, extralen2, "(11:ecdh-params%u:", ecdh_params_len); keydatalen += strlen (keydata+keydatalen-1) -1; memcpy (keydata+keydatalen, ecdh_params, ecdh_params_len); keydatalen += ecdh_params_len; memcpy (keydata+keydatalen, "))", 3); keydatalen += 2; } err = divert_writekey (ctrl, force, serialno, keyref, keydata, keydatalen); xfree (keydata); leave: xfree (ecdh_params); gcry_sexp_release (s_skey); xfree (shadow_info); 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_scd_check_running ()? 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; 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 }, { "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 }, { 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.raw_value = 0; assuan_set_io_monitor (ctx, io_monitor, NULL); agent_set_progress_cb (progress_cb, ctrl); for (;;) { pid_t client_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; } client_pid = assuan_get_pid (ctx); ctrl->server_local->connect_from_self = (client_pid == getpid ()); if (client_pid != ASSUAN_INVALID_PID) ctrl->client_pid = (unsigned long)client_pid; else ctrl->client_pid = 0; rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); continue; } } /* Reset the nonce caches. */ clear_nonce_cache (ctrl); /* Reset the SCD if needed. */ agent_reset_scd (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; } diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c index c9b70a591..fe515068c 100644 --- a/agent/cvt-openpgp.c +++ b/agent/cvt-openpgp.c @@ -1,1413 +1,1413 @@ /* cvt-openpgp.c - Convert an OpenPGP key to our internal format. * Copyright (C) 1998-2002, 2006, 2009, 2010 Free Software Foundation, Inc. * Copyright (C) 2013, 2014 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 "agent.h" #include "../common/i18n.h" #include "cvt-openpgp.h" #include "../common/host2net.h" /* Helper to pass data via the callback to do_unprotect. */ struct try_do_unprotect_arg_s { int is_v4; int is_protected; int pubkey_algo; const char *curve; int protect_algo; char *iv; int ivlen; int s2k_mode; int s2k_algo; byte *s2k_salt; u32 s2k_count; u16 desired_csum; gcry_mpi_t *skey; size_t skeysize; int skeyidx; gcry_sexp_t *r_key; }; /* Compute the keygrip from the public key and store it at GRIP. */ static gpg_error_t get_keygrip (int pubkey_algo, const char *curve, gcry_mpi_t *pkey, unsigned char *grip) { gpg_error_t err; gcry_sexp_t s_pkey = NULL; switch (pubkey_algo) { case GCRY_PK_DSA: err = gcry_sexp_build (&s_pkey, NULL, "(public-key(dsa(p%m)(q%m)(g%m)(y%m)))", pkey[0], pkey[1], pkey[2], pkey[3]); break; case GCRY_PK_ELG: err = gcry_sexp_build (&s_pkey, NULL, "(public-key(elg(p%m)(g%m)(y%m)))", pkey[0], pkey[1], pkey[2]); break; case GCRY_PK_RSA: err = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%m)(e%m)))", pkey[0], pkey[1]); break; case GCRY_PK_ECC: if (!curve) err = gpg_error (GPG_ERR_BAD_SECKEY); else { const char *format; if (!strcmp (curve, "Ed25519")) format = "(public-key(ecc(curve %s)(flags eddsa)(q%m)))"; else if (!strcmp (curve, "Curve25519")) format = "(public-key(ecc(curve %s)(flags djb-tweak)(q%m)))"; else format = "(public-key(ecc(curve %s)(q%m)))"; err = gcry_sexp_build (&s_pkey, NULL, format, curve, pkey[0]); } break; default: err = gpg_error (GPG_ERR_PUBKEY_ALGO); break; } if (!err && !gcry_pk_get_keygrip (s_pkey, grip)) err = gpg_error (GPG_ERR_INTERNAL); gcry_sexp_release (s_pkey); return err; } /* Convert a secret key given as algorithm id and an array of key parameters into our s-expression based format. Note that PUBKEY_ALGO has an gcrypt algorithm number. */ static gpg_error_t convert_secret_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey, const char *curve) { gpg_error_t err; gcry_sexp_t s_skey = NULL; *r_key = NULL; switch (pubkey_algo) { case GCRY_PK_DSA: err = gcry_sexp_build (&s_skey, NULL, "(private-key(dsa(p%m)(q%m)(g%m)(y%m)(x%m)))", skey[0], skey[1], skey[2], skey[3], skey[4]); break; case GCRY_PK_ELG: case GCRY_PK_ELG_E: err = gcry_sexp_build (&s_skey, NULL, "(private-key(elg(p%m)(g%m)(y%m)(x%m)))", skey[0], skey[1], skey[2], skey[3]); break; case GCRY_PK_RSA: case GCRY_PK_RSA_E: case GCRY_PK_RSA_S: err = gcry_sexp_build (&s_skey, NULL, "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))", skey[0], skey[1], skey[2], skey[3], skey[4], skey[5]); break; case GCRY_PK_ECC: if (!curve) err = gpg_error (GPG_ERR_BAD_SECKEY); else { const char *format; if (!strcmp (curve, "Ed25519")) /* Do not store the OID as name but the real name and the EdDSA flag. */ format = "(private-key(ecc(curve %s)(flags eddsa)(q%m)(d%M)))"; else if (!strcmp (curve, "Curve25519")) format = "(private-key(ecc(curve %s)(flags djb-tweak)(q%m)(d%m)))"; else format = "(private-key(ecc(curve %s)(q%m)(d%m)))"; err = gcry_sexp_build (&s_skey, NULL, format, curve, skey[0], skey[1]); } break; default: err = gpg_error (GPG_ERR_PUBKEY_ALGO); break; } if (!err) *r_key = s_skey; return err; } /* Convert a secret key given as algorithm id, an array of key parameters, and an S-expression of the original OpenPGP transfer key into our s-expression based format. This is a variant of convert_secret_key which is used for the openpgp-native protection mode. Note that PUBKEY_ALGO has an gcrypt algorithm number. */ static gpg_error_t convert_transfer_key (gcry_sexp_t *r_key, int pubkey_algo, gcry_mpi_t *skey, const char *curve, gcry_sexp_t transfer_key) { gpg_error_t err; gcry_sexp_t s_skey = NULL; *r_key = NULL; switch (pubkey_algo) { case GCRY_PK_DSA: err = gcry_sexp_build (&s_skey, NULL, "(protected-private-key(dsa(p%m)(q%m)(g%m)(y%m)" "(protected openpgp-native%S)))", skey[0], skey[1], skey[2], skey[3], transfer_key); break; case GCRY_PK_ELG: err = gcry_sexp_build (&s_skey, NULL, "(protected-private-key(elg(p%m)(g%m)(y%m)" "(protected openpgp-native%S)))", skey[0], skey[1], skey[2], transfer_key); break; case GCRY_PK_RSA: err = gcry_sexp_build (&s_skey, NULL, "(protected-private-key(rsa(n%m)(e%m)" "(protected openpgp-native%S)))", skey[0], skey[1], transfer_key ); break; case GCRY_PK_ECC: if (!curve) err = gpg_error (GPG_ERR_BAD_SECKEY); else { const char *format; if (!strcmp (curve, "Ed25519")) /* Do not store the OID as name but the real name and the EdDSA flag. */ format = "(protected-private-key(ecc(curve %s)(flags eddsa)(q%m)" "(protected openpgp-native%S)))"; else if (!strcmp (curve, "Curve25519")) format = "(protected-private-key(ecc(curve %s)(flags djb-tweak)(q%m)" "(protected openpgp-native%S)))"; else format = "(protected-private-key(ecc(curve %s)(q%m)" "(protected openpgp-native%S)))"; err = gcry_sexp_build (&s_skey, NULL, format, curve, skey[0], transfer_key); } break; default: err = gpg_error (GPG_ERR_PUBKEY_ALGO); break; } if (!err) *r_key = s_skey; return err; } /* Hash the passphrase and set the key. */ static gpg_error_t hash_passphrase_and_set_key (const char *passphrase, gcry_cipher_hd_t hd, int protect_algo, int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count) { gpg_error_t err; unsigned char *key; size_t keylen; keylen = gcry_cipher_get_algo_keylen (protect_algo); if (!keylen) return gpg_error (GPG_ERR_INTERNAL); key = xtrymalloc_secure (keylen); if (!key) return gpg_error_from_syserror (); err = s2k_hash_passphrase (passphrase, s2k_algo, s2k_mode, s2k_salt, s2k_count, key, keylen); if (!err) err = gcry_cipher_setkey (hd, key, keylen); xfree (key); return err; } static u16 checksum (const unsigned char *p, unsigned int n) { u16 a; for (a=0; n; n-- ) a += *p++; return a; } /* Return the number of expected key parameters. */ static void get_npkey_nskey (int pubkey_algo, size_t *npkey, size_t *nskey) { switch (pubkey_algo) { case GCRY_PK_RSA: *npkey = 2; *nskey = 6; break; case GCRY_PK_ELG: *npkey = 3; *nskey = 4; break; case GCRY_PK_ELG_E: *npkey = 3; *nskey = 4; break; case GCRY_PK_DSA: *npkey = 4; *nskey = 5; break; case GCRY_PK_ECC: *npkey = 1; *nskey = 2; break; default: *npkey = 0; *nskey = 0; break; } } /* Helper for do_unprotect. PUBKEY_ALOGO is the gcrypt algo number. On success R_NPKEY and R_NSKEY receive the number or parameters for the algorithm PUBKEY_ALGO and R_SKEYLEN the used length of SKEY. */ static int prepare_unprotect (int pubkey_algo, gcry_mpi_t *skey, size_t skeysize, int s2k_mode, unsigned int *r_npkey, unsigned int *r_nskey, unsigned int *r_skeylen) { size_t npkey, nskey, skeylen; int i; /* Count the actual number of MPIs is in the array and set the remainder to NULL for easier processing later on. */ for (skeylen = 0; skey[skeylen]; skeylen++) ; for (i=skeylen; i < skeysize; i++) skey[i] = NULL; /* Check some args. */ if (s2k_mode == 1001) { /* Stub key. */ log_info (_("secret key parts are not available\n")); return gpg_error (GPG_ERR_UNUSABLE_SECKEY); } if (gcry_pk_test_algo (pubkey_algo)) { log_info (_("public key algorithm %d (%s) is not supported\n"), pubkey_algo, gcry_pk_algo_name (pubkey_algo)); return gpg_error (GPG_ERR_PUBKEY_ALGO); } /* Get properties of the public key algorithm and do some consistency checks. Note that we need at least NPKEY+1 elements in the SKEY array. */ get_npkey_nskey (pubkey_algo, &npkey, &nskey); if (!npkey || !nskey || npkey >= nskey) return gpg_error (GPG_ERR_INTERNAL); if (skeylen <= npkey) return gpg_error (GPG_ERR_MISSING_VALUE); if (nskey+1 >= skeysize) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); /* Check that the public key parameters are all available and not encrypted. */ for (i=0; i < npkey; i++) { if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_USER1)) return gpg_error (GPG_ERR_BAD_SECKEY); } if (r_npkey) *r_npkey = npkey; if (r_nskey) *r_nskey = nskey; if (r_skeylen) *r_skeylen = skeylen; return 0; } /* Note that this function modifies SKEY. SKEYSIZE is the allocated size of the array including the NULL item; this is used for a bounds check. On success a converted key is stored at R_KEY. */ static int do_unprotect (const char *passphrase, int pkt_version, int pubkey_algo, int is_protected, const char *curve, gcry_mpi_t *skey, size_t skeysize, int protect_algo, void *protect_iv, size_t protect_ivlen, int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count, u16 desired_csum, gcry_sexp_t *r_key) { gpg_error_t err; unsigned int npkey, nskey, skeylen; gcry_cipher_hd_t cipher_hd = NULL; u16 actual_csum; size_t nbytes; int i; gcry_mpi_t tmpmpi; *r_key = NULL; err = prepare_unprotect (pubkey_algo, skey, skeysize, s2k_mode, &npkey, &nskey, &skeylen); if (err) return err; /* Check whether SKEY is at all protected. If it is not protected merely verify the checksum. */ if (!is_protected) { actual_csum = 0; for (i=npkey; i < nskey; i++) { if (!skey[i] || gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_USER1)) return gpg_error (GPG_ERR_BAD_SECKEY); if (gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE)) { unsigned int nbits; const unsigned char *buffer; buffer = gcry_mpi_get_opaque (skey[i], &nbits); nbytes = (nbits+7)/8; actual_csum += checksum (buffer, nbytes); } else { unsigned char *buffer; err = gcry_mpi_aprint (GCRYMPI_FMT_PGP, &buffer, &nbytes, skey[i]); if (!err) actual_csum += checksum (buffer, nbytes); xfree (buffer); } if (err) return err; } if (actual_csum != desired_csum) return gpg_error (GPG_ERR_CHECKSUM); goto do_convert; } if (gcry_cipher_test_algo (protect_algo)) { /* The algorithm numbers are Libgcrypt numbers but fortunately the OpenPGP algorithm numbers map one-to-one to the Libgcrypt numbers. */ log_info (_("protection algorithm %d (%s) is not supported\n"), protect_algo, gnupg_cipher_algo_name (protect_algo)); return gpg_error (GPG_ERR_CIPHER_ALGO); } if (gcry_md_test_algo (s2k_algo)) { log_info (_("protection hash algorithm %d (%s) is not supported\n"), s2k_algo, gcry_md_algo_name (s2k_algo)); return gpg_error (GPG_ERR_DIGEST_ALGO); } err = gcry_cipher_open (&cipher_hd, protect_algo, GCRY_CIPHER_MODE_CFB, (GCRY_CIPHER_SECURE | (protect_algo >= 100 ? 0 : GCRY_CIPHER_ENABLE_SYNC))); if (err) { log_error ("failed to open cipher_algo %d: %s\n", protect_algo, gpg_strerror (err)); return err; } err = hash_passphrase_and_set_key (passphrase, cipher_hd, protect_algo, s2k_mode, s2k_algo, s2k_salt, s2k_count); if (err) { gcry_cipher_close (cipher_hd); return err; } gcry_cipher_setiv (cipher_hd, protect_iv, protect_ivlen); actual_csum = 0; if (pkt_version >= 4) { int ndata; unsigned int ndatabits; const unsigned char *p; unsigned char *data; u16 csum_pgp7 = 0; if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE )) { gcry_cipher_close (cipher_hd); return gpg_error (GPG_ERR_BAD_SECKEY); } p = gcry_mpi_get_opaque (skey[npkey], &ndatabits); ndata = (ndatabits+7)/8; if (ndata > 1) csum_pgp7 = buf16_to_u16 (p+ndata-2); data = xtrymalloc_secure (ndata); if (!data) { err = gpg_error_from_syserror (); gcry_cipher_close (cipher_hd); return err; } gcry_cipher_decrypt (cipher_hd, data, ndata, p, ndata); p = data; if (is_protected == 2) { /* This is the new SHA1 checksum method to detect tampering with the key as used by the Klima/Rosa attack. */ desired_csum = 0; actual_csum = 1; /* Default to bad checksum. */ if (ndata < 20) log_error ("not enough bytes for SHA-1 checksum\n"); else { gcry_md_hd_t h; if (gcry_md_open (&h, GCRY_MD_SHA1, 1)) BUG(); /* Algo not available. */ gcry_md_write (h, data, ndata - 20); gcry_md_final (h); if (!memcmp (gcry_md_read (h, GCRY_MD_SHA1), data+ndata-20, 20)) actual_csum = 0; /* Digest does match. */ gcry_md_close (h); } } else { /* Old 16 bit checksum method. */ if (ndata < 2) { log_error ("not enough bytes for checksum\n"); desired_csum = 0; actual_csum = 1; /* Mark checksum bad. */ } else { desired_csum = buf16_to_u16 (data+ndata-2); actual_csum = checksum (data, ndata-2); if (desired_csum != actual_csum) { /* This is a PGP 7.0.0 workaround */ desired_csum = csum_pgp7; /* Take the encrypted one. */ } } } /* Better check it here. Otherwise the gcry_mpi_scan would fail because the length may have an arbitrary value. */ if (desired_csum == actual_csum) { for (i=npkey; i < nskey; i++ ) { if (gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_PGP, p, ndata, &nbytes)) { /* Checksum was okay, but not correctly decrypted. */ desired_csum = 0; actual_csum = 1; /* Mark checksum bad. */ break; } gcry_mpi_release (skey[i]); skey[i] = tmpmpi; ndata -= nbytes; p += nbytes; } skey[i] = NULL; skeylen = i; assert (skeylen <= skeysize); /* Note: at this point NDATA should be 2 for a simple checksum or 20 for the sha1 digest. */ } xfree(data); } else /* Packet version <= 3. */ { unsigned char *buffer; for (i = npkey; i < nskey; i++) { const unsigned char *p; size_t ndata; unsigned int ndatabits; if (!skey[i] || !gcry_mpi_get_flag (skey[i], GCRYMPI_FLAG_OPAQUE)) { gcry_cipher_close (cipher_hd); return gpg_error (GPG_ERR_BAD_SECKEY); } p = gcry_mpi_get_opaque (skey[i], &ndatabits); ndata = (ndatabits+7)/8; if (!(ndata >= 2) || !(ndata == (buf16_to_ushort (p) + 7)/8 + 2)) { gcry_cipher_close (cipher_hd); return gpg_error (GPG_ERR_BAD_SECKEY); } buffer = xtrymalloc_secure (ndata); if (!buffer) { err = gpg_error_from_syserror (); gcry_cipher_close (cipher_hd); return err; } gcry_cipher_sync (cipher_hd); buffer[0] = p[0]; buffer[1] = p[1]; gcry_cipher_decrypt (cipher_hd, buffer+2, ndata-2, p+2, ndata-2); actual_csum += checksum (buffer, ndata); err = gcry_mpi_scan (&tmpmpi, GCRYMPI_FMT_PGP, buffer, ndata, &ndata); xfree (buffer); if (err) { /* Checksum was okay, but not correctly decrypted. */ desired_csum = 0; actual_csum = 1; /* Mark checksum bad. */ break; } gcry_mpi_release (skey[i]); skey[i] = tmpmpi; } } gcry_cipher_close (cipher_hd); /* Now let's see whether we have used the correct passphrase. */ if (actual_csum != desired_csum) return gpg_error (GPG_ERR_BAD_PASSPHRASE); do_convert: if (nskey != skeylen) err = gpg_error (GPG_ERR_BAD_SECKEY); else err = convert_secret_key (r_key, pubkey_algo, skey, curve); if (err) return err; /* The checksum may fail, thus we also check the key itself. */ err = gcry_pk_testkey (*r_key); if (err) { gcry_sexp_release (*r_key); *r_key = NULL; return gpg_error (GPG_ERR_BAD_PASSPHRASE); } return 0; } /* Callback function to try the unprotection from the passphrase query code. */ static gpg_error_t try_do_unprotect_cb (struct pin_entry_info_s *pi) { gpg_error_t err; struct try_do_unprotect_arg_s *arg = pi->check_cb_arg; err = do_unprotect (pi->pin, arg->is_v4? 4:3, arg->pubkey_algo, arg->is_protected, arg->curve, arg->skey, arg->skeysize, arg->protect_algo, arg->iv, arg->ivlen, arg->s2k_mode, arg->s2k_algo, arg->s2k_salt, arg->s2k_count, arg->desired_csum, arg->r_key); /* SKEY may be modified now, thus we need to re-compute SKEYIDX. */ for (arg->skeyidx = 0; (arg->skeyidx < arg->skeysize && arg->skey[arg->skeyidx]); arg->skeyidx++) ; return err; } /* See convert_from_openpgp for the core of the description. This function adds an optional PASSPHRASE argument and uses this to silently decrypt the key; CACHE_NONCE and R_PASSPHRASE must both be NULL in this mode. */ static gpg_error_t convert_from_openpgp_main (ctrl_t ctrl, gcry_sexp_t s_pgp, int dontcare_exist, unsigned char *grip, const char *prompt, const char *cache_nonce, const char *passphrase, unsigned char **r_key, char **r_passphrase) { gpg_error_t err; int unattended; int from_native; gcry_sexp_t top_list; gcry_sexp_t list = NULL; const char *value; size_t valuelen; char *string; int idx; int is_v4, is_protected; int pubkey_algo; int protect_algo = 0; char iv[16]; int ivlen = 0; int s2k_mode = 0; int s2k_algo = 0; byte s2k_salt[8]; u32 s2k_count = 0; size_t npkey, nskey; gcry_mpi_t skey[10]; /* We support up to 9 parameters. */ char *curve = NULL; u16 desired_csum; int skeyidx = 0; gcry_sexp_t s_skey = NULL; *r_key = NULL; if (r_passphrase) *r_passphrase = NULL; unattended = !r_passphrase; from_native = (!cache_nonce && passphrase && !r_passphrase); top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0); if (!top_list) goto bad_seckey; list = gcry_sexp_find_token (top_list, "version", 0); if (!list) goto bad_seckey; value = gcry_sexp_nth_data (list, 1, &valuelen); if (!value || valuelen != 1 || !(value[0] == '3' || value[0] == '4')) goto bad_seckey; is_v4 = (value[0] == '4'); gcry_sexp_release (list); list = gcry_sexp_find_token (top_list, "protection", 0); if (!list) goto bad_seckey; value = gcry_sexp_nth_data (list, 1, &valuelen); if (!value) goto bad_seckey; if (valuelen == 4 && !memcmp (value, "sha1", 4)) is_protected = 2; else if (valuelen == 3 && !memcmp (value, "sum", 3)) is_protected = 1; else if (valuelen == 4 && !memcmp (value, "none", 4)) is_protected = 0; else goto bad_seckey; if (is_protected) { string = gcry_sexp_nth_string (list, 2); if (!string) goto bad_seckey; protect_algo = gcry_cipher_map_name (string); xfree (string); value = gcry_sexp_nth_data (list, 3, &valuelen); if (!value || !valuelen || valuelen > sizeof iv) goto bad_seckey; memcpy (iv, value, valuelen); ivlen = valuelen; string = gcry_sexp_nth_string (list, 4); if (!string) goto bad_seckey; s2k_mode = strtol (string, NULL, 10); xfree (string); string = gcry_sexp_nth_string (list, 5); if (!string) goto bad_seckey; s2k_algo = gcry_md_map_name (string); xfree (string); value = gcry_sexp_nth_data (list, 6, &valuelen); if (!value || !valuelen || valuelen > sizeof s2k_salt) goto bad_seckey; memcpy (s2k_salt, value, valuelen); string = gcry_sexp_nth_string (list, 7); if (!string) goto bad_seckey; s2k_count = strtoul (string, NULL, 10); xfree (string); } gcry_sexp_release (list); list = gcry_sexp_find_token (top_list, "algo", 0); if (!list) goto bad_seckey; string = gcry_sexp_nth_string (list, 1); if (!string) goto bad_seckey; pubkey_algo = gcry_pk_map_name (string); xfree (string); get_npkey_nskey (pubkey_algo, &npkey, &nskey); if (!npkey || !nskey || npkey >= nskey) goto bad_seckey; if (npkey == 1) /* This is ECC */ { gcry_sexp_release (list); list = gcry_sexp_find_token (top_list, "curve", 0); if (!list) goto bad_seckey; curve = gcry_sexp_nth_string (list, 1); if (!curve) goto bad_seckey; } gcry_sexp_release (list); list = gcry_sexp_find_token (top_list, "skey", 0); if (!list) goto bad_seckey; for (idx=0;;) { int is_enc; value = gcry_sexp_nth_data (list, ++idx, &valuelen); if (!value && skeyidx >= npkey) break; /* Ready. */ /* Check for too many parameters. Note that depending on the protection mode and version number we may see less than NSKEY (but at least NPKEY+1) parameters. */ if (idx >= 2*nskey) goto bad_seckey; if (skeyidx >= DIM (skey)-1) goto bad_seckey; if (!value || valuelen != 1 || !(value[0] == '_' || value[0] == 'e')) goto bad_seckey; is_enc = (value[0] == 'e'); value = gcry_sexp_nth_data (list, ++idx, &valuelen); if (!value || !valuelen) goto bad_seckey; if (is_enc) { /* Encrypted parameters need to be stored as opaque. */ skey[skeyidx] = gcry_mpi_set_opaque_copy (NULL, value, valuelen*8); if (!skey[skeyidx]) goto outofmem; gcry_mpi_set_flag (skey[skeyidx], GCRYMPI_FLAG_USER1); } else { if (gcry_mpi_scan (skey + skeyidx, GCRYMPI_FMT_STD, value, valuelen, NULL)) goto bad_seckey; } skeyidx++; } skey[skeyidx++] = NULL; gcry_sexp_release (list); list = gcry_sexp_find_token (top_list, "csum", 0); if (list) { string = gcry_sexp_nth_string (list, 1); if (!string) goto bad_seckey; desired_csum = strtoul (string, NULL, 10); xfree (string); } else desired_csum = 0; gcry_sexp_release (list); list = NULL; gcry_sexp_release (top_list); top_list = NULL; #if 0 log_debug ("XXX is_v4=%d\n", is_v4); log_debug ("XXX pubkey_algo=%d\n", pubkey_algo); log_debug ("XXX is_protected=%d\n", is_protected); log_debug ("XXX protect_algo=%d\n", protect_algo); log_printhex (iv, ivlen, "XXX iv"); log_debug ("XXX ivlen=%d\n", ivlen); log_debug ("XXX s2k_mode=%d\n", s2k_mode); log_debug ("XXX s2k_algo=%d\n", s2k_algo); log_printhex (s2k_salt, sizeof s2k_salt, "XXX s2k_salt"); log_debug ("XXX s2k_count=%lu\n", (unsigned long)s2k_count); log_debug ("XXX curve='%s'\n", curve); for (idx=0; skey[idx]; idx++) gcry_log_debugmpi (gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_USER1) ? "skey(e)" : "skey(_)", skey[idx]); #endif /*0*/ err = get_keygrip (pubkey_algo, curve, skey, grip); if (err) goto leave; if (!dontcare_exist && !from_native && !agent_key_available (grip)) { err = gpg_error (GPG_ERR_EEXIST); goto leave; } if (unattended && !from_native) { err = prepare_unprotect (pubkey_algo, skey, DIM(skey), s2k_mode, NULL, NULL, NULL); if (err) goto leave; err = convert_transfer_key (&s_skey, pubkey_algo, skey, curve, s_pgp); if (err) goto leave; } else { struct pin_entry_info_s *pi; struct try_do_unprotect_arg_s pi_arg; pi = xtrycalloc_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_do_unprotect_cb; pi->check_cb_arg = &pi_arg; pi_arg.is_v4 = is_v4; pi_arg.is_protected = is_protected; pi_arg.pubkey_algo = pubkey_algo; pi_arg.curve = curve; pi_arg.protect_algo = protect_algo; pi_arg.iv = iv; pi_arg.ivlen = ivlen; pi_arg.s2k_mode = s2k_mode; pi_arg.s2k_algo = s2k_algo; pi_arg.s2k_salt = s2k_salt; pi_arg.s2k_count = s2k_count; pi_arg.desired_csum = desired_csum; pi_arg.skey = skey; pi_arg.skeysize = DIM (skey); pi_arg.skeyidx = skeyidx; pi_arg.r_key = &s_skey; err = gpg_error (GPG_ERR_BAD_PASSPHRASE); if (!is_protected) { err = try_do_unprotect_cb (pi); if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE) err = gpg_error (GPG_ERR_BAD_SECKEY); } else if (cache_nonce) { char *cache_value; cache_value = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE); if (cache_value) { if (strlen (cache_value) < pi->max_length) strcpy (pi->pin, cache_value); xfree (cache_value); } if (*pi->pin) err = try_do_unprotect_cb (pi); } else if (from_native) { if (strlen (passphrase) < pi->max_length) strcpy (pi->pin, passphrase); err = try_do_unprotect_cb (pi); } if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE && !from_native) err = agent_askpin (ctrl, prompt, NULL, NULL, pi, NULL, 0); skeyidx = pi_arg.skeyidx; if (!err && r_passphrase && is_protected) { *r_passphrase = xtrystrdup (pi->pin); if (!*r_passphrase) err = gpg_error_from_syserror (); } xfree (pi); if (err) goto leave; } /* Save some memory and get rid of the SKEY array now. */ for (idx=0; idx < skeyidx; idx++) gcry_mpi_release (skey[idx]); skeyidx = 0; /* Note that the padding is not required - we use it only because that function allows us to create the result in secure memory. */ err = make_canon_sexp_pad (s_skey, 1, r_key, NULL); leave: xfree (curve); gcry_sexp_release (s_skey); gcry_sexp_release (list); gcry_sexp_release (top_list); for (idx=0; idx < skeyidx; idx++) gcry_mpi_release (skey[idx]); if (err && r_passphrase) { xfree (*r_passphrase); *r_passphrase = NULL; } return err; bad_seckey: err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; outofmem: err = gpg_error (GPG_ERR_ENOMEM); goto leave; } /* Convert an OpenPGP transfer key into our internal format. Before asking for a passphrase we check whether the key already exists in our key storage. S_PGP is the OpenPGP key in transfer format. If CACHE_NONCE is given the passphrase will be looked up in the cache. On success R_KEY will receive a canonical encoded S-expression with the unprotected key in our internal format; the caller needs to release that memory. The passphrase used to decrypt the OpenPGP key will be returned at R_PASSPHRASE; the caller must release this passphrase. If R_PASSPHRASE is NULL the unattended conversion mode will be used which uses the openpgp-native protection format for the key. The keygrip will be stored at the 20 byte buffer pointed to by GRIP. On error NULL is stored at all return arguments. */ gpg_error_t convert_from_openpgp (ctrl_t ctrl, gcry_sexp_t s_pgp, int dontcare_exist, unsigned char *grip, const char *prompt, const char *cache_nonce, unsigned char **r_key, char **r_passphrase) { return convert_from_openpgp_main (ctrl, s_pgp, dontcare_exist, grip, prompt, cache_nonce, NULL, r_key, r_passphrase); } /* This function is called by agent_unprotect to re-protect an openpgp-native protected private-key into the standard private-key protection format. */ gpg_error_t convert_from_openpgp_native (ctrl_t ctrl, gcry_sexp_t s_pgp, const char *passphrase, unsigned char **r_key) { gpg_error_t err; unsigned char grip[20]; if (!passphrase) return gpg_error (GPG_ERR_INTERNAL); err = convert_from_openpgp_main (ctrl, s_pgp, 0, grip, NULL, NULL, passphrase, r_key, NULL); /* On success try to re-write the key. */ if (!err) { if (*passphrase) { unsigned char *protectedkey = NULL; size_t protectedkeylen; if (!agent_protect (*r_key, passphrase, &protectedkey, &protectedkeylen, ctrl->s2k_count)) agent_write_private_key (grip, protectedkey, protectedkeylen, - 1/*force*/, NULL, NULL, NULL, 0); + 1/*force*/, 0, NULL, NULL, NULL, 0); xfree (protectedkey); } else { /* Empty passphrase: write key without protection. */ agent_write_private_key (grip, *r_key, gcry_sexp_canon_len (*r_key, 0, NULL,NULL), - 1/*force*/, NULL, NULL, NULL, 0); + 1/*force*/, 0, NULL, NULL, NULL, 0); } } return err; } /* Given an ARRAY of mpis with the key parameters, protect the secret parameters in that array and replace them by one opaque encoded mpi. NPKEY is the number of public key parameters and NSKEY is the number of secret key parameters (including the public ones). On success the array will have NPKEY+1 elements. */ static gpg_error_t apply_protection (gcry_mpi_t *array, int npkey, int nskey, const char *passphrase, int protect_algo, void *protect_iv, size_t protect_ivlen, int s2k_mode, int s2k_algo, byte *s2k_salt, u32 s2k_count) { gpg_error_t err; int i, j; gcry_cipher_hd_t cipherhd; unsigned char *bufarr[10]; size_t narr[10]; unsigned int nbits[10]; int ndata; unsigned char *p, *data; assert (npkey < nskey); assert (nskey < DIM (bufarr)); /* Collect only the secret key parameters into BUFARR et al and compute the required size of the data buffer. */ ndata = 20; /* Space for the SHA-1 checksum. */ for (i = npkey, j = 0; i < nskey; i++, j++ ) { err = gcry_mpi_aprint (GCRYMPI_FMT_USG, bufarr+j, narr+j, array[i]); if (err) { for (i = 0; i < j; i++) xfree (bufarr[i]); return err; } nbits[j] = gcry_mpi_get_nbits (array[i]); ndata += 2 + narr[j]; } /* Allocate data buffer and stuff it with the secret key parameters. */ data = xtrymalloc_secure (ndata); if (!data) { err = gpg_error_from_syserror (); for (i = 0; i < (nskey-npkey); i++ ) xfree (bufarr[i]); return err; } p = data; for (i = 0; i < (nskey-npkey); i++ ) { *p++ = nbits[i] >> 8 ; *p++ = nbits[i]; memcpy (p, bufarr[i], narr[i]); p += narr[i]; xfree (bufarr[i]); bufarr[i] = NULL; } assert (p == data + ndata - 20); /* Append a hash of the secret key parameters. */ gcry_md_hash_buffer (GCRY_MD_SHA1, p, data, ndata - 20); /* Encrypt it. */ err = gcry_cipher_open (&cipherhd, protect_algo, GCRY_CIPHER_MODE_CFB, GCRY_CIPHER_SECURE); if (!err) err = hash_passphrase_and_set_key (passphrase, cipherhd, protect_algo, s2k_mode, s2k_algo, s2k_salt, s2k_count); if (!err) err = gcry_cipher_setiv (cipherhd, protect_iv, protect_ivlen); if (!err) err = gcry_cipher_encrypt (cipherhd, data, ndata, NULL, 0); gcry_cipher_close (cipherhd); if (err) { xfree (data); return err; } /* Replace the secret key parameters in the array by one opaque value. */ for (i = npkey; i < nskey; i++ ) { gcry_mpi_release (array[i]); array[i] = NULL; } array[npkey] = gcry_mpi_set_opaque (NULL, data, ndata*8); return 0; } /* * Examining S_KEY in S-Expression and extract data. * When REQ_PRIVATE_KEY_DATA == 1, S_KEY's CAR should be 'private-key', * but it also allows shadowed or protected versions. * On success, it returns 0, otherwise error number. * R_ALGONAME is static string which is no need to free by caller. * R_NPKEY is pointer to number of public key data. * R_NSKEY is pointer to number of private key data. * R_ELEMS is static string which is no need to free by caller. * ARRAY contains public and private key data. * ARRAYSIZE is the allocated size of the array for cross-checking. * R_CURVE is pointer to S-Expression of the curve (can be NULL). * R_FLAGS is pointer to S-Expression of the flags (can be NULL). */ 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_elems, gcry_mpi_t *array, int arraysize, gcry_sexp_t *r_curve, gcry_sexp_t *r_flags) { gpg_error_t err; gcry_sexp_t list, l2; char *name; const char *algoname, *format; int npkey, nskey; gcry_sexp_t curve = NULL; gcry_sexp_t flags = NULL; *r_curve = NULL; *r_flags = NULL; if (!req_private_key_data) { 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 ); } else list = gcry_sexp_find_token (s_key, "private-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_string (list, 0); if (!name) { gcry_sexp_release (list); return gpg_error (GPG_ERR_INV_OBJ); /* Invalid structure of object. */ } if (arraysize < 7) BUG (); /* Map NAME to a name as used by Libgcrypt. We do not use the Libgcrypt function here because we need a lowercase name and require special treatment for some algorithms. */ strlwr (name); if (!strcmp (name, "rsa")) { algoname = "rsa"; format = "ned?p?q?u?"; npkey = 2; nskey = 6; err = gcry_sexp_extract_param (list, NULL, format, array+0, array+1, array+2, array+3, array+4, array+5, NULL); } else if (!strcmp (name, "elg")) { algoname = "elg"; format = "pgyx?"; npkey = 3; nskey = 4; err = gcry_sexp_extract_param (list, NULL, format, array+0, array+1, array+2, array+3, NULL); } else if (!strcmp (name, "dsa")) { algoname = "dsa"; format = "pqgyx?"; npkey = 4; nskey = 5; err = gcry_sexp_extract_param (list, NULL, format, array+0, array+1, array+2, array+3, array+4, NULL); } else if (!strcmp (name, "ecc") || !strcmp (name, "ecdsa")) { algoname = "ecc"; format = "qd?"; npkey = 1; nskey = 2; curve = gcry_sexp_find_token (list, "curve", 0); flags = gcry_sexp_find_token (list, "flags", 0); err = gcry_sexp_extract_param (list, NULL, format, array+0, array+1, NULL); } else { err = gpg_error (GPG_ERR_PUBKEY_ALGO); } xfree (name); gcry_sexp_release (list); if (err) { gcry_sexp_release (curve); gcry_sexp_release (flags); return err; } else { *r_algoname = algoname; if (r_elems) *r_elems = format; *r_npkey = npkey; if (r_nskey) *r_nskey = nskey; *r_curve = curve; *r_flags = flags; return 0; } } /* Convert our key S_KEY into an OpenPGP key transfer format. On success a canonical encoded S-expression is stored at R_TRANSFERKEY and its length at R_TRANSFERKEYLEN; this S-expression is also padded to a multiple of 64 bits. */ gpg_error_t convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, const char *passphrase, unsigned char **r_transferkey, size_t *r_transferkeylen) { gpg_error_t err; const char *algoname; int npkey, nskey; gcry_mpi_t array[10]; gcry_sexp_t curve = NULL; gcry_sexp_t flags = NULL; char protect_iv[16]; char salt[8]; unsigned long s2k_count; int i, j; (void)ctrl; *r_transferkey = NULL; for (i=0; i < DIM (array); i++) array[i] = NULL; err = extract_private_key (s_key, 1, &algoname, &npkey, &nskey, NULL, array, DIM (array), &curve, &flags); if (err) return err; gcry_create_nonce (protect_iv, sizeof protect_iv); gcry_create_nonce (salt, sizeof salt); /* We need to use the encoded S2k count. It is not possible to encode it after it has been used because the encoding procedure may round the value up. */ s2k_count = get_standard_s2k_count_rfc4880 (); err = apply_protection (array, npkey, nskey, passphrase, GCRY_CIPHER_AES, protect_iv, sizeof protect_iv, 3, GCRY_MD_SHA1, salt, s2k_count); /* Turn it into the transfer key S-expression. Note that we always return a protected key. */ if (!err) { char countbuf[35]; membuf_t mbuf; void *format_args[10+2]; gcry_sexp_t tmpkey; gcry_sexp_t tmpsexp = NULL; snprintf (countbuf, sizeof countbuf, "%lu", s2k_count); init_membuf (&mbuf, 50); put_membuf_str (&mbuf, "(skey"); for (i=j=0; i < npkey; i++) { put_membuf_str (&mbuf, " _ %m"); format_args[j++] = array + i; } put_membuf_str (&mbuf, " e %m"); format_args[j++] = array + npkey; put_membuf_str (&mbuf, ")\n"); put_membuf (&mbuf, "", 1); tmpkey = NULL; { char *format = get_membuf (&mbuf, NULL); if (!format) err = gpg_error_from_syserror (); else err = gcry_sexp_build_array (&tmpkey, NULL, format, format_args); xfree (format); } if (!err) err = gcry_sexp_build (&tmpsexp, NULL, "(openpgp-private-key\n" " (version 1:4)\n" " (algo %s)\n" " %S%S\n" " (protection sha1 aes %b 1:3 sha1 %b %s))\n", algoname, curve, tmpkey, (int)sizeof protect_iv, protect_iv, (int)sizeof salt, salt, countbuf); gcry_sexp_release (tmpkey); if (!err) err = make_canon_sexp_pad (tmpsexp, 0, r_transferkey, r_transferkeylen); gcry_sexp_release (tmpsexp); } for (i=0; i < DIM (array); i++) gcry_mpi_release (array[i]); gcry_sexp_release (curve); gcry_sexp_release (flags); return err; } diff --git a/agent/findkey.c b/agent/findkey.c index 31868324c..a5055bcc4 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -1,1747 +1,1752 @@ /* 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 * Copyright (C) 2023 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #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 static gpg_error_t read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta, char **r_orig_key_value); static gpg_error_t is_shadowed_key (gcry_sexp_t s_skey); /* 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. */ }; /* Return the file name for the 20 byte keygrip GRIP. With FOR_NEW * create a file name for later renaming to the actual name. Return * NULL on error. */ static char * fname_from_keygrip (const unsigned char *grip, int for_new) { char hexgrip[40+4+4+1]; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, for_new? ".key.tmp" : ".key"); return make_filename_try (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); } /* Write the S-expression formatted key (BUFFER,LENGTH) to our key * storage. With FORCE passed as true an existing key with the given * GRIP will be overwritten. If SERIALNO and KEYREF are given a Token * line is added to the key if the extended format is used. If * TIMESTAMP is not zero and the key doies not yet exists it will be * recorded as creation date. */ int agent_write_private_key (const unsigned char *grip, - const void *buffer, size_t length, int force, + const void *buffer, size_t length, + int force, int reallyforce, const char *serialno, const char *keyref, const char *dispserialno, time_t timestamp) { gpg_error_t err; char *oldfname = NULL; char *fname = NULL; estream_t fp; int newkey = 0; nvc_t pk = NULL; gcry_sexp_t key = NULL; int is_regular; int remove = 0; char *token0 = NULL; char *token = NULL; char *dispserialno_buffer = NULL; char **tokenfields = NULL; int blocksigs = 0; char *orig_key_value = NULL; const char *s; int force_modify = 0; oldfname = fname_from_keygrip (grip, 0); if (!oldfname) return out_of_core (); err = read_key_file (grip, &key, &pk, &orig_key_value); if (err) { if (gpg_err_code (err) == GPG_ERR_ENOENT) newkey = 1; else { log_error ("can't open '%s': %s\n", oldfname, gpg_strerror (err)); goto leave; } } nvc_modified (pk, 1); /* Clear that flag after a read. */ if (!pk) { /* Key is still in the old format or does not exist - create a * new container. */ pk = nvc_new_private_key (); if (!pk) { err = gpg_error_from_syserror (); goto leave; } force_modify = 1; } /* Check whether we already have a regular key. */ is_regular = (key && gpg_err_code (is_shadowed_key (key)) != GPG_ERR_TRUE); /* Turn (BUFFER,LENGTH) into a gcrypt s-expression and set it into * our name value container. */ gcry_sexp_release (key); err = gcry_sexp_sscan (&key, NULL, buffer, length); if (err) goto leave; err = nvc_set_private_key (pk, key); if (err) goto leave; /* Detect whether the key value actually changed and if not clear * the modified flag. This extra check is required because * read_key_file removes the Key entry from the container and we * then create a new Key entry which might be the same, though. */ if (!force_modify && orig_key_value && (s = nvc_get_string (pk, "Key:")) && !strcmp (orig_key_value, s)) { nvc_modified (pk, 1); /* Clear that flag. */ } xfree (orig_key_value); orig_key_value = NULL; /* Check that we do not update a regular key with a shadow key. */ if (is_regular && gpg_err_code (is_shadowed_key (key)) == GPG_ERR_TRUE) { - log_info ("updating regular key file '%s'" - " by a shadow key inhibited\n", oldfname); - err = 0; /* Simply ignore the error. */ - goto leave; + if (!reallyforce) + { + log_info ("updating regular key file '%s'" + " by a shadow key inhibited\n", oldfname); + err = 0; /* Simply ignore the error. */ + goto leave; + } } /* Check that we update a regular key only in force mode. */ if (is_regular && !force) { log_error ("secret key file '%s' already exists\n", oldfname); err = gpg_error (GPG_ERR_EEXIST); goto leave; } /* If requested write a Token line. */ if (serialno && keyref) { nve_t item; size_t token0len; if (dispserialno) { /* Escape the DISPSERIALNO. */ dispserialno_buffer = percent_plus_escape (dispserialno); if (!dispserialno_buffer) { err = gpg_error_from_syserror (); goto leave; } dispserialno = dispserialno_buffer; } token0 = strconcat (serialno, " ", keyref, NULL); if (token0) token = strconcat (token0, " - ", dispserialno? dispserialno:"-", NULL); if (!token0 || !token) { err = gpg_error_from_syserror (); goto leave; } token0len = strlen (token0); for (item = nvc_lookup (pk, "Token:"); item; item = nve_next_value (item, "Token:")) if ((s = nve_value (item)) && !strncmp (s, token0, token0len)) 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; } else { /* Token exists: Update the display s/n. It may have * changed due to changes in a newer software version. */ if (s && (tokenfields = strtokenize (s, " \t\n")) && tokenfields[0] && tokenfields[1] && tokenfields[2] && tokenfields[3] && !strcmp (tokenfields[3], dispserialno)) ; /* No need to update Token entry. */ else { err = nve_set (pk, item, token); if (err) goto leave; } } } /* If a timestamp has been supplied and the key is new, write a * creation timestamp. (We douple check that there is no Created * item yet.)*/ if (timestamp && newkey && !nvc_lookup (pk, "Created:")) { gnupg_isotime_t timebuf; epoch2isotime (timebuf, timestamp); err = nvc_add (pk, "Created:", timebuf); if (err) goto leave; } /* Check whether we need to write the file at all. */ if (!nvc_modified (pk, 0)) { err = 0; goto leave; } /* Create a temporary file for writing. */ fname = fname_from_keygrip (grip, 1); fp = fname ? es_fopen (fname, "wbx,mode=-rw") : NULL; if (!fp) { err = gpg_error_from_syserror (); log_error ("can't create '%s': %s\n", fname, gpg_strerror (err)); goto leave; } err = nvc_write (pk, fp); if (!err && es_fflush (fp)) err = gpg_error_from_syserror (); if (err) { log_error ("error writing '%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; } fp = NULL; err = gnupg_rename_file (fname, oldfname, &blocksigs); if (err) { err = gpg_error_from_syserror (); log_error ("error renaming '%s': %s\n", fname, gpg_strerror (err)); remove = 1; goto leave; } bump_key_eventcounter (); leave: if (blocksigs) gnupg_unblock_all_signals (); es_fclose (fp); if (remove && fname) gnupg_remove (fname); xfree (orig_key_value); xfree (fname); xfree (oldfname); xfree (token); xfree (token0); xfree (dispserialno_buffer); xfree (tokenfields); gcry_sexp_release (key); nvc_release (pk); return err; } /* 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; } /* 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, the meta data items are stored there. However the * "Key:" item is removed. If R_ORIG_KEY_VALUE is non-NULL and the * Key items was removed, its value is stored at that R_ORIG_KEY_VALUE * and the caller must free it. Returns an error code and stores NULL * at RESULT. */ static gpg_error_t read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta, char **r_orig_key_value) { gpg_error_t err; char *fname; estream_t fp = NULL; struct stat st; unsigned char *buf = NULL; size_t buflen, erroff; gcry_sexp_t s_skey; char first; *result = NULL; if (r_keymeta) *r_keymeta = NULL; if (r_orig_key_value) *r_orig_key_value = NULL; fname = fname_from_keygrip (grip, 0); if (!fname) { err = gpg_error_from_syserror (); goto leave; } 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)); goto leave; } 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)); goto leave; } if (es_fseek (fp, 0, SEEK_SET)) { err = gpg_error_from_syserror (); log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err)); goto leave; } if (first != '(') { /* Key is in extended format. */ nvc_t pk; int line; err = nvc_parse_private_key (&pk, &line, 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 { if (r_orig_key_value) { const char *s = nvc_get_string (pk, "Key:"); if (s) { *r_orig_key_value = xtrystrdup (s); if (!*r_orig_key_value) { err = gpg_error_from_syserror (); nvc_release (pk); goto leave; } } } nvc_delete_named (pk, "Key:"); } } if (!err && r_keymeta) *r_keymeta = pk; else nvc_release (pk); goto leave; } if (fstat (es_fileno (fp), &st)) { err = gpg_error_from_syserror (); log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err)); goto leave; } 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)); goto leave; } 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)); goto leave; } /* Convert the file into a gcrypt S-expression object. */ err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); if (err) { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (err)); goto leave; } *result = s_skey; leave: es_fclose (fp); xfree (fname); xfree (buf); return err; } /* 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, uint64_t *r_timestamp) { gpg_error_t err; unsigned char *buf; size_t len, buflen, erroff; gcry_sexp_t s_skey; nvc_t keymeta = NULL; *result = NULL; if (shadow_info) *shadow_info = NULL; if (r_passphrase) *r_passphrase = NULL; if (r_timestamp) *r_timestamp = (uint64_t)(-1); err = read_key_file (grip, &s_skey, &keymeta, NULL); 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); return err; } if (r_timestamp && keymeta) { const char *created = nvc_get_string (keymeta, "Created:"); if (created) *r_timestamp = isotime2epoch_u64 (created); } nvc_release (keymeta); 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 = 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. */ { 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); } desc_text_final = NULL; if (desc_text) err = agent_modify_description (desc_text, comment, s_skey, &desc_text_final); gcry_free (comment); 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; } return err; } buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL); err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); wipememory (buf, buflen); xfree (buf); 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; } *result = s_skey; return 0; } /* 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) { 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; } /* This function returns GPG_ERR_TRUE if S_SKEY represents a shadowed * key. 0 is return for other key types. Any other error may occur * if S_SKEY is not valid. */ static gpg_error_t is_shadowed_key (gcry_sexp_t s_skey) { gpg_error_t err; unsigned char *buf; size_t buflen; err = make_canon_sexp (s_skey, &buf, &buflen); if (err) return err; if (agent_private_key_type (buf) == PRIVATE_KEY_SHADOWED) err = gpg_error (GPG_ERR_TRUE); wipememory (buf, buflen); xfree (buf); return err; } /* 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, NULL); if (!err) *result = s_skey; return err; } gpg_error_t agent_keymeta_from_file (ctrl_t ctrl, const unsigned char *grip, nvc_t *r_keymeta) { gpg_error_t err; gcry_sexp_t s_skey; (void)ctrl; err = read_key_file (grip, &s_skey, r_keymeta, NULL); gcry_sexp_release (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, 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 = !gnupg_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, 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, 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. If - * DISPSERIALNO is not NULL the human readable s/n will also be - * recorded in the key file. */ + * REALLYFORCE is also true, even a private key will be overwritten by + * a shadown key. If DISPSERIALNO is not NULL the human readable s/n + * will also be recorded in the key file. */ gpg_error_t agent_write_shadow_key (const unsigned char *grip, const char *serialno, const char *keyid, - const unsigned char *pkbuf, int force, + const unsigned char *pkbuf, int force, int reallyforce, const char *dispserialno) { 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, + err = agent_write_private_key (grip, shdkey, len, force, reallyforce, serialno, keyid, dispserialno, 0); xfree (shdkey); if (err) log_error ("error writing key: %s\n", gpg_strerror (err)); return err; } diff --git a/agent/genkey.c b/agent/genkey.c index 291862f2f..d080bac57 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -1,650 +1,650 @@ /* genkey.c - Generate a keypair * Copyright (C) 2002, 2003, 2004, 2007, 2010 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 . */ #include #include #include #include #include #include #include #include #include "agent.h" #include "../common/i18n.h" #include "../common/exechelp.h" #include "../common/sysutils.h" static int store_key (gcry_sexp_t private, const char *passphrase, int force, unsigned long s2k_count, time_t timestamp) { int rc; unsigned char *buf; size_t len; unsigned char grip[20]; if ( !gcry_pk_get_keygrip (private, grip) ) { log_error ("can't calculate keygrip\n"); return gpg_error (GPG_ERR_GENERAL); } len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = gcry_malloc_secure (len); if (!buf) return out_of_core (); len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len); assert (len); if (passphrase) { unsigned char *p; rc = agent_protect (buf, passphrase, &p, &len, s2k_count); if (rc) { xfree (buf); return rc; } xfree (buf); buf = p; } - rc = agent_write_private_key (grip, buf, len, force, + rc = agent_write_private_key (grip, buf, len, force, 0, NULL, NULL, NULL, timestamp); xfree (buf); return rc; } /* Count the number of non-alpha characters in S. Control characters and non-ascii characters are not considered. */ static size_t nonalpha_count (const char *s) { size_t n; for (n=0; *s; s++) if (isascii (*s) && ( isdigit (*s) || ispunct (*s) )) n++; return n; } /* Check PW against a list of pattern. Return 0 if PW does not match these pattern. If CHECK_CONSTRAINTS_NEW_SYMKEY is set in flags and --check-sym-passphrase-pattern has been configured, use the pattern file from that option. */ static int do_check_passphrase_pattern (ctrl_t ctrl, const char *pw, unsigned int flags) { gpg_error_t err = 0; const char *pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CHECK_PATTERN); estream_t stream_to_check_pattern = NULL; const char *argv[10]; pid_t pid; int result, i; const char *pattern; char *patternfname; (void)ctrl; pattern = opt.check_passphrase_pattern; if ((flags & CHECK_CONSTRAINTS_NEW_SYMKEY) && opt.check_sym_passphrase_pattern) pattern = opt.check_sym_passphrase_pattern; if (!pattern) return 1; /* Oops - Assume password should not be used */ if (strchr (pattern, '/') || strchr (pattern, '\\') || (*pattern == '~' && pattern[1] == '/')) patternfname = make_absfilename_try (pattern, NULL); else patternfname = make_filename_try (gnupg_sysconfdir (), pattern, NULL); if (!patternfname) { log_error ("error making filename from '%s': %s\n", pattern, gpg_strerror (gpg_error_from_syserror ())); return 1; /* Do not pass the check. */ } /* Make debugging a broken config easier by printing a useful error * message. */ if (gnupg_access (patternfname, F_OK)) { log_error ("error accessing '%s': %s\n", patternfname, gpg_strerror (gpg_error_from_syserror ())); xfree (patternfname); return 1; /* Do not pass the check. */ } i = 0; argv[i++] = "--null"; argv[i++] = "--", argv[i++] = patternfname, argv[i] = NULL; assert (i < sizeof argv); if (gnupg_spawn_process (pgmname, argv, NULL, NULL, 0, &stream_to_check_pattern, NULL, NULL, &pid)) result = 1; /* Execute error - assume password should no be used. */ else { es_set_binary (stream_to_check_pattern); if (es_fwrite (pw, strlen (pw), 1, stream_to_check_pattern) != 1) { err = gpg_error_from_syserror (); log_error (_("error writing to pipe: %s\n"), gpg_strerror (err)); result = 1; /* Error - assume password should not be used. */ } else es_fflush (stream_to_check_pattern); es_fclose (stream_to_check_pattern); if (gnupg_wait_process (pgmname, pid, 1, NULL)) result = 1; /* Helper returned an error - probably a match. */ else result = 0; /* Success; i.e. no match. */ gnupg_release_process (pid); } xfree (patternfname); return result; } static int take_this_one_anyway2 (ctrl_t ctrl, const char *desc, const char *anyway_btn) { gpg_error_t err; if (opt.enforce_passphrase_constraints) { err = agent_show_message (ctrl, desc, L_("Enter new passphrase")); if (!err) err = gpg_error (GPG_ERR_CANCELED); } else err = agent_get_confirmation (ctrl, desc, anyway_btn, L_("Enter new passphrase"), 0); return err; } static int take_this_one_anyway (ctrl_t ctrl, const char *desc) { return take_this_one_anyway2 (ctrl, desc, L_("Take this one anyway")); } /* Check whether the passphrase PW is suitable. Returns 0 if the * passphrase is suitable and true if it is not and the user should be * asked to provide a different one. If FAILED_CONSTRAINT is set, a * message describing the problem is returned at FAILED_CONSTRAINT. * The FLAGS are: * CHECK_CONSTRAINTS_NOT_EMPTY * Do not allow an empty passphrase * CHECK_CONSTRAINTS_NEW_SYMKEY * Hint that the passphrase is used for a new symmetric key. */ int check_passphrase_constraints (ctrl_t ctrl, const char *pw, unsigned int flags, char **failed_constraint) { gpg_error_t err = 0; unsigned int minlen = opt.min_passphrase_len; unsigned int minnonalpha = opt.min_passphrase_nonalpha; char *msg1 = NULL; char *msg2 = NULL; char *msg3 = NULL; int no_empty = !!(flags & CHECK_CONSTRAINTS_NOT_EMPTY); if (ctrl && ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) return 0; if (!pw) pw = ""; /* The first check is to warn about an empty passphrase. */ if (!*pw) { const char *desc = (opt.enforce_passphrase_constraints || no_empty? L_("You have not entered a passphrase!%0A" "An empty passphrase is not allowed.") : L_("You have not entered a passphrase - " "this is in general a bad idea!%0A" "Please confirm that you do not want to " "have any protection on your key.")); err = 1; if (failed_constraint) { if (opt.enforce_passphrase_constraints || no_empty) *failed_constraint = xstrdup (desc); else err = take_this_one_anyway2 (ctrl, desc, L_("Yes, protection is not needed")); } goto leave; } /* Now check the constraints and collect the error messages unless in silent mode which returns immediately. */ if (utf8_charcount (pw, -1) < minlen ) { if (!failed_constraint) { err = gpg_error (GPG_ERR_INV_PASSPHRASE); goto leave; } msg1 = xtryasprintf ( ngettext ("A passphrase should be at least %u character long.", "A passphrase should be at least %u characters long.", minlen), minlen ); if (!msg1) { err = gpg_error_from_syserror (); goto leave; } } if (nonalpha_count (pw) < minnonalpha ) { if (!failed_constraint) { err = gpg_error (GPG_ERR_INV_PASSPHRASE); goto leave; } msg2 = xtryasprintf ( ngettext ("A passphrase should contain at least %u digit or%%0A" "special character.", "A passphrase should contain at least %u digits or%%0A" "special characters.", minnonalpha), minnonalpha ); if (!msg2) { err = gpg_error_from_syserror (); goto leave; } } /* If configured check the passphrase against a list of known words and pattern. The actual test is done by an external program. The warning message is generic to give the user no hint on how to circumvent this list. */ if (*pw && (opt.check_passphrase_pattern || opt.check_sym_passphrase_pattern) && do_check_passphrase_pattern (ctrl, pw, flags)) { if (!failed_constraint) { err = gpg_error (GPG_ERR_INV_PASSPHRASE); goto leave; } msg3 = xtryasprintf (L_("A passphrase may not be a known term or match%%0A" "certain pattern.")); if (!msg3) { err = gpg_error_from_syserror (); goto leave; } } if (failed_constraint && (msg1 || msg2 || msg3)) { char *msg; size_t n; msg = strconcat (L_("Warning: You have entered an insecure passphrase."), "%0A%0A", msg1? msg1 : "", msg1? "%0A" : "", msg2? msg2 : "", msg2? "%0A" : "", msg3? msg3 : "", msg3? "%0A" : "", NULL); if (!msg) { err = gpg_error_from_syserror (); goto leave; } /* Strip a trailing "%0A". */ n = strlen (msg); if (n > 3 && !strcmp (msg + n - 3, "%0A")) msg[n-3] = 0; err = 1; if (opt.enforce_passphrase_constraints) *failed_constraint = msg; else { err = take_this_one_anyway (ctrl, msg); xfree (msg); } } leave: xfree (msg1); xfree (msg2); xfree (msg3); return err; } /* Callback function to compare the first entered PIN with the one currently being entered. */ static gpg_error_t reenter_compare_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); } /* Ask the user for a new passphrase using PROMPT. On success the function returns 0 and store the passphrase at R_PASSPHRASE; if the user opted not to use a passphrase NULL will be stored there. The user needs to free the returned string. In case of an error and error code is returned and NULL stored at R_PASSPHRASE. */ gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, char **r_passphrase) { gpg_error_t err; const char *text1 = prompt; const char *text2 = L_("Please re-enter this passphrase"); char *initial_errtext = NULL; struct pin_entry_info_s *pi, *pi2; *r_passphrase = NULL; if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) { size_t size; unsigned char *buffer; err = pinentry_loopback (ctrl, "NEW_PASSPHRASE", &buffer, &size, MAX_PASSPHRASE_LEN); if (!err) { if (size) { buffer[size] = 0; *r_passphrase = buffer; } else *r_passphrase = NULL; } return err; } pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); if (!pi) return gpg_error_from_syserror (); pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1); if (!pi2) { err = gpg_error_from_syserror (); xfree (pi); return err; } pi->max_length = MAX_PASSPHRASE_LEN + 1; pi->max_tries = 3; pi->with_qualitybar = 0; pi->with_repeat = 1; pi2->max_length = MAX_PASSPHRASE_LEN + 1; pi2->max_tries = 3; pi2->check_cb = reenter_compare_cb; pi2->check_cb_arg = pi->pin; next_try: err = agent_askpin (ctrl, text1, NULL, initial_errtext, pi, NULL, 0); xfree (initial_errtext); initial_errtext = NULL; if (!err) { if (check_passphrase_constraints (ctrl, pi->pin, 0, &initial_errtext)) { pi->failed_tries = 0; pi2->failed_tries = 0; goto next_try; } /* Unless the passphrase is empty or the pinentry told us that it already did the repetition check, ask to confirm it. */ if (*pi->pin && !pi->repeat_okay) { err = agent_askpin (ctrl, text2, NULL, NULL, pi2, NULL, 0); if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE) { /* The re-entered one did not match and the user did not hit cancel. */ initial_errtext = xtrystrdup (L_("does not match - try again")); if (initial_errtext) goto next_try; err = gpg_error_from_syserror (); } } } if (!err && *pi->pin) { /* User wants a passphrase. */ *r_passphrase = xtrystrdup (pi->pin); if (!*r_passphrase) err = gpg_error_from_syserror (); } xfree (initial_errtext); xfree (pi2); xfree (pi); return err; } /* Generate a new keypair according to the parameters given in KEYPARAM. If CACHE_NONCE is given first try to lookup a passphrase using the cache nonce. If NO_PROTECTION is true the key will not be protected by a passphrase. If OVERRIDE_PASSPHRASE is true that passphrase will be used for the new key. If TIMESTAMP is not zero it will be recorded as creation date of the key (unless extended format is disabled) . */ int agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, const char *keyparam, size_t keyparamlen, int no_protection, const char *override_passphrase, int preset, membuf_t *outbuf) { gcry_sexp_t s_keyparam, s_key, s_private, s_public; char *passphrase_buffer = NULL; const char *passphrase; int rc; size_t len; char *buf; rc = gcry_sexp_sscan (&s_keyparam, NULL, keyparam, keyparamlen); if (rc) { log_error ("failed to convert keyparam: %s\n", gpg_strerror (rc)); return gpg_error (GPG_ERR_INV_DATA); } /* Get the passphrase now, cause key generation may take a while. */ if (override_passphrase) passphrase = override_passphrase; else if (no_protection || !cache_nonce) passphrase = NULL; else { passphrase_buffer = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE); passphrase = passphrase_buffer; } if (passphrase || no_protection) ; else { rc = agent_ask_new_passphrase (ctrl, L_("Please enter the passphrase to%0A" "protect your new key"), &passphrase_buffer); if (rc) return rc; passphrase = passphrase_buffer; } rc = gcry_pk_genkey (&s_key, s_keyparam ); gcry_sexp_release (s_keyparam); if (rc) { log_error ("key generation failed: %s\n", gpg_strerror (rc)); xfree (passphrase_buffer); return rc; } /* break out the parts */ s_private = gcry_sexp_find_token (s_key, "private-key", 0); if (!s_private) { log_error ("key generation failed: invalid return value\n"); gcry_sexp_release (s_key); xfree (passphrase_buffer); return gpg_error (GPG_ERR_INV_DATA); } s_public = gcry_sexp_find_token (s_key, "public-key", 0); if (!s_public) { log_error ("key generation failed: invalid return value\n"); gcry_sexp_release (s_private); gcry_sexp_release (s_key); xfree (passphrase_buffer); return gpg_error (GPG_ERR_INV_DATA); } gcry_sexp_release (s_key); s_key = NULL; /* store the secret key */ if (DBG_CRYPTO) log_debug ("storing private key\n"); rc = store_key (s_private, passphrase, 0, ctrl->s2k_count, timestamp); if (!rc) { if (!cache_nonce) { char tmpbuf[12]; gcry_create_nonce (tmpbuf, 12); cache_nonce = bin2hex (tmpbuf, 12, NULL); } if (cache_nonce && !no_protection && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, ctrl->cache_ttl_opt_preset)) agent_write_status (ctrl, "CACHE_NONCE", cache_nonce, NULL); if (preset && !no_protection) { unsigned char grip[20]; char hexgrip[40+1]; if (gcry_pk_get_keygrip (s_private, grip)) { bin2hex(grip, 20, hexgrip); rc = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, passphrase, ctrl->cache_ttl_opt_preset); } } } xfree (passphrase_buffer); passphrase_buffer = NULL; passphrase = NULL; gcry_sexp_release (s_private); if (rc) { gcry_sexp_release (s_public); return rc; } /* return the public key */ if (DBG_CRYPTO) log_debug ("returning public key\n"); len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xtrymalloc (len); if (!buf) { gpg_error_t tmperr = out_of_core (); gcry_sexp_release (s_private); gcry_sexp_release (s_public); return tmperr; } len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, buf, len); assert (len); put_membuf (outbuf, buf, len); gcry_sexp_release (s_public); xfree (buf); return 0; } /* Apply a new passphrase to the key S_SKEY and store it. If PASSPHRASE_ADDR and *PASSPHRASE_ADDR are not NULL, use that passphrase. If PASSPHRASE_ADDR is not NULL store a newly entered passphrase at that address. */ gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey, char **passphrase_addr) { gpg_error_t err; if (passphrase_addr && *passphrase_addr) { /* Take an empty string as request not to protect the key. */ err = store_key (s_skey, **passphrase_addr? *passphrase_addr:NULL, 1, ctrl->s2k_count, 0); } else { char *pass = NULL; if (passphrase_addr) { xfree (*passphrase_addr); *passphrase_addr = NULL; } err = agent_ask_new_passphrase (ctrl, L_("Please enter the new passphrase"), &pass); if (!err) err = store_key (s_skey, pass, 1, ctrl->s2k_count, 0); if (!err && passphrase_addr) *passphrase_addr = pass; else xfree (pass); } return err; } diff --git a/agent/learncard.c b/agent/learncard.c index 0df9b69c1..f007b6da6 100644 --- a/agent/learncard.c +++ b/agent/learncard.c @@ -1,452 +1,455 @@ /* learncard.c - Handle the LEARN command * Copyright (C) 2002, 2003, 2004, 2009 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 #include #include "agent.h" #include /* Structures used by the callback mechanism to convey information pertaining to key pairs. */ struct keypair_info_s { struct keypair_info_s *next; int no_cert; char *id; /* points into grip */ char hexgrip[1]; /* The keygrip (i.e. a hash over the public key parameters) formatted as a hex string. Allocated somewhat large to also act as memeory for the above ID field. */ }; typedef struct keypair_info_s *KEYPAIR_INFO; struct kpinfo_cb_parm_s { ctrl_t ctrl; int error; KEYPAIR_INFO info; }; /* Structures used by the callback mechanism to convey information pertaining to certificates. */ struct certinfo_s { struct certinfo_s *next; int type; int done; char id[1]; }; typedef struct certinfo_s *CERTINFO; struct certinfo_cb_parm_s { ctrl_t ctrl; int error; CERTINFO info; }; /* Structures used by the callback mechanism to convey assuan status lines. */ struct sinfo_s { struct sinfo_s *next; char *data; /* Points into keyword. */ char keyword[1]; }; typedef struct sinfo_s *SINFO; struct sinfo_cb_parm_s { int error; SINFO info; }; /* Destructor for key information objects. */ static void release_keypair_info (KEYPAIR_INFO info) { while (info) { KEYPAIR_INFO tmp = info->next; xfree (info); info = tmp; } } /* Destructor for certificate information objects. */ static void release_certinfo (CERTINFO info) { while (info) { CERTINFO tmp = info->next; xfree (info); info = tmp; } } /* Destructor for status information objects. */ static void release_sinfo (SINFO info) { while (info) { SINFO tmp = info->next; xfree (info); info = tmp; } } /* This callback is used by agent_card_learn and passed the content of all KEYPAIRINFO lines. It merely stores this data away */ static void kpinfo_cb (void *opaque, const char *line) { struct kpinfo_cb_parm_s *parm = opaque; KEYPAIR_INFO item; char *p; if (parm->error) return; /* no need to gather data after an error occurred */ if ((parm->error = agent_write_status (parm->ctrl, "PROGRESS", "learncard", "k", "0", "0", NULL))) return; item = xtrycalloc (1, sizeof *item + strlen (line)); if (!item) { parm->error = out_of_core (); return; } strcpy (item->hexgrip, line); for (p = item->hexgrip; hexdigitp (p); p++) ; if (p == item->hexgrip && *p == 'X' && spacep (p+1)) { item->no_cert = 1; p++; } else if ((p - item->hexgrip) != 40 || !spacep (p)) { /* not a 20 byte hex keygrip or not followed by a space */ parm->error = gpg_error (GPG_ERR_INV_RESPONSE); xfree (item); return; } *p++ = 0; while (spacep (p)) p++; item->id = p; while (*p && !spacep (p)) p++; if (p == item->id) { /* invalid ID string */ parm->error = gpg_error (GPG_ERR_INV_RESPONSE); xfree (item); return; } *p = 0; /* ignore trailing stuff */ /* store it */ item->next = parm->info; parm->info = item; } /* This callback is used by agent_card_learn and passed the content of all CERTINFO lines. It merely stores this data away */ static void certinfo_cb (void *opaque, const char *line) { struct certinfo_cb_parm_s *parm = opaque; CERTINFO item; int type; char *p, *pend; if (parm->error) return; /* no need to gather data after an error occurred */ if ((parm->error = agent_write_status (parm->ctrl, "PROGRESS", "learncard", "c", "0", "0", NULL))) return; type = strtol (line, &p, 10); while (spacep (p)) p++; for (pend = p; *pend && !spacep (pend); pend++) ; if (p == pend || !*p) { parm->error = gpg_error (GPG_ERR_INV_RESPONSE); return; } *pend = 0; /* ignore trailing stuff */ item = xtrycalloc (1, sizeof *item + strlen (p)); if (!item) { parm->error = out_of_core (); return; } item->type = type; strcpy (item->id, p); /* store it */ item->next = parm->info; parm->info = item; } /* This callback is used by agent_card_learn and passed the content of all SINFO lines. It merely stores this data away */ static void sinfo_cb (void *opaque, const char *keyword, size_t keywordlen, const char *data) { struct sinfo_cb_parm_s *sparm = opaque; SINFO item; if (sparm->error) return; /* no need to gather data after an error occurred */ item = xtrycalloc (1, sizeof *item + keywordlen + 1 + strlen (data)); if (!item) { sparm->error = out_of_core (); return; } memcpy (item->keyword, keyword, keywordlen); item->data = item->keyword + keywordlen; *item->data = 0; item->data++; strcpy (item->data, data); /* store it */ item->next = sparm->info; sparm->info = item; } static int send_cert_back (ctrl_t ctrl, const char *id, void *assuan_context) { int rc; char *derbuf; size_t derbuflen; rc = agent_card_readcert (ctrl, id, &derbuf, &derbuflen); if (rc) { const char *action; switch (gpg_err_code (rc)) { case GPG_ERR_INV_ID: case GPG_ERR_NOT_FOUND: action = " - ignored"; break; default: action = ""; break; } if (opt.verbose || !*action) log_info ("error reading certificate '%s': %s%s\n", id? id:"?", gpg_strerror (rc), action); return *action? 0 : rc; } rc = assuan_send_data (assuan_context, derbuf, derbuflen); xfree (derbuf); if (!rc) rc = assuan_send_data (assuan_context, NULL, 0); if (!rc) rc = assuan_write_line (assuan_context, "END"); if (rc) { log_error ("sending certificate failed: %s\n", gpg_strerror (rc)); return rc; } return 0; } /* Perform the learn operation. If ASSUAN_CONTEXT is not NULL and - SEND is true all new certificates are send back via Assuan. */ + SEND is true all new certificates are send back via Assuan. If + REALLYFORCE is true a private key will be overwritten by a stub + key. */ int -agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force) +agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, + int force, int reallyforce) { int rc; struct kpinfo_cb_parm_s parm; struct certinfo_cb_parm_s cparm; struct sinfo_cb_parm_s sparm; const char *serialno = NULL; KEYPAIR_INFO item; SINFO sitem; unsigned char grip[20]; char *p; int i; static int certtype_list[] = { 111, /* Root CA */ 101, /* trusted */ 102, /* useful */ 100, /* regular */ /* We don't include 110 here because gpgsm can't handle that special root CA format. */ -1 /* end of list */ }; memset (&parm, 0, sizeof parm); memset (&cparm, 0, sizeof cparm); memset (&sparm, 0, sizeof sparm); parm.ctrl = ctrl; cparm.ctrl = ctrl; /* Now gather all the available info. */ rc = agent_card_learn (ctrl, kpinfo_cb, &parm, certinfo_cb, &cparm, sinfo_cb, &sparm); if (!rc && (parm.error || cparm.error || sparm.error)) rc = parm.error? parm.error : cparm.error? cparm.error : sparm.error; if (rc) { log_debug ("agent_card_learn failed: %s\n", gpg_strerror (rc)); goto leave; } /* Pass on all the collected status information. */ for (sitem = sparm.info; sitem; sitem = sitem->next) { if (!strcmp (sitem->keyword, "SERIALNO")) serialno = sitem->data; if (assuan_context) assuan_write_status (assuan_context, sitem->keyword, sitem->data); } if (!serialno) { rc = GPG_ERR_NOT_FOUND; goto leave; } log_info ("card has S/N: %s\n", serialno); /* Write out the certificates in a standard order. */ for (i=0; certtype_list[i] != -1; i++) { CERTINFO citem; for (citem = cparm.info; citem; citem = citem->next) { if (certtype_list[i] != citem->type) continue; if (opt.verbose) log_info (" id: %s (type=%d)\n", citem->id, citem->type); if (assuan_context && send) { rc = send_cert_back (ctrl, citem->id, assuan_context); if (rc) goto leave; citem->done = 1; } } } for (item = parm.info; item; item = item->next) { unsigned char *pubkey; if (opt.verbose) log_info (" id: %s (grip=%s)\n", item->id, item->hexgrip); if (item->no_cert) continue; /* No public key yet available. */ if (assuan_context) { agent_write_status (ctrl, "KEYPAIRINFO", item->hexgrip, item->id, NULL); } for (p=item->hexgrip, i=0; i < 20; p += 2, i++) grip[i] = xtoi_2 (p); if (!force && !agent_key_available (grip)) continue; /* The key is already available. */ /* Unknown key - store it. */ rc = agent_card_readkey (ctrl, item->id, &pubkey); if (rc) { log_debug ("agent_card_readkey failed: %s\n", gpg_strerror (rc)); goto leave; } { char *dispserialno; agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno); rc = agent_write_shadow_key (grip, serialno, item->id, pubkey, - force, dispserialno); + force, reallyforce, dispserialno); xfree (dispserialno); } xfree (pubkey); if (rc) goto leave; if (opt.verbose) log_info (" id: %s - shadow key created\n", item->id); if (assuan_context && send) { CERTINFO citem; /* only send the certificate if we have not done so before */ for (citem = cparm.info; citem; citem = citem->next) { if (!strcmp (citem->id, item->id)) break; } if (!citem) { rc = send_cert_back (ctrl, item->id, assuan_context); if (rc) goto leave; } } } leave: release_keypair_info (parm.info); release_certinfo (cparm.info); release_sinfo (sparm.info); return rc; } diff --git a/agent/protect-tool.c b/agent/protect-tool.c index c0c8cbd04..35ed9932f 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -1,834 +1,836 @@ /* 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 #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 char *get_passphrase (int promptno); static void release_passphrase (char *pw); static ARGPARSE_OPTS 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); assert (len); result = xmalloc (len); len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, result, len); 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); assert (len); result = xmalloc (len); len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len); 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 = gnupg_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); 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; 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); release_passphrase (pw); xfree (key); if (rc) { if (opt_status_msg) log_info ("[PROTECT-TOOL:] bad-passphrase\n"); log_error ("unprotecting the key failed: %s\n", gpg_strerror (rc)); 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"); } 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); 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); 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); 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 ) { ARGPARSE_ARGS pargs; int cmd = 0; const char *fname; ctrl_t ctrl; early_system_init (); 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 (gnupg_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: /* dummy */; break; default: pargs.err = ARGPARSE_PRINT_ERROR; break; } } gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (log_get_errorcount (0)) exit (2); fname = "-"; if (argc == 1) fname = *argv; else if (argc > 1) 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); } 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 void *buffer, size_t length, + int force, int reallyforce, const char *serialno, const char *keyref, const char *dispserialno, time_t timestamp) { char hexgrip[40+4+1]; char *p; + (void)reallyforce; (void)force; (void)timestamp; (void)serialno; (void)keyref; (void)dispserialno; 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/g10/call-agent.c b/g10/call-agent.c index 20235d2ad..486b7d95f 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -1,2732 +1,2738 @@ /* call-agent.c - Divert GPG operations to the agent. * Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc. * Copyright (C) 2013-2015 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 #ifdef HAVE_LOCALE_H #include #endif #include "gpg.h" #include #include "../common/util.h" #include "../common/membuf.h" #include "options.h" #include "../common/i18n.h" #include "../common/asshelp.h" #include "../common/sysutils.h" #include "call-agent.h" #include "../common/status.h" #include "../common/shareddefs.h" #include "../common/host2net.h" #define CONTROL_D ('D' - 'A' + 1) static assuan_context_t agent_ctx = NULL; static int did_early_card_test; struct default_inq_parm_s { ctrl_t ctrl; assuan_context_t ctx; struct { u32 *keyid; u32 *mainkeyid; int pubkey_algo; } keyinfo; }; struct cipher_parm_s { struct default_inq_parm_s *dflt; assuan_context_t ctx; unsigned char *ciphertext; size_t ciphertextlen; }; struct writecert_parm_s { struct default_inq_parm_s *dflt; const unsigned char *certdata; size_t certdatalen; }; struct writekey_parm_s { struct default_inq_parm_s *dflt; const unsigned char *keydata; size_t keydatalen; }; struct genkey_parm_s { struct default_inq_parm_s *dflt; const char *keyparms; const char *passphrase; }; struct import_key_parm_s { struct default_inq_parm_s *dflt; const void *key; size_t keylen; }; struct cache_nonce_parm_s { char **cache_nonce_addr; char **passwd_nonce_addr; }; static gpg_error_t learn_status_cb (void *opaque, const char *line); /* If RC is not 0, write an appropriate status message. */ static void status_sc_op_failure (int rc) { switch (gpg_err_code (rc)) { case 0: break; case GPG_ERR_CANCELED: case GPG_ERR_FULLY_CANCELED: write_status_text (STATUS_SC_OP_FAILURE, "1"); break; case GPG_ERR_BAD_PIN: write_status_text (STATUS_SC_OP_FAILURE, "2"); break; default: write_status (STATUS_SC_OP_FAILURE); break; } } /* This is the default inquiry callback. It mainly handles the Pinentry notifications. */ static gpg_error_t default_inq_cb (void *opaque, const char *line) { gpg_error_t err = 0; struct default_inq_parm_s *parm = opaque; if (has_leading_keyword (line, "PINENTRY_LAUNCHED")) { err = gpg_proxy_pinentry_notify (parm->ctrl, line); if (err) log_error (_("failed to proxy %s inquiry to client\n"), "PINENTRY_LAUNCHED"); /* We do not pass errors to avoid breaking other code. */ } else if ((has_leading_keyword (line, "PASSPHRASE") || has_leading_keyword (line, "NEW_PASSPHRASE")) && opt.pinentry_mode == PINENTRY_MODE_LOOPBACK) { assuan_begin_confidential (parm->ctx); if (have_static_passphrase ()) { const char *s = get_static_passphrase (); err = assuan_send_data (parm->ctx, s, strlen (s)); } else { char *pw; char buf[32]; if (parm->keyinfo.keyid) emit_status_need_passphrase (parm->ctrl, parm->keyinfo.keyid, parm->keyinfo.mainkeyid, parm->keyinfo.pubkey_algo); snprintf (buf, sizeof (buf), "%u", 100); write_status_text (STATUS_INQUIRE_MAXLEN, buf); pw = cpr_get_hidden ("passphrase.enter", _("Enter passphrase: ")); cpr_kill_prompt (); if (*pw == CONTROL_D && !pw[1]) err = gpg_error (GPG_ERR_CANCELED); else err = assuan_send_data (parm->ctx, pw, strlen (pw)); xfree (pw); } assuan_end_confidential (parm->ctx); } else log_debug ("ignoring gpg-agent inquiry '%s'\n", line); return err; } /* Print a warning if the server's version number is less than our version number. Returns an error code on a connection problem. */ static gpg_error_t warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode) { gpg_error_t err; char *serverversion; const char *myversion = strusage (13); err = get_assuan_server_version (ctx, mode, &serverversion); if (err) log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED? GPGRT_LOG_INFO : GPGRT_LOG_ERROR, _("error getting version from '%s': %s\n"), servername, gpg_strerror (err)); else if (compare_version_strings (serverversion, myversion) < 0) { char *warn; warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), servername, serverversion, myversion); if (!warn) err = gpg_error_from_syserror (); else { log_info (_("WARNING: %s\n"), warn); if (!opt.quiet) { log_info (_("Note: Outdated servers may lack important" " security fixes.\n")); log_info (_("Note: Use the command \"%s\" to restart them.\n"), "gpgconf --kill all"); } write_status_strings (STATUS_WARNING, "server_version_mismatch 0", " ", warn, NULL); xfree (warn); } } xfree (serverversion); return err; } #define FLAG_FOR_CARD_SUPPRESS_ERRORS 2 /* Try to connect to the agent via socket or fork it off and work by pipes. Handle the server's initial greeting */ static int start_agent (ctrl_t ctrl, int flag_for_card) { int rc; (void)ctrl; /* Not yet used. */ /* Fixme: We need a context for each thread or serialize the access to the agent. */ if (agent_ctx) rc = 0; else { rc = start_new_gpg_agent (&agent_ctx, GPG_ERR_SOURCE_DEFAULT, opt.agent_program, opt.lc_ctype, opt.lc_messages, opt.session_env, opt.autostart, opt.verbose, DBG_IPC, NULL, NULL); if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT) { static int shown; if (!shown) { shown = 1; log_info (_("no gpg-agent running in this session\n")); } } else if (!rc && !(rc = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0))) { /* Tell the agent that we support Pinentry notifications. No error checking so that it will work also with older agents. */ assuan_transact (agent_ctx, "OPTION allow-pinentry-notify", NULL, NULL, NULL, NULL, NULL, NULL); /* Tell the agent about what version we are aware. This is here used to indirectly enable GPG_ERR_FULLY_CANCELED. */ assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0", NULL, NULL, NULL, NULL, NULL, NULL); /* Pass on the pinentry mode. */ if (opt.pinentry_mode) { char *tmp = xasprintf ("OPTION pinentry-mode=%s", str_pinentry_mode (opt.pinentry_mode)); rc = assuan_transact (agent_ctx, tmp, NULL, NULL, NULL, NULL, NULL, NULL); xfree (tmp); if (rc) { log_error ("setting pinentry mode '%s' failed: %s\n", str_pinentry_mode (opt.pinentry_mode), gpg_strerror (rc)); write_status_error ("set_pinentry_mode", rc); } } /* Pass on the request origin. */ if (opt.request_origin) { char *tmp = xasprintf ("OPTION pretend-request-origin=%s", str_request_origin (opt.request_origin)); rc = assuan_transact (agent_ctx, tmp, NULL, NULL, NULL, NULL, NULL, NULL); xfree (tmp); if (rc) { log_error ("setting request origin '%s' failed: %s\n", str_request_origin (opt.request_origin), gpg_strerror (rc)); write_status_error ("set_request_origin", rc); } } /* In DE_VS mode under Windows we require that the JENT RNG * is active. */ #ifdef HAVE_W32_SYSTEM if (!rc && opt.compliance == CO_DE_VS) { if (assuan_transact (agent_ctx, "GETINFO jent_active", NULL, NULL, NULL, NULL, NULL, NULL)) { rc = gpg_error (GPG_ERR_FORBIDDEN); log_error (_("%s is not compliant with %s mode\n"), GPG_AGENT_NAME, gnupg_compliance_option_string (opt.compliance)); write_status_error ("random-compliance", rc); } } #endif /*HAVE_W32_SYSTEM*/ } } if (!rc && flag_for_card && !did_early_card_test) { /* Request the serial number of the card for an early test. */ struct agent_card_info_s info; memset (&info, 0, sizeof info); if (!(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS)) rc = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2); if (!rc) rc = assuan_transact (agent_ctx, opt.flags.use_only_openpgp_card? "SCD SERIALNO openpgp" : "SCD SERIALNO", NULL, NULL, NULL, NULL, learn_status_cb, &info); if (rc && !(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS)) { switch (gpg_err_code (rc)) { case GPG_ERR_NOT_SUPPORTED: case GPG_ERR_NO_SCDAEMON: write_status_text (STATUS_CARDCTRL, "6"); break; case GPG_ERR_OBJ_TERM_STATE: write_status_text (STATUS_CARDCTRL, "7"); break; default: write_status_text (STATUS_CARDCTRL, "4"); log_info ("selecting card failed: %s\n", gpg_strerror (rc)); break; } } if (!rc && is_status_enabled () && info.serialno) { char *buf; buf = xasprintf ("3 %s", info.serialno); write_status_text (STATUS_CARDCTRL, buf); xfree (buf); } agent_release_card_info (&info); if (!rc) did_early_card_test = 1; } return rc; } /* 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. */ static char * unescape_status_string (const unsigned char *s) { return percent_plus_unescape (s, 0xff); } /* Take a 20 byte hexencoded string and put it into the provided 20 byte buffer FPR in binary format. */ static int unhexify_fpr (const char *hexstr, unsigned char *fpr) { const char *s; int n; for (s=hexstr, n=0; hexdigitp (s); s++, n++) ; if ((*s && *s != ' ') || (n != 40)) return 0; /* no fingerprint (invalid or wrong length). */ for (s=hexstr, n=0; *s && n < 20; s += 2, n++) fpr[n] = xtoi_2 (s); return 1; /* okay */ } /* Take the serial number from LINE and return it verbatim in a newly allocated string. We make sure that only hex characters are returned. */ static char * store_serialno (const char *line) { const char *s; char *p; for (s=line; hexdigitp (s); s++) ; p = xtrymalloc (s + 1 - line); if (p) { memcpy (p, line, s-line); p[s-line] = 0; } return p; } /* This is a dummy data line callback. */ static gpg_error_t dummy_data_cb (void *opaque, const void *buffer, size_t length) { (void)opaque; (void)buffer; (void)length; return 0; } /* A simple callback used to return the serialnumber of a card. */ static gpg_error_t get_serialno_cb (void *opaque, const char *line) { char **serialno = opaque; const char *keyword = line; const char *s; int keywordlen, n; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { if (*serialno) return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */ for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n || (n&1)|| !(spacep (s) || !*s) ) return gpg_error (GPG_ERR_ASS_PARAMETER); *serialno = xtrymalloc (n+1); if (!*serialno) return out_of_core (); memcpy (*serialno, line, n); (*serialno)[n] = 0; } return 0; } /* Release the card info structure INFO. */ void agent_release_card_info (struct agent_card_info_s *info) { int i; if (!info) return; xfree (info->reader); info->reader = NULL; xfree (info->manufacturer_name); info->manufacturer_name = NULL; xfree (info->serialno); info->serialno = NULL; xfree (info->apptype); info->apptype = NULL; xfree (info->disp_name); info->disp_name = NULL; xfree (info->disp_lang); info->disp_lang = NULL; xfree (info->pubkey_url); info->pubkey_url = NULL; xfree (info->login_data); info->login_data = NULL; info->cafpr1valid = info->cafpr2valid = info->cafpr3valid = 0; info->fpr1valid = info->fpr2valid = info->fpr3valid = 0; for (i=0; i < DIM(info->private_do); i++) { xfree (info->private_do[i]); info->private_do[i] = NULL; } } static gpg_error_t learn_status_cb (void *opaque, const char *line) { struct agent_card_info_s *parm = opaque; const char *keyword = line; int keywordlen; int i; char *endp; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 6 && !memcmp (keyword, "READER", keywordlen)) { xfree (parm->reader); parm->reader = unescape_status_string (line); } else if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { xfree (parm->serialno); parm->serialno = store_serialno (line); parm->is_v2 = (strlen (parm->serialno) >= 16 && (xtoi_2 (parm->serialno+12) == 0 /* Yubikey */ || xtoi_2 (parm->serialno+12) >= 2)); } else if (keywordlen == 7 && !memcmp (keyword, "APPTYPE", keywordlen)) { xfree (parm->apptype); parm->apptype = unescape_status_string (line); } else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen)) { xfree (parm->disp_name); parm->disp_name = unescape_status_string (line); } else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen)) { xfree (parm->disp_lang); parm->disp_lang = unescape_status_string (line); } else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen)) { parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; } else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen)) { xfree (parm->pubkey_url); parm->pubkey_url = unescape_status_string (line); } else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen)) { xfree (parm->login_data); parm->login_data = unescape_status_string (line); } else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen)) { parm->sig_counter = strtoul (line, NULL, 0); } else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen)) { char *p, *buf; buf = p = unescape_status_string (line); if (buf) { while (spacep (p)) p++; parm->chv1_cached = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; for (i=0; *p && i < 3; i++) { parm->chvmaxlen[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } for (i=0; *p && i < 3; i++) { parm->chvretry[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } xfree (buf); } } else if (keywordlen == 6 && !memcmp (keyword, "EXTCAP", keywordlen)) { char *p, *p2, *buf; int abool; buf = p = unescape_status_string (line); if (buf) { for (p = strtok (buf, " "); p; p = strtok (NULL, " ")) { p2 = strchr (p, '='); if (p2) { *p2++ = 0; abool = (*p2 == '1'); if (!strcmp (p, "ki")) parm->extcap.ki = abool; else if (!strcmp (p, "aac")) parm->extcap.aac = abool; else if (!strcmp (p, "kdf")) parm->extcap.kdf = abool; else if (!strcmp (p, "si")) parm->status_indicator = strtoul (p2, NULL, 10); } } xfree (buf); } } else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->fpr1valid = unhexify_fpr (line, parm->fpr1); else if (no == 2) parm->fpr2valid = unhexify_fpr (line, parm->fpr2); else if (no == 3) parm->fpr3valid = unhexify_fpr (line, parm->fpr3); } else if (keywordlen == 8 && !memcmp (keyword, "KEY-TIME", keywordlen)) { int no = atoi (line); while (* line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->fpr1time = strtoul (line, NULL, 10); else if (no == 2) parm->fpr2time = strtoul (line, NULL, 10); else if (no == 3) parm->fpr3time = strtoul (line, NULL, 10); } else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) { const char *hexgrp = line; int no; while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (strncmp (line, "OPENPGP.", 8)) ; else if ((no = atoi (line+8)) == 1) unhexify_fpr (hexgrp, parm->grp1); else if (no == 2) unhexify_fpr (hexgrp, parm->grp2); else if (no == 3) unhexify_fpr (hexgrp, parm->grp3); } else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->cafpr1valid = unhexify_fpr (line, parm->cafpr1); else if (no == 2) parm->cafpr2valid = unhexify_fpr (line, parm->cafpr2); else if (no == 3) parm->cafpr3valid = unhexify_fpr (line, parm->cafpr3); } else if (keywordlen == 8 && !memcmp (keyword, "KEY-ATTR", keywordlen)) { int keyno = 0; int algo = PUBKEY_ALGO_RSA; int n = 0; sscanf (line, "%d %d %n", &keyno, &algo, &n); keyno--; if (keyno < 0 || keyno >= DIM (parm->key_attr)) return 0; parm->key_attr[keyno].algo = algo; if (algo == PUBKEY_ALGO_RSA) parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10); else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA) parm->key_attr[keyno].curve = openpgp_is_curve_supported (line + n, NULL, NULL); } else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11) && strchr("1234", keyword[11])) { int no = keyword[11] - '1'; log_assert (no >= 0 && no <= 3); xfree (parm->private_do[no]); parm->private_do[no] = unescape_status_string (line); } else if (keywordlen == 12 && !memcmp (keyword, "MANUFACTURER", 12)) { xfree (parm->manufacturer_name); parm->manufacturer_name = NULL; parm->manufacturer_id = strtoul (line, &endp, 0); while (endp && spacep (endp)) endp++; if (endp && *endp) parm->manufacturer_name = xstrdup (endp); } else if (keywordlen == 3 && !memcmp (keyword, "KDF", 3)) { unsigned char *data = unescape_status_string (line); if (data[2] != 0x03) parm->kdf_do_enabled = 0; else if (data[22] != 0x85) parm->kdf_do_enabled = 1; else parm->kdf_do_enabled = 2; xfree (data); } return 0; } /* Call the scdaemon to learn about a smartcard. Note that in * contradiction to the function's name, gpg-agent's LEARN command is * used and not the low-level "SCD LEARN". * Used by: * card-util.c * keyedit_menu * card_store_key_with_backup (Woth force to remove secret key data) + * + * If force has the value 2 the --reallyforce option is also used. + * This is to make sure the sshadow key overwrites the private key. + * Note that this option is gnupg 2.2 specific because since 2.4.4 an + * ephemeral private key store is used instead. */ int agent_scd_learn (struct agent_card_info_s *info, int force) { int rc; struct default_inq_parm_s parm; struct agent_card_info_s dummyinfo; if (!info) info = &dummyinfo; memset (info, 0, sizeof *info); memset (&parm, 0, sizeof parm); rc = start_agent (NULL, 1); if (rc) return rc; parm.ctx = agent_ctx; rc = assuan_transact (agent_ctx, + force == 2? "LEARN --sendinfo --force --reallyforce" : force ? "LEARN --sendinfo --force" : "LEARN --sendinfo", dummy_data_cb, NULL, default_inq_cb, &parm, learn_status_cb, info); /* Also try to get the key attributes. */ if (!rc) agent_scd_getattr ("KEY-ATTR", info); if (info == &dummyinfo) agent_release_card_info (info); return rc; } /* Callback for the agent_scd_keypairinfo function. */ static gpg_error_t scd_keypairinfo_status_cb (void *opaque, const char *line) { strlist_t *listaddr = opaque; const char *keyword = line; int keywordlen; strlist_t sl; char *p; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) { sl = append_to_strlist (listaddr, line); p = sl->d; /* Make sure that we only have two tokens so that future * extensions of the format won't change the format expected by * the caller. */ while (*p && !spacep (p)) p++; if (*p) { while (spacep (p)) p++; while (*p && !spacep (p)) p++; if (*p) { *p++ = 0; while (spacep (p)) p++; while (*p && !spacep (p)) { switch (*p++) { case 'c': sl->flags |= GCRY_PK_USAGE_CERT; break; case 's': sl->flags |= GCRY_PK_USAGE_SIGN; break; case 'e': sl->flags |= GCRY_PK_USAGE_ENCR; break; case 'a': sl->flags |= GCRY_PK_USAGE_AUTH; break; } } } } } return 0; } /* Read the keypairinfo lines of the current card directly from * scdaemon. The list is returned as a string made up of the keygrip, * a space and the keyref. The flags of the string carry the usage * bits. */ gpg_error_t agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list) { gpg_error_t err; strlist_t list = NULL; struct default_inq_parm_s inq_parm; *r_list = NULL; err= start_agent (ctrl, 1); if (err) return err; memset (&inq_parm, 0, sizeof inq_parm); inq_parm.ctx = agent_ctx; err = assuan_transact (agent_ctx, "SCD LEARN --keypairinfo", NULL, NULL, default_inq_cb, &inq_parm, scd_keypairinfo_status_cb, &list); if (!err && !list) err = gpg_error (GPG_ERR_NO_DATA); if (err) { free_strlist (list); return err; } *r_list = list; return 0; } /* Send an APDU to the current card. On success the status word is * stored at R_SW. With HEXAPDU being NULL only a RESET command is * send to scd. HEXAPDU may also be one of these special strings: * * "undefined" :: Send the command "SCD SERIALNO undefined" * "lock" :: Send the command "SCD LOCK --wait" * "trylock" :: Send the command "SCD LOCK" * "unlock" :: Send the command "SCD UNLOCK" * "reset-keep-lock" :: Send the command "SCD RESET --keep-lock" * * Used by: * card-util.c */ gpg_error_t agent_scd_apdu (const char *hexapdu, unsigned int *r_sw) { gpg_error_t err; /* Start the agent but not with the card flag so that we do not autoselect the openpgp application. */ err = start_agent (NULL, 0); if (err) return err; if (!hexapdu) { err = assuan_transact (agent_ctx, "SCD RESET", NULL, NULL, NULL, NULL, NULL, NULL); } else if (!strcmp (hexapdu, "reset-keep-lock")) { err = assuan_transact (agent_ctx, "SCD RESET --keep-lock", NULL, NULL, NULL, NULL, NULL, NULL); } else if (!strcmp (hexapdu, "lock")) { err = assuan_transact (agent_ctx, "SCD LOCK --wait", NULL, NULL, NULL, NULL, NULL, NULL); } else if (!strcmp (hexapdu, "trylock")) { err = assuan_transact (agent_ctx, "SCD LOCK", NULL, NULL, NULL, NULL, NULL, NULL); } else if (!strcmp (hexapdu, "unlock")) { err = assuan_transact (agent_ctx, "SCD UNLOCK", NULL, NULL, NULL, NULL, NULL, NULL); } else if (!strcmp (hexapdu, "undefined")) { err = assuan_transact (agent_ctx, "SCD SERIALNO undefined", NULL, NULL, NULL, NULL, NULL, NULL); } else { char line[ASSUAN_LINELENGTH]; membuf_t mb; unsigned char *data; size_t datalen; init_membuf (&mb, 256); snprintf (line, DIM(line), "SCD APDU %s", hexapdu); err = assuan_transact (agent_ctx, line, put_membuf_cb, &mb, NULL, NULL, NULL, NULL); if (!err) { data = get_membuf (&mb, &datalen); if (!data) err = gpg_error_from_syserror (); else if (datalen < 2) /* Ooops */ err = gpg_error (GPG_ERR_CARD); else { *r_sw = buf16_to_uint (data+datalen-2); } xfree (data); } } return err; } /* Used by: * card_store_subkey * card_store_key_with_backup */ int agent_keytocard (const char *hexgrip, int keyno, int force, const char *serialno, const char *timestamp, const char *ecdh_param_str) { int rc; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s parm; memset (&parm, 0, sizeof parm); snprintf (line, DIM(line), "KEYTOCARD %s%s %s OPENPGP.%d %s%s%s", force?"--force ": "", hexgrip, serialno, keyno, timestamp, ecdh_param_str? " ":"", ecdh_param_str? ecdh_param_str:""); rc = start_agent (NULL, 1); if (rc) return rc; parm.ctx = agent_ctx; rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, NULL, NULL); status_sc_op_failure (rc); return rc; } /* Object used with the agent_scd_getattr_one. */ struct getattr_one_parm_s { const char *keyword; /* Keyword to look for. */ char *data; /* Malloced and unescaped data. */ gpg_error_t err; /* Error code or 0 on success. */ }; /* Callback for agent_scd_getattr_one. */ static gpg_error_t getattr_one_status_cb (void *opaque, const char *line) { struct getattr_one_parm_s *parm = opaque; const char *s; if (parm->data) return 0; /* We want only the first occurrence. */ if ((s=has_leading_keyword (line, parm->keyword))) { parm->data = percent_plus_unescape (s, 0xff); if (!parm->data) parm->err = gpg_error_from_syserror (); } return 0; } /* Simplified version of agent_scd_getattr. This function returns * only the first occurance of the attribute NAME and stores it at * R_VALUE. A nul in the result is silennly replaced by 0xff. On * error NULL is stored at R_VALUE. */ gpg_error_t agent_scd_getattr_one (const char *name, char **r_value) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s inqparm; struct getattr_one_parm_s parm; *r_value = NULL; if (!*name) return gpg_error (GPG_ERR_INV_VALUE); memset (&inqparm, 0, sizeof inqparm); inqparm.ctx = agent_ctx; memset (&parm, 0, sizeof parm); parm.keyword = name; /* We assume that NAME does not need escaping. */ if (12 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); stpcpy (stpcpy (line, "SCD GETATTR "), name); err = start_agent (NULL, 1); if (err) return err; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &inqparm, getattr_one_status_cb, &parm); if (!err && parm.err) err = parm.err; else if (!err && !parm.data) err = gpg_error (GPG_ERR_NO_DATA); if (!err) *r_value = parm.data; else xfree (parm.data); return err; } /* Call the agent to retrieve a data object. This function returns * the data in the same structure as used by the learn command. It is * allowed to update such a structure using this command. * * Used by: * build_sk_list * enum_secret_keys * get_signature_count * card-util.c * generate_keypair (KEY-ATTR) * card_store_key_with_backup (SERIALNO) * generate_card_subkeypair (KEY-ATTR) */ int agent_scd_getattr (const char *name, struct agent_card_info_s *info) { int rc; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s parm; memset (&parm, 0, sizeof parm); if (!*name) return gpg_error (GPG_ERR_INV_VALUE); /* We assume that NAME does not need escaping. */ if (12 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); stpcpy (stpcpy (line, "SCD GETATTR "), name); rc = start_agent (NULL, 1); if (rc) return rc; parm.ctx = agent_ctx; rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, learn_status_cb, info); if (!rc && !strcmp (name, "KEY-FPR")) { /* Let the agent create the shadow keys if not yet done. */ if (info->fpr1valid) assuan_transact (agent_ctx, "READKEY --card --no-data -- $SIGNKEYID", NULL, NULL, NULL, NULL, NULL, NULL); if (info->fpr2valid) assuan_transact (agent_ctx, "READKEY --card --no-data -- $ENCRKEYID", NULL, NULL, NULL, NULL, NULL, NULL); if (info->fpr3valid) assuan_transact (agent_ctx, "READKEY --card --no-data -- $AUTHKEYID", NULL, NULL, NULL, NULL, NULL, NULL); } return rc; } /* Send an setattr command to the SCdaemon. * Used by: * card-util.c */ gpg_error_t agent_scd_setattr (const char *name, const void *value_arg, size_t valuelen) { gpg_error_t err; const unsigned char *value = value_arg; char line[ASSUAN_LINELENGTH]; char *p; struct default_inq_parm_s parm; memset (&parm, 0, sizeof parm); if (!*name || !valuelen) return gpg_error (GPG_ERR_INV_VALUE); /* We assume that NAME does not need escaping. */ if (12 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); p = stpcpy (stpcpy (line, "SCD SETATTR "), name); *p++ = ' '; for (; valuelen; value++, valuelen--) { if (p >= line + DIM(line)-5 ) return gpg_error (GPG_ERR_TOO_LARGE); if (*value < ' ' || *value == '+' || *value == '%') { sprintf (p, "%%%02X", *value); p += 3; } else if (*value == ' ') *p++ = '+'; else *p++ = *value; } *p = 0; err = start_agent (NULL, 1); if (!err) { parm.ctx = agent_ctx; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, NULL, NULL); } status_sc_op_failure (err); return err; } /* Handle a CERTDATA inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the END command. */ static gpg_error_t inq_writecert_parms (void *opaque, const char *line) { int rc; struct writecert_parm_s *parm = opaque; if (has_leading_keyword (line, "CERTDATA")) { rc = assuan_send_data (parm->dflt->ctx, parm->certdata, parm->certdatalen); } else rc = default_inq_cb (parm->dflt, line); return rc; } /* Send a WRITECERT command to the SCdaemon. * Used by: * card-util.c */ int agent_scd_writecert (const char *certidstr, const unsigned char *certdata, size_t certdatalen) { int rc; char line[ASSUAN_LINELENGTH]; struct writecert_parm_s parms; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); rc = start_agent (NULL, 1); if (rc) return rc; memset (&parms, 0, sizeof parms); snprintf (line, DIM(line), "SCD WRITECERT %s", certidstr); dfltparm.ctx = agent_ctx; parms.dflt = &dfltparm; parms.certdata = certdata; parms.certdatalen = certdatalen; rc = assuan_transact (agent_ctx, line, NULL, NULL, inq_writecert_parms, &parms, NULL, NULL); return rc; } /* Status callback for the SCD GENKEY command. */ static gpg_error_t scd_genkey_cb (void *opaque, const char *line) { u32 *createtime = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen)) { *createtime = (u32)strtoul (line, NULL, 10); } else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen)) { write_status_text (STATUS_PROGRESS, line); } return 0; } /* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0, * the value will be passed to SCDAEMON with --timestamp option so that * the key is created with this. Otherwise, timestamp was generated by * SCDEAMON. On success, creation time is stored back to * CREATETIME. * Used by: * gen_card_key */ int agent_scd_genkey (int keyno, int force, u32 *createtime) { int rc; char line[ASSUAN_LINELENGTH]; gnupg_isotime_t tbuf; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); rc = start_agent (NULL, 1); if (rc) return rc; if (*createtime) epoch2isotime (tbuf, *createtime); else *tbuf = 0; snprintf (line, DIM(line), "SCD GENKEY %s%s %s %d", *tbuf? "--timestamp=":"", tbuf, force? "--force":"", keyno); dfltparm.ctx = agent_ctx; rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, scd_genkey_cb, createtime); status_sc_op_failure (rc); return rc; } /* Return the serial number of the card or an appropriate error. The * serial number is returned as a hexstring. With DEMAND the active * card is switched to the card with that serialno. * Used by: * card-util.c * build_sk_list * enum_secret_keys */ int agent_scd_serialno (char **r_serialno, const char *demand) { int err; char *serialno = NULL; char line[ASSUAN_LINELENGTH]; err = start_agent (NULL, (1 | FLAG_FOR_CARD_SUPPRESS_ERRORS)); if (err) return err; if (!demand) strcpy (line, "SCD SERIALNO"); else snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); if (err) { xfree (serialno); return err; } *r_serialno = serialno; return 0; } /* Send a READCERT command to the SCdaemon. * Used by: * card-util.c */ int agent_scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); *r_buf = NULL; rc = start_agent (NULL, 1); if (rc) return rc; dfltparm.ctx = agent_ctx; init_membuf (&data, 2048); snprintf (line, DIM(line), "SCD READCERT %s", certidstr); rc = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return rc; } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return gpg_error (GPG_ERR_ENOMEM); return 0; } /* This is a variant of agent_readkey which sends a READKEY command * directly Scdaemon. On success a new s-expression is stored at * R_RESULT. */ gpg_error_t agent_scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; membuf_t data; unsigned char *buf; size_t len, buflen; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctx = agent_ctx; *r_result = NULL; err = start_agent (NULL, 1); if (err) return err; init_membuf (&data, 1024); snprintf (line, DIM(line), "SCD READKEY %s", keyrefstr); err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &buflen); if (!buf) return gpg_error_from_syserror (); err = gcry_sexp_new (r_result, buf, buflen, 0); xfree (buf); return err; } /* This can be called for a quick and dirty update/creation of the * shadow key stubs. */ gpg_error_t agent_update_shadow_keys (void) { gpg_error_t err; err = start_agent (NULL, 1); if (err) return err; assuan_transact (agent_ctx, "READKEY --card --no-data -- $SIGNKEYID", NULL, NULL, NULL, NULL, NULL, NULL); assuan_transact (agent_ctx, "READKEY --card --no-data -- $ENCRKEYID", NULL, NULL, NULL, NULL, NULL, NULL); assuan_transact (agent_ctx, "READKEY --card --no-data -- $AUTHKEYID", NULL, NULL, NULL, NULL, NULL, NULL); return err; } struct card_cardlist_parm_s { int error; strlist_t list; }; /* Callback function for agent_card_cardlist. */ static gpg_error_t card_cardlist_cb (void *opaque, const char *line) { struct card_cardlist_parm_s *parm = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { const char *s; int n; for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n || (n&1) || *s) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); else add_to_strlist (&parm->list, line); } return 0; } /* Return a list of currently available cards. * Used by: * card-util.c * skclist.c */ int agent_scd_cardlist (strlist_t *result) { int err; char line[ASSUAN_LINELENGTH]; struct card_cardlist_parm_s parm; memset (&parm, 0, sizeof parm); *result = NULL; err = start_agent (NULL, 1); if (err) return err; strcpy (line, "SCD GETINFO card_list"); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, card_cardlist_cb, &parm); if (!err && parm.error) err = parm.error; if (!err) *result = parm.list; else free_strlist (parm.list); return 0; } /* Change the PIN of an OpenPGP card or reset the retry counter. * CHVNO 1: Change the PIN * 2: For v1 cards: Same as 1. * For v2 cards: Reset the PIN using the Reset Code. * 3: Change the admin PIN * 101: Set a new PIN and reset the retry counter * 102: For v1 cars: Same as 101. * For v2 cards: Set a new Reset Code. * SERIALNO is not used. * Used by: * card-util.c */ int agent_scd_change_pin (int chvno, const char *serialno) { int rc; char line[ASSUAN_LINELENGTH]; const char *reset = ""; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); (void)serialno; if (chvno >= 100) reset = "--reset"; chvno %= 100; rc = start_agent (NULL, 1); if (rc) return rc; dfltparm.ctx = agent_ctx; snprintf (line, DIM(line), "SCD PASSWD %s %d", reset, chvno); rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); status_sc_op_failure (rc); return rc; } /* Perform a CHECKPIN operation. SERIALNO should be the serial * number of the card - optionally followed by the fingerprint; * however the fingerprint is ignored here. * Used by: * card-util.c */ int agent_scd_checkpin (const char *serialno) { int rc; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); rc = start_agent (NULL, 1); if (rc) return rc; dfltparm.ctx = agent_ctx; snprintf (line, DIM(line), "SCD CHECKPIN %s", serialno); rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); status_sc_op_failure (rc); return rc; } /* Note: All strings shall be UTF-8. On success the caller needs to free the string stored at R_PASSPHRASE. On error NULL will be stored at R_PASSPHRASE and an appropriate error code returned. Only called from passphrase.c:passphrase_get - see there for more comments on this ugly API. */ gpg_error_t agent_get_passphrase (const char *cache_id, const char *err_msg, const char *prompt, const char *desc_msg, int newsymkey, int repeat, int check, char **r_passphrase) { int rc; char line[ASSUAN_LINELENGTH]; char *arg1 = NULL; char *arg2 = NULL; char *arg3 = NULL; char *arg4 = NULL; membuf_t data; struct default_inq_parm_s dfltparm; int have_newsymkey, wasconf; memset (&dfltparm, 0, sizeof dfltparm); *r_passphrase = NULL; rc = start_agent (NULL, 0); if (rc) return rc; dfltparm.ctx = agent_ctx; /* Check that the gpg-agent understands the repeat option. */ if (assuan_transact (agent_ctx, "GETINFO cmd_has_option GET_PASSPHRASE repeat", NULL, NULL, NULL, NULL, NULL, NULL)) return gpg_error (GPG_ERR_NOT_SUPPORTED); have_newsymkey = !(assuan_transact (agent_ctx, "GETINFO cmd_has_option GET_PASSPHRASE newsymkey", NULL, NULL, NULL, NULL, NULL, NULL)); if (cache_id && *cache_id) if (!(arg1 = percent_plus_escape (cache_id))) goto no_mem; if (err_msg && *err_msg) if (!(arg2 = percent_plus_escape (err_msg))) goto no_mem; if (prompt && *prompt) if (!(arg3 = percent_plus_escape (prompt))) goto no_mem; if (desc_msg && *desc_msg) if (!(arg4 = percent_plus_escape (desc_msg))) goto no_mem; /* CHECK && REPEAT or NEWSYMKEY is here an indication that a new * passphrase for symmetric encryption is requested; if the agent * supports this we enable the modern API by also passing --newsymkey. */ snprintf (line, DIM(line), "GET_PASSPHRASE --data --repeat=%d%s%s -- %s %s %s %s", repeat, ((repeat && check) || newsymkey)? " --check":"", (have_newsymkey && newsymkey)? " --newsymkey":"", arg1? arg1:"X", arg2? arg2:"X", arg3? arg3:"X", arg4? arg4:"X"); xfree (arg1); xfree (arg2); xfree (arg3); xfree (arg4); init_membuf_secure (&data, 64); wasconf = assuan_get_flag (agent_ctx, ASSUAN_CONFIDENTIAL); assuan_begin_confidential (agent_ctx); rc = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (!wasconf) assuan_end_confidential (agent_ctx); if (rc) xfree (get_membuf (&data, NULL)); else { put_membuf (&data, "", 1); *r_passphrase = get_membuf (&data, NULL); if (!*r_passphrase) rc = gpg_error_from_syserror (); } return rc; no_mem: rc = gpg_error_from_syserror (); xfree (arg1); xfree (arg2); xfree (arg3); xfree (arg4); return rc; } gpg_error_t agent_clear_passphrase (const char *cache_id) { int rc; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); if (!cache_id || !*cache_id) return 0; rc = start_agent (NULL, 0); if (rc) return rc; dfltparm.ctx = agent_ctx; snprintf (line, DIM(line), "CLEAR_PASSPHRASE %s", cache_id); return assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); } /* Ask the agent to pop up a confirmation dialog with the text DESC and an okay and cancel button. */ gpg_error_t gpg_agent_get_confirmation (const char *desc) { int rc; char *tmp; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); rc = start_agent (NULL, 0); if (rc) return rc; dfltparm.ctx = agent_ctx; tmp = percent_plus_escape (desc); if (!tmp) return gpg_error_from_syserror (); snprintf (line, DIM(line), "GET_CONFIRMATION %s", tmp); xfree (tmp); rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); return rc; } /* Return the S2K iteration count as computed by gpg-agent. */ gpg_error_t agent_get_s2k_count (unsigned long *r_count) { gpg_error_t err; membuf_t data; char *buf; *r_count = 0; err = start_agent (NULL, 0); if (err) return err; init_membuf (&data, 32); err = assuan_transact (agent_ctx, "GETINFO s2k_count", put_membuf_cb, &data, NULL, NULL, NULL, NULL); if (err) xfree (get_membuf (&data, NULL)); else { put_membuf (&data, "", 1); buf = get_membuf (&data, NULL); if (!buf) err = gpg_error_from_syserror (); else { *r_count = strtoul (buf, NULL, 10); xfree (buf); } } return err; } struct keyinfo_data_parm_s { char *serialno; int is_smartcard; int passphrase_cached; int cleartext; }; static gpg_error_t keyinfo_status_cb (void *opaque, const char *line) { struct keyinfo_data_parm_s *data = opaque; char *s; if ((s = has_leading_keyword (line, "KEYINFO")) && data) { /* Parse the arguments: * 0 1 2 3 4 5 * * * 6 7 8 * */ char *fields[9]; if (split_fields (s, fields, DIM (fields)) == 9) { data->is_smartcard = (fields[1][0] == 'T'); if (data->is_smartcard && !data->serialno && strcmp (fields[2], "-")) data->serialno = xtrystrdup (fields[2]); /* '1' for cached */ data->passphrase_cached = (fields[4][0] == '1'); /* 'P' for protected, 'C' for clear */ data->cleartext = (fields[5][0] == 'C'); } } return 0; } /* Ask the agent whether a secret key for the given public key is available. Returns 0 if not available. Bigger value is preferred. */ int agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; char *hexgrip; struct keyinfo_data_parm_s keyinfo; memset (&keyinfo, 0, sizeof keyinfo); err = start_agent (ctrl, 0); if (err) return err; err = hexkeygrip_from_pk (pk, &hexgrip); if (err) return err; snprintf (line, sizeof line, "KEYINFO %s", hexgrip); xfree (hexgrip); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, keyinfo_status_cb, &keyinfo); xfree (keyinfo.serialno); if (err) return 0; if (keyinfo.passphrase_cached) return 3; if (keyinfo.is_smartcard) return 2; return 1; } /* Ask the agent whether a secret key is available for any of the keys (primary or sub) in KEYBLOCK. Returns 0 if available. */ gpg_error_t agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; char *p; kbnode_t kbctx, node; int nkeys; unsigned char grip[20]; err = start_agent (ctrl, 0); if (err) return err; err = gpg_error (GPG_ERR_NO_SECKEY); /* Just in case no key was found in KEYBLOCK. */ p = stpcpy (line, "HAVEKEY"); for (kbctx=NULL, nkeys=0; (node = walk_kbnode (keyblock, &kbctx, 0)); ) if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_KEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) { if (nkeys && ((p - line) + 41) > (ASSUAN_LINELENGTH - 2)) { err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err != gpg_err_code (GPG_ERR_NO_SECKEY)) break; /* Seckey available or unexpected error - ready. */ p = stpcpy (line, "HAVEKEY"); nkeys = 0; } err = keygrip_from_pk (node->pkt->pkt.public_key, grip); if (err) return err; *p++ = ' '; bin2hex (grip, 20, p); p += 40; nkeys++; } if (!err && nkeys) err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return err; } /* Return the serial number for a secret key. If the returned serial number is NULL, the key is not stored on a smartcard. Caller needs to free R_SERIALNO. if r_cleartext is not NULL, the referenced int will be set to 1 if the agent's copy of the key is stored in the clear, or 0 otherwise */ gpg_error_t agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno, int *r_cleartext) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct keyinfo_data_parm_s keyinfo; memset (&keyinfo, 0,sizeof keyinfo); *r_serialno = NULL; err = start_agent (ctrl, 0); if (err) return err; if (!hexkeygrip || strlen (hexkeygrip) != 40) return gpg_error (GPG_ERR_INV_VALUE); snprintf (line, DIM(line), "KEYINFO %s", hexkeygrip); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, keyinfo_status_cb, &keyinfo); if (!err && keyinfo.serialno) { /* Sanity check for bad characters. */ if (strpbrk (keyinfo.serialno, ":\n\r")) err = GPG_ERR_INV_VALUE; } if (err) xfree (keyinfo.serialno); else { *r_serialno = keyinfo.serialno; if (r_cleartext) *r_cleartext = keyinfo.cleartext; } return err; } /* Status callback for agent_import_key, agent_export_key and agent_genkey. */ static gpg_error_t cache_nonce_status_cb (void *opaque, const char *line) { struct cache_nonce_parm_s *parm = opaque; const char *s; if ((s = has_leading_keyword (line, "CACHE_NONCE"))) { if (parm->cache_nonce_addr) { xfree (*parm->cache_nonce_addr); *parm->cache_nonce_addr = xtrystrdup (s); } } else if ((s = has_leading_keyword (line, "PASSWD_NONCE"))) { if (parm->passwd_nonce_addr) { xfree (*parm->passwd_nonce_addr); *parm->passwd_nonce_addr = xtrystrdup (s); } } else if ((s = has_leading_keyword (line, "PROGRESS"))) { if (opt.enable_progress_filter) write_status_text (STATUS_PROGRESS, s); } return 0; } /* Handle a KEYPARMS inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the end */ static gpg_error_t inq_genkey_parms (void *opaque, const char *line) { struct genkey_parm_s *parm = opaque; gpg_error_t err; if (has_leading_keyword (line, "KEYPARAM")) { err = assuan_send_data (parm->dflt->ctx, parm->keyparms, strlen (parm->keyparms)); } else if (has_leading_keyword (line, "NEWPASSWD") && parm->passphrase) { err = assuan_send_data (parm->dflt->ctx, parm->passphrase, strlen (parm->passphrase)); } else err = default_inq_cb (parm->dflt, line); return err; } /* Call the agent to generate a new key. KEYPARMS is the usual S-expression giving the parameters of the key. gpg-agent passes it gcry_pk_genkey. If NO_PROTECTION is true the agent is advised not to protect the generated key. If NO_PROTECTION is not set and PASSPHRASE is not NULL the agent is requested to protect the key with that passphrase instead of asking for one. TIMESTAMP is the creation time of the key or zero. */ gpg_error_t agent_genkey (ctrl_t ctrl, char **cache_nonce_addr, char **passwd_nonce_addr, const char *keyparms, int no_protection, const char *passphrase, time_t timestamp, gcry_sexp_t *r_pubkey) { gpg_error_t err; struct genkey_parm_s gk_parm; struct cache_nonce_parm_s cn_parm; struct default_inq_parm_s dfltparm; membuf_t data; size_t len; unsigned char *buf; char timestamparg[16 + 16]; /* The 2nd 16 is sizeof(gnupg_isotime_t) */ char line[ASSUAN_LINELENGTH]; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; *r_pubkey = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (timestamp) { strcpy (timestamparg, " --timestamp="); epoch2isotime (timestamparg+13, timestamp); } else *timestamparg = 0; if (passwd_nonce_addr && *passwd_nonce_addr) ; /* A RESET would flush the passwd nonce cache. */ else { err = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } init_membuf (&data, 1024); gk_parm.dflt = &dfltparm; gk_parm.keyparms = keyparms; gk_parm.passphrase = passphrase; snprintf (line, sizeof line, "GENKEY%s%s%s%s%s%s", *timestamparg? timestamparg : "", no_protection? " --no-protection" : passphrase ? " --inq-passwd" : /* */ "", passwd_nonce_addr && *passwd_nonce_addr? " --passwd-nonce=":"", passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"", cache_nonce_addr && *cache_nonce_addr? " ":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:""); cn_parm.cache_nonce_addr = cache_nonce_addr; cn_parm.passwd_nonce_addr = NULL; err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, inq_genkey_parms, &gk_parm, cache_nonce_status_cb, &cn_parm); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) err = gpg_error_from_syserror (); else { err = gcry_sexp_sscan (r_pubkey, NULL, buf, len); xfree (buf); } return err; } /* Call the agent to read the public key part for a given keygrip. If FROMCARD is true, the key is directly read from the current smartcard. In this case HEXKEYGRIP should be the keyID (e.g. OPENPGP.3). */ gpg_error_t agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip, unsigned char **r_pubkey) { gpg_error_t err; membuf_t data; size_t len; unsigned char *buf; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; *r_pubkey = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; err = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; snprintf (line, DIM(line), "READKEY %s%s", fromcard? "--card ":"", hexkeygrip); init_membuf (&data, 1024); err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) return gpg_error_from_syserror (); if (!gcry_sexp_canon_len (buf, len, NULL, NULL)) { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } *r_pubkey = buf; return 0; } /* Call the agent to do a sign operation using the key identified by the hex string KEYGRIP. DESC is a description of the key to be displayed if the agent needs to ask for the PIN. DIGEST and DIGESTLEN is the hash value to sign and DIGESTALGO the algorithm id used to compute the digest. If CACHE_NONCE is used the agent is advised to first try a passphrase associated with that nonce. */ gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce, const char *keygrip, const char *desc, u32 *keyid, u32 *mainkeyid, int pubkey_algo, unsigned char *digest, size_t digestlen, int digestalgo, gcry_sexp_t *r_sigval) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; membuf_t data; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; dfltparm.keyinfo.keyid = keyid; dfltparm.keyinfo.mainkeyid = mainkeyid; dfltparm.keyinfo.pubkey_algo = pubkey_algo; *r_sigval = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (digestlen*2 + 50 > DIM(line)) return gpg_error (GPG_ERR_GENERAL); err = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; snprintf (line, DIM(line), "SIGKEY %s", keygrip); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } snprintf (line, sizeof line, "SETHASH %d ", digestalgo); bin2hex (digest, digestlen, line + strlen (line)); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; init_membuf (&data, 1024); snprintf (line, sizeof line, "PKSIGN%s%s", cache_nonce? " -- ":"", cache_nonce? cache_nonce:""); err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (err) xfree (get_membuf (&data, NULL)); else { unsigned char *buf; size_t len; buf = get_membuf (&data, &len); if (!buf) err = gpg_error_from_syserror (); else { err = gcry_sexp_sscan (r_sigval, NULL, buf, len); xfree (buf); } } return err; } /* Handle a CIPHERTEXT inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the END. */ static gpg_error_t inq_ciphertext_cb (void *opaque, const char *line) { struct cipher_parm_s *parm = opaque; int rc; if (has_leading_keyword (line, "CIPHERTEXT")) { assuan_begin_confidential (parm->ctx); rc = assuan_send_data (parm->dflt->ctx, parm->ciphertext, parm->ciphertextlen); assuan_end_confidential (parm->ctx); } else rc = default_inq_cb (parm->dflt, line); return rc; } /* Check whether there is any padding info from the agent. */ static gpg_error_t padding_info_cb (void *opaque, const char *line) { int *r_padding = opaque; const char *s; if ((s=has_leading_keyword (line, "PADDING"))) { *r_padding = atoi (s); } return 0; } /* Call the agent to do a decrypt operation using the key identified by the hex string KEYGRIP and the input data S_CIPHERTEXT. On the success the decoded value is stored verbatim at R_BUF and its length at R_BUF; the callers needs to release it. KEYID, MAINKEYID and PUBKEY_ALGO are used to construct additional promots or status messages. The padding information is stored at R_PADDING with -1 for not known. */ gpg_error_t agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, u32 *keyid, u32 *mainkeyid, int pubkey_algo, gcry_sexp_t s_ciphertext, unsigned char **r_buf, size_t *r_buflen, int *r_padding) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t n, len; char *p, *buf, *endp; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; dfltparm.keyinfo.keyid = keyid; dfltparm.keyinfo.mainkeyid = mainkeyid; dfltparm.keyinfo.pubkey_algo = pubkey_algo; if (!keygrip || strlen(keygrip) != 40 || !s_ciphertext || !r_buf || !r_buflen || !r_padding) return gpg_error (GPG_ERR_INV_VALUE); *r_buf = NULL; *r_padding = -1; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; err = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; snprintf (line, sizeof line, "SETKEY %s", keygrip); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } init_membuf_secure (&data, 1024); { struct cipher_parm_s parm; parm.dflt = &dfltparm; parm.ctx = agent_ctx; err = make_canon_sexp (s_ciphertext, &parm.ciphertext, &parm.ciphertextlen); if (err) return err; err = assuan_transact (agent_ctx, "PKDECRYPT", put_membuf_cb, &data, inq_ciphertext_cb, &parm, padding_info_cb, r_padding); xfree (parm.ciphertext); } if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) return gpg_error_from_syserror (); if (len == 0 || *buf != '(') { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } if (len < 12 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)" */ { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } while (buf[len-1] == 0) len--; if (buf[len-1] != ')') return gpg_error (GPG_ERR_INV_SEXP); len--; /* Drop the final close-paren. */ p = buf + 8; /* Skip leading parenthesis and the value tag. */ len -= 8; /* Count only the data of the second part. */ n = strtoul (p, &endp, 10); if (!n || *endp != ':') { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } endp++; if (endp-p+n > len) { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */ } memmove (buf, endp, n); *r_buflen = n; *r_buf = buf; return 0; } /* Retrieve a key encryption key from the agent. With FOREXPORT true the key shall be used for export, with false for import. On success the new key is stored at R_KEY and its length at R_KEKLEN. */ gpg_error_t agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen) { gpg_error_t err; membuf_t data; size_t len; unsigned char *buf; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; *r_kek = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; snprintf (line, DIM(line), "KEYWRAP_KEY %s", forexport? "--export":"--import"); init_membuf_secure (&data, 64); err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) return gpg_error_from_syserror (); *r_kek = buf; *r_keklen = len; return 0; } /* Handle the inquiry for an IMPORT_KEY command. */ static gpg_error_t inq_import_key_parms (void *opaque, const char *line) { struct import_key_parm_s *parm = opaque; gpg_error_t err; if (has_leading_keyword (line, "KEYDATA")) { err = assuan_send_data (parm->dflt->ctx, parm->key, parm->keylen); } else err = default_inq_cb (parm->dflt, line); return err; } /* Call the agent to import a key into the agent. */ gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, const void *key, size_t keylen, int unattended, int force, u32 *keyid, u32 *mainkeyid, int pubkey_algo, u32 timestamp) { gpg_error_t err; struct import_key_parm_s parm; struct cache_nonce_parm_s cn_parm; char timestamparg[16 + 16]; /* The 2nd 16 is sizeof(gnupg_isotime_t) */ char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; dfltparm.keyinfo.keyid = keyid; dfltparm.keyinfo.mainkeyid = mainkeyid; dfltparm.keyinfo.pubkey_algo = pubkey_algo; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (timestamp) { strcpy (timestamparg, " --timestamp="); epoch2isotime (timestamparg+13, timestamp); } else *timestamparg = 0; if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } parm.dflt = &dfltparm; parm.key = key; parm.keylen = keylen; snprintf (line, sizeof line, "IMPORT_KEY%s%s%s%s%s", *timestamparg? timestamparg : "", unattended? " --unattended":"", force? " --force":"", cache_nonce_addr && *cache_nonce_addr? " ":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:""); cn_parm.cache_nonce_addr = cache_nonce_addr; cn_parm.passwd_nonce_addr = NULL; err = assuan_transact (agent_ctx, line, NULL, NULL, inq_import_key_parms, &parm, cache_nonce_status_cb, &cn_parm); return err; } /* Receive a secret key from the agent. HEXKEYGRIP is the hexified keygrip, DESC a prompt to be displayed with the agent's passphrase question (needs to be plus+percent escaped). if OPENPGP_PROTECTED is not zero, ensure that the key material is returned in RFC 4880-compatible passphrased-protected form. If CACHE_NONCE_ADDR is not NULL the agent is advised to first try a passphrase associated with that nonce. On success the key is stored as a canonical S-expression at R_RESULT and R_RESULTLEN. */ gpg_error_t agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int openpgp_protected, char **cache_nonce_addr, unsigned char **r_result, size_t *r_resultlen, u32 *keyid, u32 *mainkeyid, int pubkey_algo) { gpg_error_t err; struct cache_nonce_parm_s cn_parm; membuf_t data; size_t len; unsigned char *buf; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; dfltparm.keyinfo.keyid = keyid; dfltparm.keyinfo.mainkeyid = mainkeyid; dfltparm.keyinfo.pubkey_algo = pubkey_algo; *r_result = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } snprintf (line, DIM(line), "EXPORT_KEY %s%s%s %s", openpgp_protected ? "--openpgp ":"", cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", hexkeygrip); init_membuf_secure (&data, 1024); cn_parm.cache_nonce_addr = cache_nonce_addr; cn_parm.passwd_nonce_addr = NULL; err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, cache_nonce_status_cb, &cn_parm); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) return gpg_error_from_syserror (); *r_result = buf; *r_resultlen = len; return 0; } /* Ask the agent to delete the key identified by HEXKEYGRIP. If DESC is not NULL, display DESC instead of the default description message. If FORCE is true the agent is advised not to ask for confirmation. */ gpg_error_t agent_delete_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int force) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; err = start_agent (ctrl, 0); if (err) return err; if (!hexkeygrip || strlen (hexkeygrip) != 40) return gpg_error (GPG_ERR_INV_VALUE); if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } snprintf (line, DIM(line), "DELETE_KEY%s %s", force? " --force":"", hexkeygrip); err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); return err; } /* Ask the agent to change the passphrase of the key identified by * HEXKEYGRIP. If DESC is not NULL, display DESC instead of the * default description message. If CACHE_NONCE_ADDR is not NULL the * agent is advised to first try a passphrase associated with that * nonce. If PASSWD_NONCE_ADDR is not NULL the agent will try to use * the passphrase associated with that nonce for the new passphrase. * If VERIFY is true the passphrase is only verified. */ gpg_error_t agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int verify, char **cache_nonce_addr, char **passwd_nonce_addr) { gpg_error_t err; struct cache_nonce_parm_s cn_parm; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (!hexkeygrip || strlen (hexkeygrip) != 40) return gpg_error (GPG_ERR_INV_VALUE); if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } if (verify) snprintf (line, DIM(line), "PASSWD %s%s --verify %s", cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", hexkeygrip); else snprintf (line, DIM(line), "PASSWD %s%s %s%s %s", cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", passwd_nonce_addr && *passwd_nonce_addr? "--passwd-nonce=":"", passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"", hexkeygrip); cn_parm.cache_nonce_addr = cache_nonce_addr; cn_parm.passwd_nonce_addr = passwd_nonce_addr; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, cache_nonce_status_cb, &cn_parm); return err; } /* Return the version reported by gpg-agent. */ gpg_error_t agent_get_version (ctrl_t ctrl, char **r_version) { gpg_error_t err; err = start_agent (ctrl, 0); if (err) return err; err = get_assuan_server_version (agent_ctx, 0, r_version); return err; } diff --git a/g10/keygen.c b/g10/keygen.c index 08e546954..c8487be0a 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -1,6035 +1,6038 @@ /* keygen.c - Generate a key pair * Copyright (C) 1998-2007, 2009-2011 Free Software Foundation, Inc. * Copyright (C) 2014, 2015, 2016 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 "gpg.h" #include "../common/util.h" #include "main.h" #include "packet.h" #include "../common/ttyio.h" #include "options.h" #include "keydb.h" #include "trustdb.h" #include "../common/status.h" #include "../common/i18n.h" #include "keyserver-internal.h" #include "call-agent.h" #include "pkglue.h" #include "../common/shareddefs.h" #include "../common/host2net.h" #include "../common/mbox-util.h" /* The default algorithms. You should also check that the value is inside the bounds enforced by ask_keysize and gen_xxx. See also get_keysize_range which encodes the allowed ranges. */ #define DEFAULT_STD_KEY_PARAM "rsa3072/cert,sign+rsa3072/encr" #define FUTURE_STD_KEY_PARAM "ed25519/cert,sign+cv25519/encr" /* When generating keys using the streamlined key generation dialog, use this as a default expiration interval. */ const char *default_expiration_interval = "3y"; /* Flag bits used during key generation. */ #define KEYGEN_FLAG_NO_PROTECTION 1 #define KEYGEN_FLAG_TRANSIENT_KEY 2 /* Maximum number of supported algorithm preferences. */ #define MAX_PREFS 30 enum para_name { pKEYTYPE, pKEYLENGTH, pKEYCURVE, pKEYUSAGE, pSUBKEYTYPE, pSUBKEYLENGTH, pSUBKEYCURVE, pSUBKEYUSAGE, pAUTHKEYTYPE, pNAMEREAL, pNAMEEMAIL, pNAMECOMMENT, pPREFERENCES, pREVOKER, pUSERID, pCREATIONDATE, pKEYCREATIONDATE, /* Same in seconds since epoch. */ pEXPIREDATE, pKEYEXPIRE, /* in n seconds */ pSUBKEYEXPIRE, /* in n seconds */ pPASSPHRASE, pSERIALNO, pCARDBACKUPKEY, pHANDLE, pKEYSERVER, pKEYGRIP, pSUBKEYGRIP, }; struct para_data_s { struct para_data_s *next; int lnr; enum para_name key; union { u32 expire; u32 creation; unsigned int usage; struct revocation_key revkey; char value[1]; } u; }; struct output_control_s { int lnr; int dryrun; unsigned int keygen_flags; int use_files; struct { char *fname; char *newfname; IOBUF stream; armor_filter_context_t *afx; } pub; }; struct opaque_data_usage_and_pk { unsigned int usage; const char *cpl_notation; PKT_public_key *pk; }; static int prefs_initialized = 0; static byte sym_prefs[MAX_PREFS]; static int nsym_prefs; static byte hash_prefs[MAX_PREFS]; static int nhash_prefs; static byte zip_prefs[MAX_PREFS]; static int nzip_prefs; static int mdc_available,ks_modify; static int aead_available; static gpg_error_t parse_algo_usage_expire (ctrl_t ctrl, int for_subkey, const char *algostr, const char *usagestr, const char *expirestr, int *r_algo, unsigned int *r_usage, u32 *r_expire, unsigned int *r_nbits, const char **r_curve, char **r_keygrip); static void do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, struct output_control_s *outctrl, int card ); static int write_keyblock (iobuf_t out, kbnode_t node); static gpg_error_t gen_card_key (int keyno, int algo, int is_primary, kbnode_t pub_root, u32 *timestamp, u32 expireval); static unsigned int get_keysize_range (int algo, unsigned int *min, unsigned int *max); static void do_add_notation (PKT_signature *sig, const char *name, const char *value, int critical); /* Return the algo string for a default new key. */ const char * get_default_pubkey_algo (void) { if (opt.def_new_key_algo) { if (*opt.def_new_key_algo && !strchr (opt.def_new_key_algo, ':')) return opt.def_new_key_algo; /* To avoid checking that option every time we delay that until * here. The only thing we really need to make sure is that * there is no colon in the string so that the --gpgconf-list * command won't mess up its output. */ log_info (_("invalid value for option '%s'\n"), "--default-new-key-algo"); } return DEFAULT_STD_KEY_PARAM; } static void print_status_key_created (int letter, PKT_public_key *pk, const char *handle) { byte array[MAX_FINGERPRINT_LEN], *s; char *buf, *p; size_t i, n; if (!handle) handle = ""; buf = xmalloc (MAX_FINGERPRINT_LEN*2+31 + strlen (handle) + 1); p = buf; if (letter || pk) { *p++ = letter; if (pk) { *p++ = ' '; fingerprint_from_pk (pk, array, &n); s = array; /* Fixme: Use bin2hex */ for (i=0; i < n ; i++, s++, p += 2) snprintf (p, 3, "%02X", *s); } } if (*handle) { *p++ = ' '; for (i=0; handle[i] && i < 100; i++) *p++ = isspace ((unsigned int)handle[i])? '_':handle[i]; } *p = 0; write_status_text ((letter || pk)?STATUS_KEY_CREATED:STATUS_KEY_NOT_CREATED, buf); xfree (buf); } static void print_status_key_not_created (const char *handle) { print_status_key_created (0, NULL, handle); } static gpg_error_t write_uid (kbnode_t root, const char *s) { PACKET *pkt = xmalloc_clear (sizeof *pkt); size_t n = strlen (s); if (n > MAX_UID_PACKET_LENGTH - 10) return gpg_error (GPG_ERR_INV_USER_ID); pkt->pkttype = PKT_USER_ID; pkt->pkt.user_id = xmalloc_clear (sizeof *pkt->pkt.user_id + n); pkt->pkt.user_id->len = n; pkt->pkt.user_id->ref = 1; strcpy (pkt->pkt.user_id->name, s); add_kbnode (root, new_kbnode (pkt)); return 0; } static void do_add_key_flags (PKT_signature *sig, unsigned int use) { byte buf[1]; buf[0] = 0; /* The spec says that all primary keys MUST be able to certify. */ if(sig->sig_class!=0x18) buf[0] |= 0x01; if (use & PUBKEY_USAGE_SIG) buf[0] |= 0x02; if (use & PUBKEY_USAGE_ENC) buf[0] |= 0x04 | 0x08; if (use & PUBKEY_USAGE_AUTH) buf[0] |= 0x20; build_sig_subpkt (sig, SIGSUBPKT_KEY_FLAGS, buf, 1); } int keygen_add_key_expire (PKT_signature *sig, void *opaque) { PKT_public_key *pk = opaque; byte buf[8]; u32 u; if (pk->expiredate) { if (pk->expiredate > pk->timestamp) u = pk->expiredate - pk->timestamp; else u = 1; buf[0] = (u >> 24) & 0xff; buf[1] = (u >> 16) & 0xff; buf[2] = (u >> 8) & 0xff; buf[3] = u & 0xff; build_sig_subpkt (sig, SIGSUBPKT_KEY_EXPIRE, buf, 4); } else { /* Make sure we don't leave a key expiration subpacket lying around */ delete_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE); } return 0; } /* Add the key usage (i.e. key flags) in SIG from the public keys * pubkey_usage field. OPAQUE has the public key. */ int keygen_add_key_flags (PKT_signature *sig, void *opaque) { PKT_public_key *pk = opaque; do_add_key_flags (sig, pk->pubkey_usage); return 0; } /* This is only used to write the key binding signature. It is not * used for the primary key. */ static int keygen_add_key_flags_and_expire (PKT_signature *sig, void *opaque) { struct opaque_data_usage_and_pk *oduap = opaque; do_add_key_flags (sig, oduap->usage); if (oduap->cpl_notation) do_add_notation (sig, "cpl@gnupg.org", oduap->cpl_notation, 0); return keygen_add_key_expire (sig, oduap->pk); } static int set_one_pref (int val, int type, const char *item, byte *buf, int *nbuf) { int i; for (i=0; i < *nbuf; i++ ) if (buf[i] == val) { log_info (_("preference '%s' duplicated\n"), item); return -1; } if (*nbuf >= MAX_PREFS) { if(type==1) log_info(_("too many cipher preferences\n")); else if(type==2) log_info(_("too many digest preferences\n")); else if(type==3) log_info(_("too many compression preferences\n")); else BUG(); return -1; } buf[(*nbuf)++] = val; return 0; } /* * Parse the supplied string and use it to set the standard * preferences. The string may be in a form like the one printed by * "pref" (something like: "S10 S3 H3 H2 Z2 Z1") or the actual * cipher/hash/compress names. Use NULL to set the default * preferences. Returns: 0 = okay */ int keygen_set_std_prefs (const char *string,int personal) { byte sym[MAX_PREFS], hash[MAX_PREFS], zip[MAX_PREFS]; int nsym=0, nhash=0, nzip=0, val, rc=0; int mdc=1, modify=0; /* mdc defaults on, modify defaults off. */ int ocb; char dummy_string[20*4+1]; /* Enough for 20 items. */ /* Use OCB as default in GnuPG and de-vs mode. */ ocb = GNUPG; if (!string || !ascii_strcasecmp (string, "default")) { if (opt.def_preference_list) string=opt.def_preference_list; else { int any_compress = 0; dummy_string[0]='\0'; /* The rationale why we use the order AES256,192,128 is for compatibility reasons with PGP. If gpg would define AES128 first, we would get the somewhat confusing situation: gpg -r pgpkey -r gpgkey ---gives--> AES256 gpg -r gpgkey -r pgpkey ---gives--> AES Note that by using --personal-cipher-preferences it is possible to prefer AES128. */ /* Make sure we do not add more than 15 items here, as we could overflow the size of dummy_string. We currently have at most 12. */ if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES256) ) strcat(dummy_string,"S9 "); if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES192) ) strcat(dummy_string,"S8 "); if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES) ) strcat(dummy_string,"S7 "); strcat(dummy_string,"S2 "); /* 3DES */ if (personal) { /* The default internal hash algo order is: * SHA-256, SHA-384, SHA-512, SHA-224, SHA-1. */ if (!openpgp_md_test_algo (DIGEST_ALGO_SHA256)) strcat (dummy_string, "H8 "); if (!openpgp_md_test_algo (DIGEST_ALGO_SHA384)) strcat (dummy_string, "H9 "); if (!openpgp_md_test_algo (DIGEST_ALGO_SHA512)) strcat (dummy_string, "H10 "); } else { /* The default advertised hash algo order is: * SHA-512, SHA-384, SHA-256, SHA-224, SHA-1. */ if (!openpgp_md_test_algo (DIGEST_ALGO_SHA512)) strcat (dummy_string, "H10 "); if (!openpgp_md_test_algo (DIGEST_ALGO_SHA384)) strcat (dummy_string, "H9 "); if (!openpgp_md_test_algo (DIGEST_ALGO_SHA256)) strcat (dummy_string, "H8 "); } if (!openpgp_md_test_algo (DIGEST_ALGO_SHA224)) strcat (dummy_string, "H11 "); strcat (dummy_string, "H2 "); /* SHA-1 */ if(!check_compress_algo(COMPRESS_ALGO_ZLIB)) { strcat(dummy_string,"Z2 "); any_compress = 1; } if(!check_compress_algo(COMPRESS_ALGO_BZIP2)) { strcat(dummy_string,"Z3 "); any_compress = 1; } if(!check_compress_algo(COMPRESS_ALGO_ZIP)) { strcat(dummy_string,"Z1 "); any_compress = 1; } /* In case we have no compress algo at all, declare that we prefer no compresssion. */ if (!any_compress) strcat(dummy_string,"Z0 "); /* Remove the trailing space. */ if (*dummy_string && dummy_string[strlen (dummy_string)-1] == ' ') dummy_string[strlen (dummy_string)-1] = 0; string=dummy_string; } } else if (!ascii_strcasecmp (string, "none")) string = ""; if(strlen(string)) { char *prefstringbuf; char *tok, *prefstring; /* We need a writable string. */ prefstring = prefstringbuf = xstrdup (string); while((tok=strsep(&prefstring," ,"))) { if((val=string_to_cipher_algo (tok))) { if(set_one_pref(val,1,tok,sym,&nsym)) rc=-1; } else if((val=string_to_digest_algo (tok))) { if(set_one_pref(val,2,tok,hash,&nhash)) rc=-1; } else if((val=string_to_compress_algo(tok))>-1) { if(set_one_pref(val,3,tok,zip,&nzip)) rc=-1; } else if (!ascii_strcasecmp(tok, "mdc") || !ascii_strcasecmp(tok, "[mdc]")) mdc=1; else if (!ascii_strcasecmp(tok, "no-mdc") || !ascii_strcasecmp(tok, "[no-mdc]")) mdc=0; else if (!ascii_strcasecmp(tok, "ks-modify") || !ascii_strcasecmp(tok, "[ks-modify]")) modify=1; else if (!ascii_strcasecmp(tok,"no-ks-modify") || !ascii_strcasecmp(tok,"[no-ks-modify]")) modify=0; else if (!ascii_strcasecmp(tok,"aead") || !ascii_strcasecmp(tok,"[aead]")) ocb = 1; else if (!ascii_strcasecmp(tok,"no-aead") || !ascii_strcasecmp(tok,"[no-aead]")) ocb = 0; else { log_info (_("invalid item '%s' in preference string\n"),tok); rc=-1; } } xfree (prefstringbuf); } /* For now we require a compat flag to set OCB into the preferences. */ if (!(opt.compat_flags & COMPAT_VSD_ALLOW_OCB)) ocb = 0; if(!rc) { if(personal) { if(personal==PREFTYPE_SYM) { xfree(opt.personal_cipher_prefs); if(nsym==0) opt.personal_cipher_prefs=NULL; else { int i; opt.personal_cipher_prefs= xmalloc(sizeof(prefitem_t *)*(nsym+1)); for (i=0; iref=1; uid->prefs=xmalloc((sizeof(prefitem_t *)* (nsym_prefs+nhash_prefs+nzip_prefs+1))); for(i=0;iprefs[j].type=PREFTYPE_SYM; uid->prefs[j].value=sym_prefs[i]; } for(i=0;iprefs[j].type=PREFTYPE_HASH; uid->prefs[j].value=hash_prefs[i]; } for(i=0;iprefs[j].type=PREFTYPE_ZIP; uid->prefs[j].value=zip_prefs[i]; } uid->prefs[j].type=PREFTYPE_NONE; uid->prefs[j].value=0; uid->flags.mdc=mdc_available; uid->flags.aead=aead_available; uid->flags.ks_modify=ks_modify; return uid; } static void add_feature_mdc (PKT_signature *sig,int enabled) { const byte *s; size_t n; int i; char *buf; s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n ); /* Already set or cleared */ if (s && n && ((enabled && (s[0] & 0x01)) || (!enabled && !(s[0] & 0x01)))) return; if (!s || !n) { /* create a new one */ n = 1; buf = xmalloc_clear (n); } else { buf = xmalloc (n); memcpy (buf, s, n); } if(enabled) buf[0] |= 0x01; /* MDC feature */ else buf[0] &= ~0x01; /* Are there any bits set? */ for(i=0;ihashed, SIGSUBPKT_FEATURES); else build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n); xfree (buf); } static void add_feature_aead (PKT_signature *sig, int enabled) { const byte *s; size_t n; int i; char *buf; s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n ); if (s && n && ((enabled && (s[0] & 0x02)) || (!enabled && !(s[0] & 0x02)))) return; /* Already set or cleared */ if (!s || !n) { /* Create a new one */ n = 1; buf = xmalloc_clear (n); } else { buf = xmalloc (n); memcpy (buf, s, n); } if (enabled) buf[0] |= 0x02; /* AEAD supported */ else buf[0] &= ~0x02; /* Are there any bits set? */ for (i=0; i < n; i++) if (buf[i]) break; if (i == n) delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES); else build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n); xfree (buf); } static void add_keyserver_modify (PKT_signature *sig,int enabled) { const byte *s; size_t n; int i; char *buf; /* The keyserver modify flag is a negative flag (i.e. no-modify) */ enabled=!enabled; s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS, &n ); /* Already set or cleared */ if (s && n && ((enabled && (s[0] & 0x80)) || (!enabled && !(s[0] & 0x80)))) return; if (!s || !n) { /* create a new one */ n = 1; buf = xmalloc_clear (n); } else { buf = xmalloc (n); memcpy (buf, s, n); } if(enabled) buf[0] |= 0x80; /* no-modify flag */ else buf[0] &= ~0x80; /* Are there any bits set? */ for(i=0;ihashed, SIGSUBPKT_KS_FLAGS); else build_sig_subpkt (sig, SIGSUBPKT_KS_FLAGS, buf, n); xfree (buf); } int keygen_upd_std_prefs (PKT_signature *sig, void *opaque) { (void)opaque; if (!prefs_initialized) keygen_set_std_prefs (NULL, 0); if (nsym_prefs) build_sig_subpkt (sig, SIGSUBPKT_PREF_SYM, sym_prefs, nsym_prefs); else { delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_SYM); delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_SYM); } if (aead_available) /* The only preference is AEAD_ALGO_OCB. */ build_sig_subpkt (sig, SIGSUBPKT_PREF_AEAD, "\x02", 1); else { delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_AEAD); delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_AEAD); } if (nhash_prefs) build_sig_subpkt (sig, SIGSUBPKT_PREF_HASH, hash_prefs, nhash_prefs); else { delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_HASH); delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_HASH); } if (nzip_prefs) build_sig_subpkt (sig, SIGSUBPKT_PREF_COMPR, zip_prefs, nzip_prefs); else { delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_COMPR); delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_COMPR); } /* Make sure that the MDC and AEAD feature flags are set as needed. */ add_feature_mdc (sig,mdc_available); add_feature_aead (sig, aead_available); add_keyserver_modify (sig,ks_modify); keygen_add_keyserver_url(sig,NULL); return 0; } /**************** * Add preference to the self signature packet. * This is only called for packets with version > 3. */ int keygen_add_std_prefs (PKT_signature *sig, void *opaque) { PKT_public_key *pk = opaque; do_add_key_flags (sig, pk->pubkey_usage); keygen_add_key_expire (sig, opaque ); keygen_upd_std_prefs (sig, opaque); keygen_add_keyserver_url (sig,NULL); return 0; } int keygen_add_keyserver_url(PKT_signature *sig, void *opaque) { const char *url=opaque; if(!url) url=opt.def_keyserver_url; if(url) build_sig_subpkt(sig,SIGSUBPKT_PREF_KS,url,strlen(url)); else delete_sig_subpkt (sig->hashed,SIGSUBPKT_PREF_KS); return 0; } /* This function is used to add a notations to a signature. In * general the caller should have cleared exiting notations before * adding new ones. For example by calling: * * delete_sig_subpkt(sig->hashed,SIGSUBPKT_NOTATION); * delete_sig_subpkt(sig->unhashed,SIGSUBPKT_NOTATION); * * Only human readable notaions may be added. NAME and value are * expected to be UTF-* strings. */ static void do_add_notation (PKT_signature *sig, const char *name, const char *value, int critical) { unsigned char *buf; unsigned int n1,n2; n1 = strlen (name); n2 = strlen (value); buf = xmalloc (8 + n1 + n2); buf[0] = 0x80; /* human readable. */ buf[1] = buf[2] = buf[3] = 0; buf[4] = n1 >> 8; buf[5] = n1; buf[6] = n2 >> 8; buf[7] = n2; memcpy (buf+8, name, n1); memcpy (buf+8+n1, value, n2); build_sig_subpkt (sig, (SIGSUBPKT_NOTATION|(critical?SIGSUBPKT_FLAG_CRITICAL:0)), buf, 8+n1+n2 ); xfree (buf); } int keygen_add_notations(PKT_signature *sig,void *opaque) { struct notation *notation; /* We always start clean */ delete_sig_subpkt(sig->hashed,SIGSUBPKT_NOTATION); delete_sig_subpkt(sig->unhashed,SIGSUBPKT_NOTATION); sig->flags.notation=0; for(notation=opaque;notation;notation=notation->next) if(!notation->flags.ignore) { unsigned char *buf; unsigned int n1,n2; n1=strlen(notation->name); if(notation->altvalue) n2=strlen(notation->altvalue); else if(notation->bdat) n2=notation->blen; else n2=strlen(notation->value); buf = xmalloc( 8 + n1 + n2 ); /* human readable or not */ buf[0] = notation->bdat?0:0x80; buf[1] = buf[2] = buf[3] = 0; buf[4] = n1 >> 8; buf[5] = n1; buf[6] = n2 >> 8; buf[7] = n2; memcpy(buf+8, notation->name, n1 ); if(notation->altvalue) memcpy(buf+8+n1, notation->altvalue, n2 ); else if(notation->bdat) memcpy(buf+8+n1, notation->bdat, n2 ); else memcpy(buf+8+n1, notation->value, n2 ); build_sig_subpkt( sig, SIGSUBPKT_NOTATION | (notation->flags.critical?SIGSUBPKT_FLAG_CRITICAL:0), buf, 8+n1+n2 ); xfree(buf); } return 0; } int keygen_add_revkey (PKT_signature *sig, void *opaque) { struct revocation_key *revkey = opaque; byte buf[2+MAX_FINGERPRINT_LEN]; buf[0] = revkey->class; buf[1] = revkey->algid; memcpy (&buf[2], revkey->fpr, MAX_FINGERPRINT_LEN); build_sig_subpkt (sig, SIGSUBPKT_REV_KEY, buf, 2+MAX_FINGERPRINT_LEN); /* All sigs with revocation keys set are nonrevocable. */ sig->flags.revocable = 0; buf[0] = 0; build_sig_subpkt (sig, SIGSUBPKT_REVOCABLE, buf, 1); parse_revkeys (sig); return 0; } /* Create a back-signature. If TIMESTAMP is not NULL, use it for the signature creation time. */ gpg_error_t make_backsig (ctrl_t ctrl, PKT_signature *sig, PKT_public_key *pk, PKT_public_key *sub_pk, PKT_public_key *sub_psk, u32 timestamp, const char *cache_nonce) { gpg_error_t err; PKT_signature *backsig; cache_public_key (sub_pk); err = make_keysig_packet (ctrl, &backsig, pk, NULL, sub_pk, sub_psk, 0x19, 0, timestamp, 0, NULL, NULL, cache_nonce); if (err) log_error ("make_keysig_packet failed for backsig: %s\n", gpg_strerror (err)); else { /* Get it into a binary packed form. */ IOBUF backsig_out = iobuf_temp(); PACKET backsig_pkt; init_packet (&backsig_pkt); backsig_pkt.pkttype = PKT_SIGNATURE; backsig_pkt.pkt.signature = backsig; err = build_packet (backsig_out, &backsig_pkt); free_packet (&backsig_pkt, NULL); if (err) log_error ("build_packet failed for backsig: %s\n", gpg_strerror (err)); else { size_t pktlen = 0; byte *buf = iobuf_get_temp_buffer (backsig_out); /* Remove the packet header. */ if(buf[0]&0x40) { if (buf[1] < 192) { pktlen = buf[1]; buf += 2; } else if(buf[1] < 224) { pktlen = (buf[1]-192)*256; pktlen += buf[2]+192; buf += 3; } else if (buf[1] == 255) { pktlen = buf32_to_size_t (buf+2); buf += 6; } else BUG (); } else { int mark = 1; switch (buf[0]&3) { case 3: BUG (); break; case 2: pktlen = (size_t)buf[mark++] << 24; pktlen |= buf[mark++] << 16; /* fall through */ case 1: pktlen |= buf[mark++] << 8; /* fall through */ case 0: pktlen |= buf[mark++]; } buf += mark; } /* Now make the binary blob into a subpacket. */ build_sig_subpkt (sig, SIGSUBPKT_SIGNATURE, buf, pktlen); iobuf_close (backsig_out); } } return err; } /* Write a direct key signature to the first key in ROOT using the key PSK. REVKEY is describes the direct key signature and TIMESTAMP is the timestamp to set on the signature. */ static gpg_error_t write_direct_sig (ctrl_t ctrl, kbnode_t root, PKT_public_key *psk, struct revocation_key *revkey, u32 timestamp, const char *cache_nonce) { gpg_error_t err; PACKET *pkt; PKT_signature *sig; KBNODE node; PKT_public_key *pk; if (opt.verbose) log_info (_("writing direct signature\n")); /* Get the pk packet from the pub_tree. */ node = find_kbnode (root, PKT_PUBLIC_KEY); if (!node) BUG (); pk = node->pkt->pkt.public_key; /* We have to cache the key, so that the verification of the signature creation is able to retrieve the public key. */ cache_public_key (pk); /* Make the signature. */ err = make_keysig_packet (ctrl, &sig, pk, NULL,NULL, psk, 0x1F, 0, timestamp, 0, keygen_add_revkey, revkey, cache_nonce); if (err) { log_error ("make_keysig_packet failed: %s\n", gpg_strerror (err) ); return err; } pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; add_kbnode (root, new_kbnode (pkt)); return err; } /* Write a self-signature to the first user id in ROOT using the key PSK. USE and TIMESTAMP give the extra data we need for the signature. */ static gpg_error_t write_selfsigs (ctrl_t ctrl, kbnode_t root, PKT_public_key *psk, unsigned int use, u32 timestamp, const char *cache_nonce) { gpg_error_t err; PACKET *pkt; PKT_signature *sig; PKT_user_id *uid; KBNODE node; PKT_public_key *pk; if (opt.verbose) log_info (_("writing self signature\n")); /* Get the uid packet from the list. */ node = find_kbnode (root, PKT_USER_ID); if (!node) BUG(); /* No user id packet in tree. */ uid = node->pkt->pkt.user_id; /* Get the pk packet from the pub_tree. */ node = find_kbnode (root, PKT_PUBLIC_KEY); if (!node) BUG(); pk = node->pkt->pkt.public_key; /* The usage has not yet been set - do it now. */ pk->pubkey_usage = use; /* We have to cache the key, so that the verification of the signature creation is able to retrieve the public key. */ cache_public_key (pk); /* Make the signature. */ err = make_keysig_packet (ctrl, &sig, pk, uid, NULL, psk, 0x13, 0, timestamp, 0, keygen_add_std_prefs, pk, cache_nonce); if (err) { log_error ("make_keysig_packet failed: %s\n", gpg_strerror (err)); return err; } pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; add_kbnode (root, new_kbnode (pkt)); return err; } /* Write the key binding signature. If TIMESTAMP is not NULL use the signature creation time. PRI_PSK is the key use for signing. SUB_PSK is a key used to create a back-signature; that one is only used if USE has the PUBKEY_USAGE_SIG capability. */ static int write_keybinding (ctrl_t ctrl, kbnode_t root, PKT_public_key *pri_psk, PKT_public_key *sub_psk, unsigned int use, u32 timestamp, const char *cache_nonce) { gpg_error_t err; PACKET *pkt; PKT_signature *sig; KBNODE node; PKT_public_key *pri_pk, *sub_pk; struct opaque_data_usage_and_pk oduap; if (opt.verbose) log_info(_("writing key binding signature\n")); /* Get the primary pk packet from the tree. */ node = find_kbnode (root, PKT_PUBLIC_KEY); if (!node) BUG(); pri_pk = node->pkt->pkt.public_key; /* We have to cache the key, so that the verification of the * signature creation is able to retrieve the public key. */ cache_public_key (pri_pk); /* Find the last subkey. */ sub_pk = NULL; for (node = root; node; node = node->next ) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) sub_pk = node->pkt->pkt.public_key; } if (!sub_pk) BUG(); /* Make the signature. */ oduap.usage = use; if ((use & PUBKEY_USAGE_ENC) && opt.compliance == CO_DE_VS && gnupg_rng_is_compliant (CO_DE_VS)) oduap.cpl_notation = "de-vs"; else oduap.cpl_notation = NULL; oduap.pk = sub_pk; err = make_keysig_packet (ctrl, &sig, pri_pk, NULL, sub_pk, pri_psk, 0x18, 0, timestamp, 0, keygen_add_key_flags_and_expire, &oduap, cache_nonce); if (err) { log_error ("make_keysig_packeto failed: %s\n", gpg_strerror (err)); return err; } /* Make a backsig. */ if (use & PUBKEY_USAGE_SIG) { err = make_backsig (ctrl, sig, pri_pk, sub_pk, sub_psk, timestamp, cache_nonce); if (err) return err; } pkt = xmalloc_clear ( sizeof *pkt ); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; add_kbnode (root, new_kbnode (pkt) ); return err; } static gpg_error_t ecckey_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp, int algo) { gpg_error_t err; gcry_sexp_t list, l2; char *curve = NULL; int i; const char *oidstr; unsigned int nbits; array[0] = NULL; array[1] = NULL; array[2] = NULL; list = gcry_sexp_find_token (sexp, "public-key", 0); if (!list) return gpg_error (GPG_ERR_INV_OBJ); l2 = gcry_sexp_cadr (list); gcry_sexp_release (list); list = l2; if (!list) return gpg_error (GPG_ERR_NO_OBJ); l2 = gcry_sexp_find_token (list, "curve", 0); if (!l2) { err = gpg_error (GPG_ERR_NO_OBJ); goto leave; } curve = gcry_sexp_nth_string (l2, 1); if (!curve) { err = gpg_error (GPG_ERR_NO_OBJ); goto leave; } gcry_sexp_release (l2); oidstr = openpgp_curve_to_oid (curve, &nbits, NULL); if (!oidstr) { /* That can't happen because we used one of the curves gpg_curve_to_oid knows about. */ err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } err = openpgp_oid_from_str (oidstr, &array[0]); if (err) goto leave; l2 = gcry_sexp_find_token (list, "q", 0); if (!l2) { err = gpg_error (GPG_ERR_NO_OBJ); goto leave; } array[1] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG); gcry_sexp_release (l2); if (!array[1]) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } gcry_sexp_release (list); if (algo == PUBKEY_ALGO_ECDH) { array[2] = pk_ecdh_default_params (nbits); if (!array[2]) { err = gpg_error_from_syserror (); goto leave; } } leave: xfree (curve); if (err) { for (i=0; i < 3; i++) { gcry_mpi_release (array[i]); array[i] = NULL; } } return err; } /* Extract key parameters from SEXP and store them in ARRAY. ELEMS is a string where each character denotes a parameter name. TOPNAME is the name of the top element above the elements. */ static int key_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp, const char *topname, const char *elems) { gcry_sexp_t list, l2; const char *s; int i, idx; int rc = 0; list = gcry_sexp_find_token (sexp, topname, 0); if (!list) return gpg_error (GPG_ERR_INV_OBJ); l2 = gcry_sexp_cadr (list); gcry_sexp_release (list); list = l2; if (!list) return gpg_error (GPG_ERR_NO_OBJ); for (idx=0,s=elems; *s; s++, idx++) { l2 = gcry_sexp_find_token (list, s, 1); if (!l2) { rc = gpg_error (GPG_ERR_NO_OBJ); /* required parameter not found */ goto leave; } array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG); gcry_sexp_release (l2); if (!array[idx]) { rc = gpg_error (GPG_ERR_INV_OBJ); /* required parameter invalid */ goto leave; } } gcry_sexp_release (list); leave: if (rc) { for (i=0; itimestamp = timestamp; pk->version = 4; if (expireval) pk->expiredate = pk->timestamp + expireval; pk->pubkey_algo = algo; if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA || algo == PUBKEY_ALGO_ECDH ) err = ecckey_from_sexp (pk->pkey, s_key, algo); else err = key_from_sexp (pk->pkey, s_key, "public-key", algoelem); if (err) { log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) ); gcry_sexp_release (s_key); free_public_key (pk); return err; } gcry_sexp_release (s_key); pkt = xtrycalloc (1, sizeof *pkt); if (!pkt) { err = gpg_error_from_syserror (); free_public_key (pk); return err; } pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY; pkt->pkt.public_key = pk; add_kbnode (pub_root, new_kbnode (pkt)); return 0; } /* Common code for the key generation function gen_xxx. */ static int common_gen (const char *keyparms, int algo, const char *algoelem, kbnode_t pub_root, u32 timestamp, u32 expireval, int is_subkey, int keygen_flags, const char *passphrase, char **cache_nonce_addr, char **passwd_nonce_addr) { int err; PACKET *pkt; PKT_public_key *pk; gcry_sexp_t s_key; err = agent_genkey (NULL, cache_nonce_addr, passwd_nonce_addr, keyparms, !!(keygen_flags & KEYGEN_FLAG_NO_PROTECTION), passphrase, timestamp, &s_key); if (err) { log_error ("agent_genkey failed: %s\n", gpg_strerror (err) ); return err; } pk = xtrycalloc (1, sizeof *pk); if (!pk) { err = gpg_error_from_syserror (); gcry_sexp_release (s_key); return err; } pk->timestamp = timestamp; pk->version = 4; if (expireval) pk->expiredate = pk->timestamp + expireval; pk->pubkey_algo = algo; if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA || algo == PUBKEY_ALGO_ECDH ) err = ecckey_from_sexp (pk->pkey, s_key, algo); else err = key_from_sexp (pk->pkey, s_key, "public-key", algoelem); if (err) { log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) ); gcry_sexp_release (s_key); free_public_key (pk); return err; } gcry_sexp_release (s_key); pkt = xtrycalloc (1, sizeof *pkt); if (!pkt) { err = gpg_error_from_syserror (); free_public_key (pk); return err; } pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY; pkt->pkt.public_key = pk; add_kbnode (pub_root, new_kbnode (pkt)); return 0; } /* * Generate an Elgamal key. */ static int gen_elg (int algo, unsigned int nbits, KBNODE pub_root, u32 timestamp, u32 expireval, int is_subkey, int keygen_flags, const char *passphrase, char **cache_nonce_addr, char **passwd_nonce_addr) { int err; char *keyparms; char nbitsstr[35]; log_assert (is_ELGAMAL (algo)); if (nbits < 1024) { nbits = 2048; log_info (_("keysize invalid; using %u bits\n"), nbits ); } else if (nbits > 4096) { nbits = 4096; log_info (_("keysize invalid; using %u bits\n"), nbits ); } if ((nbits % 32)) { nbits = ((nbits + 31) / 32) * 32; log_info (_("keysize rounded up to %u bits\n"), nbits ); } /* Note that we use transient-key only if no-protection has also been enabled. */ snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits); keyparms = xtryasprintf ("(genkey(%s(nbits %zu:%s)%s))", algo == GCRY_PK_ELG_E ? "openpgp-elg" : algo == GCRY_PK_ELG ? "elg" : "x-oops" , strlen (nbitsstr), nbitsstr, ((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY) && (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))? "(transient-key)" : "" ); if (!keyparms) err = gpg_error_from_syserror (); else { err = common_gen (keyparms, algo, "pgy", pub_root, timestamp, expireval, is_subkey, keygen_flags, passphrase, cache_nonce_addr, passwd_nonce_addr); xfree (keyparms); } return err; } /* * Generate an DSA key */ static gpg_error_t gen_dsa (unsigned int nbits, KBNODE pub_root, u32 timestamp, u32 expireval, int is_subkey, int keygen_flags, const char *passphrase, char **cache_nonce_addr, char **passwd_nonce_addr) { int err; unsigned int qbits; char *keyparms; char nbitsstr[35]; char qbitsstr[35]; if (nbits < 768) { nbits = 2048; log_info(_("keysize invalid; using %u bits\n"), nbits ); } else if ( nbits > 3072 ) { nbits = 3072; log_info(_("keysize invalid; using %u bits\n"), nbits ); } if( (nbits % 64) ) { nbits = ((nbits + 63) / 64) * 64; log_info(_("keysize rounded up to %u bits\n"), nbits ); } /* To comply with FIPS rules we round up to the next value unless in expert mode. */ if (!opt.expert && nbits > 1024 && (nbits % 1024)) { nbits = ((nbits + 1023) / 1024) * 1024; log_info(_("keysize rounded up to %u bits\n"), nbits ); } /* Figure out a q size based on the key size. FIPS 180-3 says: L = 1024, N = 160 L = 2048, N = 224 L = 2048, N = 256 L = 3072, N = 256 2048/256 is an odd pair since there is also a 2048/224 and 3072/256. Matching sizes is not a very exact science. We'll do 256 qbits for nbits over 2047, 224 for nbits over 1024 but less than 2048, and 160 for 1024 (DSA1). */ if (nbits > 2047) qbits = 256; else if ( nbits > 1024) qbits = 224; else qbits = 160; if (qbits != 160 ) log_info (_("WARNING: some OpenPGP programs can't" " handle a DSA key with this digest size\n")); snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits); snprintf (qbitsstr, sizeof qbitsstr, "%u", qbits); keyparms = xtryasprintf ("(genkey(dsa(nbits %zu:%s)(qbits %zu:%s)%s))", strlen (nbitsstr), nbitsstr, strlen (qbitsstr), qbitsstr, ((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY) && (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))? "(transient-key)" : "" ); if (!keyparms) err = gpg_error_from_syserror (); else { err = common_gen (keyparms, PUBKEY_ALGO_DSA, "pqgy", pub_root, timestamp, expireval, is_subkey, keygen_flags, passphrase, cache_nonce_addr, passwd_nonce_addr); xfree (keyparms); } return err; } /* * Generate an ECC key */ static gpg_error_t gen_ecc (int algo, const char *curve, kbnode_t pub_root, u32 timestamp, u32 expireval, int is_subkey, int keygen_flags, const char *passphrase, char **cache_nonce_addr, char **passwd_nonce_addr) { gpg_error_t err; char *keyparms; log_assert (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA || algo == PUBKEY_ALGO_ECDH); if (!curve || !*curve) return gpg_error (GPG_ERR_UNKNOWN_CURVE); /* Map the displayed short forms of some curves to their canonical * names. */ if (!ascii_strcasecmp (curve, "cv25519")) curve = "Curve25519"; else if (!ascii_strcasecmp (curve, "ed25519")) curve = "Ed25519"; /* Note that we use the "comp" flag with EdDSA to request the use of a 0x40 compression prefix octet. */ if (algo == PUBKEY_ALGO_EDDSA) keyparms = xtryasprintf ("(genkey(ecc(curve %zu:%s)(flags eddsa comp%s)))", strlen (curve), curve, (((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY) && (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))? " transient-key" : "")); else if (algo == PUBKEY_ALGO_ECDH && !strcmp (curve, "Curve25519")) keyparms = xtryasprintf ("(genkey(ecc(curve %zu:%s)(flags djb-tweak comp%s)))", strlen (curve), curve, (((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY) && (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))? " transient-key" : "")); else keyparms = xtryasprintf ("(genkey(ecc(curve %zu:%s)(flags nocomp%s)))", strlen (curve), curve, (((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY) && (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))? " transient-key" : "")); if (!keyparms) err = gpg_error_from_syserror (); else { err = common_gen (keyparms, algo, "", pub_root, timestamp, expireval, is_subkey, keygen_flags, passphrase, cache_nonce_addr, passwd_nonce_addr); xfree (keyparms); } return err; } /* * Generate an RSA key. */ static int gen_rsa (int algo, unsigned int nbits, KBNODE pub_root, u32 timestamp, u32 expireval, int is_subkey, int keygen_flags, const char *passphrase, char **cache_nonce_addr, char **passwd_nonce_addr) { int err; char *keyparms; char nbitsstr[35]; const unsigned maxsize = (opt.flags.large_rsa ? 8192 : 4096); log_assert (is_RSA(algo)); if (!nbits) nbits = get_keysize_range (algo, NULL, NULL); if (nbits < 1024) { nbits = 3072; log_info (_("keysize invalid; using %u bits\n"), nbits ); } else if (nbits > maxsize) { nbits = maxsize; log_info (_("keysize invalid; using %u bits\n"), nbits ); } if ((nbits % 32)) { nbits = ((nbits + 31) / 32) * 32; log_info (_("keysize rounded up to %u bits\n"), nbits ); } snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits); keyparms = xtryasprintf ("(genkey(rsa(nbits %zu:%s)%s))", strlen (nbitsstr), nbitsstr, ((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY) && (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))? "(transient-key)" : "" ); if (!keyparms) err = gpg_error_from_syserror (); else { err = common_gen (keyparms, algo, "ne", pub_root, timestamp, expireval, is_subkey, keygen_flags, passphrase, cache_nonce_addr, passwd_nonce_addr); xfree (keyparms); } return err; } /**************** * check valid days: * return 0 on error or the multiplier */ static int check_valid_days( const char *s ) { if( !digitp(s) ) return 0; for( s++; *s; s++) if( !digitp(s) ) break; if( !*s ) return 1; if( s[1] ) return 0; /* e.g. "2323wc" */ if( *s == 'd' || *s == 'D' ) return 1; if( *s == 'w' || *s == 'W' ) return 7; if( *s == 'm' || *s == 'M' ) return 30; if( *s == 'y' || *s == 'Y' ) return 365; return 0; } static void print_key_flags(int flags) { if(flags&PUBKEY_USAGE_SIG) tty_printf("%s ",_("Sign")); if(flags&PUBKEY_USAGE_CERT) tty_printf("%s ",_("Certify")); if(flags&PUBKEY_USAGE_ENC) tty_printf("%s ",_("Encrypt")); if(flags&PUBKEY_USAGE_AUTH) tty_printf("%s ",_("Authenticate")); } /* Ask for the key flags and return them. CURRENT gives the current * usage which should normally be given as 0. MASK gives the allowed * flags. */ unsigned int ask_key_flags_with_mask (int algo, int subkey, unsigned int current, unsigned int mask) { /* TRANSLATORS: Please use only plain ASCII characters for the * translation. If this is not possible use single digits. The * string needs to 8 bytes long. Here is a description of the * functions: * * s = Toggle signing capability * e = Toggle encryption capability * a = Toggle authentication capability * q = Finish */ const char *togglers = _("SsEeAaQq"); char *answer = NULL; const char *s; unsigned int possible; if ( strlen(togglers) != 8 ) { tty_printf ("NOTE: Bad translation at %s:%d. " "Please report.\n", __FILE__, __LINE__); togglers = "11223300"; } /* Mask the possible usage flags. This is for example used for a * card based key. */ possible = (openpgp_pk_algo_usage (algo) & mask); /* However, only primary keys may certify. */ if (subkey) possible &= ~PUBKEY_USAGE_CERT; /* Preload the current set with the possible set, without * authentication if CURRENT is 0. If CURRENT is non-zero we mask * with all possible usages. */ if (current) current &= possible; else current = (possible&~PUBKEY_USAGE_AUTH); for (;;) { tty_printf("\n"); tty_printf(_("Possible actions for a %s key: "), (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA) ? "ECDSA/EdDSA" : openpgp_pk_algo_name (algo)); print_key_flags(possible); tty_printf("\n"); tty_printf(_("Current allowed actions: ")); print_key_flags(current); tty_printf("\n\n"); if(possible&PUBKEY_USAGE_SIG) tty_printf(_(" (%c) Toggle the sign capability\n"), togglers[0]); if(possible&PUBKEY_USAGE_ENC) tty_printf(_(" (%c) Toggle the encrypt capability\n"), togglers[2]); if(possible&PUBKEY_USAGE_AUTH) tty_printf(_(" (%c) Toggle the authenticate capability\n"), togglers[4]); tty_printf(_(" (%c) Finished\n"),togglers[6]); tty_printf("\n"); xfree(answer); answer = cpr_get("keygen.flags",_("Your selection? ")); cpr_kill_prompt(); if (*answer == '=') { /* Hack to allow direct entry of the capabilities. */ current = 0; for (s=answer+1; *s; s++) { if ((*s == 's' || *s == 'S') && (possible&PUBKEY_USAGE_SIG)) current |= PUBKEY_USAGE_SIG; else if ((*s == 'e' || *s == 'E') && (possible&PUBKEY_USAGE_ENC)) current |= PUBKEY_USAGE_ENC; else if ((*s == 'a' || *s == 'A') && (possible&PUBKEY_USAGE_AUTH)) current |= PUBKEY_USAGE_AUTH; else if (!subkey && *s == 'c') { /* Accept 'c' for the primary key because USAGE_CERT will be set anyway. This is for folks who want to experiment with a cert-only primary key. */ current |= PUBKEY_USAGE_CERT; } } break; } else if (strlen(answer)>1) tty_printf(_("Invalid selection.\n")); else if(*answer=='\0' || *answer==togglers[6] || *answer==togglers[7]) break; else if((*answer==togglers[0] || *answer==togglers[1]) && possible&PUBKEY_USAGE_SIG) { if(current&PUBKEY_USAGE_SIG) current&=~PUBKEY_USAGE_SIG; else current|=PUBKEY_USAGE_SIG; } else if((*answer==togglers[2] || *answer==togglers[3]) && possible&PUBKEY_USAGE_ENC) { if(current&PUBKEY_USAGE_ENC) current&=~PUBKEY_USAGE_ENC; else current|=PUBKEY_USAGE_ENC; } else if((*answer==togglers[4] || *answer==togglers[5]) && possible&PUBKEY_USAGE_AUTH) { if(current&PUBKEY_USAGE_AUTH) current&=~PUBKEY_USAGE_AUTH; else current|=PUBKEY_USAGE_AUTH; } else tty_printf(_("Invalid selection.\n")); } xfree(answer); return current; } unsigned int ask_key_flags (int algo, int subkey, unsigned int current) { return ask_key_flags_with_mask (algo, subkey, current, ~0); } /* Check whether we have a key for the key with HEXGRIP. Returns 0 if there is no such key or the OpenPGP algo number for the key. */ static int check_keygrip (ctrl_t ctrl, const char *hexgrip) { gpg_error_t err; unsigned char *public; size_t publiclen; int algo; if (hexgrip[0] == '&') hexgrip++; err = agent_readkey (ctrl, 0, hexgrip, &public); if (err) return 0; publiclen = gcry_sexp_canon_len (public, 0, NULL, NULL); algo = get_pk_algo_from_canon_sexp (public, publiclen); xfree (public); return map_pk_gcry_to_openpgp (algo); } /* Ask for an algorithm. The function returns the algorithm id to * create. If ADDMODE is false the function won't show an option to * create the primary and subkey combined and won't set R_USAGE * either. If a combined algorithm has been selected, the subkey * algorithm is stored at R_SUBKEY_ALGO. If R_KEYGRIP is given, the * user has the choice to enter the keygrip of an existing key. That * keygrip is then stored at this address. The caller needs to free * it. */ static int ask_algo (ctrl_t ctrl, int addmode, int *r_subkey_algo, unsigned int *r_usage, char **r_keygrip) { gpg_error_t err; char *keygrip = NULL; char *answer = NULL; int algo; int dummy_algo; char *p; if (!r_subkey_algo) r_subkey_algo = &dummy_algo; tty_printf (_("Please select what kind of key you want:\n")); #if GPG_USE_RSA if (!addmode) tty_printf (_(" (%d) RSA and RSA (default)\n"), 1 ); #endif if (!addmode && opt.compliance != CO_DE_VS) tty_printf (_(" (%d) DSA and Elgamal\n"), 2 ); if (opt.compliance != CO_DE_VS) tty_printf (_(" (%d) DSA (sign only)\n"), 3 ); #if GPG_USE_RSA tty_printf (_(" (%d) RSA (sign only)\n"), 4 ); #endif if (addmode) { if (opt.compliance != CO_DE_VS) tty_printf (_(" (%d) Elgamal (encrypt only)\n"), 5 ); #if GPG_USE_RSA tty_printf (_(" (%d) RSA (encrypt only)\n"), 6 ); #endif } if (opt.expert) { if (opt.compliance != CO_DE_VS) tty_printf (_(" (%d) DSA (set your own capabilities)\n"), 7 ); #if GPG_USE_RSA tty_printf (_(" (%d) RSA (set your own capabilities)\n"), 8 ); #endif } #if GPG_USE_ECDSA || GPG_USE_ECDH || GPG_USE_EDDSA if (opt.expert && !addmode) tty_printf (_(" (%d) ECC and ECC\n"), 9 ); if (opt.expert) tty_printf (_(" (%d) ECC (sign only)\n"), 10 ); if (opt.expert) tty_printf (_(" (%d) ECC (set your own capabilities)\n"), 11 ); if (opt.expert && addmode) tty_printf (_(" (%d) ECC (encrypt only)\n"), 12 ); #endif if (opt.expert && r_keygrip) tty_printf (_(" (%d) Existing key\n"), 13 ); if (r_keygrip) tty_printf (_(" (%d) Existing key from card\n"), 14 ); for (;;) { *r_usage = 0; *r_subkey_algo = 0; xfree (answer); answer = cpr_get ("keygen.algo", _("Your selection? ")); cpr_kill_prompt (); algo = *answer? atoi (answer) : 1; if (opt.compliance == CO_DE_VS && (algo == 2 || algo == 3 || algo == 5 || algo == 7)) { tty_printf (_("Invalid selection.\n")); } else if ((algo == 1 || !strcmp (answer, "rsa+rsa")) && !addmode) { algo = PUBKEY_ALGO_RSA; *r_subkey_algo = PUBKEY_ALGO_RSA; break; } else if ((algo == 2 || !strcmp (answer, "dsa+elg")) && !addmode) { algo = PUBKEY_ALGO_DSA; *r_subkey_algo = PUBKEY_ALGO_ELGAMAL_E; break; } else if (algo == 3 || !strcmp (answer, "dsa")) { algo = PUBKEY_ALGO_DSA; *r_usage = PUBKEY_USAGE_SIG; break; } else if (algo == 4 || !strcmp (answer, "rsa/s")) { algo = PUBKEY_ALGO_RSA; *r_usage = PUBKEY_USAGE_SIG; break; } else if ((algo == 5 || !strcmp (answer, "elg")) && addmode) { algo = PUBKEY_ALGO_ELGAMAL_E; *r_usage = PUBKEY_USAGE_ENC; break; } else if ((algo == 6 || !strcmp (answer, "rsa/e")) && addmode) { algo = PUBKEY_ALGO_RSA; *r_usage = PUBKEY_USAGE_ENC; break; } else if ((algo == 7 || !strcmp (answer, "dsa/*")) && opt.expert) { algo = PUBKEY_ALGO_DSA; *r_usage = ask_key_flags (algo, addmode, 0); break; } else if ((algo == 8 || !strcmp (answer, "rsa/*")) && opt.expert) { algo = PUBKEY_ALGO_RSA; *r_usage = ask_key_flags (algo, addmode, 0); break; } else if ((algo == 9 || !strcmp (answer, "ecc+ecc")) && opt.expert && !addmode) { algo = PUBKEY_ALGO_ECDSA; *r_subkey_algo = PUBKEY_ALGO_ECDH; break; } else if ((algo == 10 || !strcmp (answer, "ecc/s")) && opt.expert) { algo = PUBKEY_ALGO_ECDSA; *r_usage = PUBKEY_USAGE_SIG; break; } else if ((algo == 11 || !strcmp (answer, "ecc/*")) && opt.expert) { algo = PUBKEY_ALGO_ECDSA; *r_usage = ask_key_flags (algo, addmode, 0); break; } else if ((algo == 12 || !strcmp (answer, "ecc/e")) && opt.expert && addmode) { algo = PUBKEY_ALGO_ECDH; *r_usage = PUBKEY_USAGE_ENC; break; } else if ((algo == 13 || !strcmp (answer, "keygrip")) && opt.expert && r_keygrip) { for (;;) { xfree (answer); answer = cpr_get ("keygen.keygrip", _("Enter the keygrip: ")); cpr_kill_prompt (); trim_spaces (answer); if (!*answer) { xfree (answer); answer = NULL; continue; } if (strlen (answer) != 40 && !(answer[0] == '&' && strlen (answer+1) == 40)) tty_printf (_("Not a valid keygrip (expecting 40 hex digits)\n")); else if (!(algo = check_keygrip (ctrl, answer)) ) tty_printf (_("No key with this keygrip\n")); else break; /* Okay. */ } xfree (keygrip); keygrip = answer; answer = NULL; *r_usage = ask_key_flags (algo, addmode, 0); break; } else if ((algo == 14 || !strcmp (answer, "cardkey")) && r_keygrip) { char *serialno; strlist_t keypairlist, sl; int count, selection; err = agent_scd_serialno (&serialno, NULL); if (err) { tty_printf (_("error reading the card: %s\n"), gpg_strerror (err)); goto ask_again; } tty_printf (_("Serial number of the card: %s\n"), serialno); xfree (serialno); err = agent_scd_keypairinfo (ctrl, &keypairlist); if (err) { tty_printf (_("error reading the card: %s\n"), gpg_strerror (err)); goto ask_again; } do { tty_printf (_("Available keys:\n")); for (count=1,sl=keypairlist; sl; sl = sl->next, count++) { gcry_sexp_t s_pkey; char *algostr = NULL; enum gcry_pk_algos algoid = 0; const char *keyref; int any = 0; keyref = strchr (sl->d, ' '); if (keyref) { keyref++; if (!agent_scd_readkey (keyref, &s_pkey)) { algostr = pubkey_algo_string (s_pkey, &algoid); gcry_sexp_release (s_pkey); } } /* We use the flags also encode the algo for use * below. We need to tweak the algo in case * GCRY_PK_ECC is returned becuase pubkey_algo_string * is not aware of the OpenPGP algo mapping. * FIXME: This is an ugly hack. */ sl->flags &= 0xff; if (algoid == GCRY_PK_ECC && algostr && !strncmp (algostr, "nistp", 5) && !(sl->flags & GCRY_PK_USAGE_ENCR)) sl->flags |= (PUBKEY_ALGO_ECDSA << 8); else if (algoid == GCRY_PK_ECC && algostr && !strncmp (algostr, "brainpool", 9) && !(sl->flags & GCRY_PK_USAGE_ENCR)) sl->flags |= (PUBKEY_ALGO_ECDSA << 8); else if (algoid == GCRY_PK_ECC && algostr && !strcmp (algostr, "ed25519") && !(sl->flags & GCRY_PK_USAGE_ENCR)) sl->flags = (PUBKEY_ALGO_EDDSA << 8); else sl->flags |= (map_pk_gcry_to_openpgp (algoid) << 8); tty_printf (" (%d) %s %s", count, sl->d, algostr); if ((sl->flags & GCRY_PK_USAGE_CERT)) { tty_printf ("%scert", any?",":" ("); any = 1; } if ((sl->flags & GCRY_PK_USAGE_SIGN)) { tty_printf ("%ssign", any?",":" ("); any = 1; } if ((sl->flags & GCRY_PK_USAGE_AUTH)) { tty_printf ("%sauth", any?",":" ("); any = 1; } if ((sl->flags & GCRY_PK_USAGE_ENCR)) { tty_printf ("%sencr", any?",":" ("); any = 1; } tty_printf ("%s\n", any?")":""); xfree (algostr); } xfree (answer); answer = cpr_get ("keygen.cardkey", _("Your selection? ")); cpr_kill_prompt (); trim_spaces (answer); selection = atoi (answer); } while (!(selection > 0 && selection < count)); for (count=1,sl=keypairlist; sl; sl = sl->next, count++) if (count == selection) break; if (!sl) { /* Just in case COUNT is zero (no keys). */ free_strlist (keypairlist); goto ask_again; } xfree (keygrip); keygrip = xstrdup (sl->d); if ((p = strchr (keygrip, ' '))) *p = 0; algo = (sl->flags >>8); if (opt.expert) *r_usage = ask_key_flags_with_mask (algo, addmode, (sl->flags & 0xff), (sl->flags & 0xff)); else { *r_usage = (sl->flags & 0xff); if (addmode) *r_usage &= ~GCRY_PK_USAGE_CERT; } free_strlist (keypairlist); break; } else tty_printf (_("Invalid selection.\n")); ask_again: ; } xfree(answer); if (r_keygrip) *r_keygrip = keygrip; return algo; } static unsigned int get_keysize_range (int algo, unsigned int *min, unsigned int *max) { unsigned int def; unsigned int dummy1, dummy2; if (!min) min = &dummy1; if (!max) max = &dummy2; switch(algo) { case PUBKEY_ALGO_DSA: *min = opt.expert? 768 : 1024; *max=3072; def=2048; break; case PUBKEY_ALGO_ECDSA: case PUBKEY_ALGO_ECDH: *min=256; *max=521; def=256; break; case PUBKEY_ALGO_EDDSA: *min=255; *max=441; def=255; break; default: *min = opt.compliance == CO_DE_VS ? 2048: 1024; *max = 4096; def = 3072; break; } return def; } /* Return a fixed up keysize depending on ALGO. */ static unsigned int fixup_keysize (unsigned int nbits, int algo, int silent) { if (algo == PUBKEY_ALGO_DSA && (nbits % 64)) { nbits = ((nbits + 63) / 64) * 64; if (!silent) tty_printf (_("rounded up to %u bits\n"), nbits); } else if (algo == PUBKEY_ALGO_EDDSA) { if (nbits != 255 && nbits != 441) { if (nbits < 256) nbits = 255; else nbits = 441; if (!silent) tty_printf (_("rounded to %u bits\n"), nbits); } } else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA) { if (nbits != 256 && nbits != 384 && nbits != 521) { if (nbits < 256) nbits = 256; else if (nbits < 384) nbits = 384; else nbits = 521; if (!silent) tty_printf (_("rounded to %u bits\n"), nbits); } } else if ((nbits % 32)) { nbits = ((nbits + 31) / 32) * 32; if (!silent) tty_printf (_("rounded up to %u bits\n"), nbits ); } return nbits; } /* Ask for the key size. ALGO is the algorithm. If PRIMARY_KEYSIZE is not 0, the function asks for the size of the encryption subkey. */ static unsigned ask_keysize (int algo, unsigned int primary_keysize) { unsigned int nbits; unsigned int min, def, max; int for_subkey = !!primary_keysize; int autocomp = 0; def = get_keysize_range (algo, &min, &max); if (primary_keysize && !opt.expert) { /* Deduce the subkey size from the primary key size. */ if (algo == PUBKEY_ALGO_DSA && primary_keysize > 3072) nbits = 3072; /* For performance reasons we don't support more than 3072 bit DSA. However we won't see this case anyway because DSA can't be used as an encryption subkey ;-). */ else nbits = primary_keysize; autocomp = 1; goto leave; } tty_printf(_("%s keys may be between %u and %u bits long.\n"), openpgp_pk_algo_name (algo), min, max); for (;;) { char *prompt, *answer; if (for_subkey) prompt = xasprintf (_("What keysize do you want " "for the subkey? (%u) "), def); else prompt = xasprintf (_("What keysize do you want? (%u) "), def); answer = cpr_get ("keygen.size", prompt); cpr_kill_prompt (); nbits = *answer? atoi (answer): def; xfree(prompt); xfree(answer); if(nbitsmax) tty_printf(_("%s keysizes must be in the range %u-%u\n"), openpgp_pk_algo_name (algo), min, max); else break; } tty_printf (_("Requested keysize is %u bits\n"), nbits); leave: nbits = fixup_keysize (nbits, algo, autocomp); return nbits; } /* Ask for the curve. ALGO is the selected algorithm which this function may adjust. Returns a const string of the name of the curve. */ const char * ask_curve (int *algo, int *subkey_algo, const char *current) { /* NB: We always use a complete algo list so that we have stable numbers in the menu regardless on how Gpg was configured. */ struct { const char *name; const char* eddsa_curve; /* Corresponding EdDSA curve. */ const char *pretty_name; unsigned int supported : 1; /* Supported by gpg. */ unsigned int de_vs : 1; /* Allowed in CO_DE_VS. */ unsigned int expert_only : 1; /* Only with --expert */ unsigned int available : 1; /* Available in Libycrypt (runtime checked) */ } curves[] = { #if GPG_USE_ECDSA || GPG_USE_ECDH # define MY_USE_ECDSADH 1 #else # define MY_USE_ECDSADH 0 #endif { "Curve25519", "Ed25519", "Curve 25519", !!GPG_USE_EDDSA, 0, 0, 0 }, { "Curve448", "Ed448", "Curve 448", 0/*reserved*/ , 0, 1, 0 }, { "NIST P-256", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 }, { "NIST P-384", NULL, NULL, MY_USE_ECDSADH, 0, 0, 0 }, { "NIST P-521", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 }, { "brainpoolP256r1", NULL, "Brainpool P-256", MY_USE_ECDSADH, 1, 1, 0 }, { "brainpoolP384r1", NULL, "Brainpool P-384", MY_USE_ECDSADH, 1, 1, 0 }, { "brainpoolP512r1", NULL, "Brainpool P-512", MY_USE_ECDSADH, 1, 1, 0 }, { "secp256k1", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 }, }; #undef MY_USE_ECDSADH int idx; char *answer; const char *result = NULL; gcry_sexp_t keyparms; tty_printf (_("Please select which elliptic curve you want:\n")); keyparms = NULL; for (idx=0; idx < DIM(curves); idx++) { int rc; curves[idx].available = 0; if (!curves[idx].supported) continue; if (opt.compliance==CO_DE_VS) { if (!curves[idx].de_vs) continue; /* Not allowed. */ } else if (!opt.expert && curves[idx].expert_only) continue; /* We need to switch from the ECDH name of the curve to the EDDSA name of the curve if we want a signing key. */ gcry_sexp_release (keyparms); rc = gcry_sexp_build (&keyparms, NULL, "(public-key(ecc(curve %s)))", curves[idx].eddsa_curve? curves[idx].eddsa_curve /**/ : curves[idx].name); if (rc) continue; if (!gcry_pk_get_curve (keyparms, 0, NULL)) continue; if (subkey_algo && curves[idx].eddsa_curve) { /* Both Curve 25519 (or 448) keys are to be created. Check that Libgcrypt also supports the real Curve25519 (or 448). */ gcry_sexp_release (keyparms); rc = gcry_sexp_build (&keyparms, NULL, "(public-key(ecc(curve %s)))", curves[idx].name); if (rc) continue; if (!gcry_pk_get_curve (keyparms, 0, NULL)) continue; } curves[idx].available = 1; tty_printf (" (%d) %s\n", idx + 1, curves[idx].pretty_name? curves[idx].pretty_name:curves[idx].name); } gcry_sexp_release (keyparms); for (;;) { answer = cpr_get ("keygen.curve", _("Your selection? ")); cpr_kill_prompt (); idx = *answer? atoi (answer) : 1; if (!*answer && current) { xfree(answer); return NULL; } else if (*answer && !idx) { /* See whether the user entered the name of the curve. */ for (idx=0; idx < DIM(curves); idx++) { if (!opt.expert && curves[idx].expert_only) continue; if (!stricmp (curves[idx].name, answer) || (curves[idx].pretty_name && !stricmp (curves[idx].pretty_name, answer))) break; } if (idx == DIM(curves)) idx = -1; } else idx--; xfree(answer); answer = NULL; if (idx < 0 || idx >= DIM (curves) || !curves[idx].available) tty_printf (_("Invalid selection.\n")); else { /* If the user selected a signing algorithm and Curve25519 we need to set the algo to EdDSA and update the curve name. If switching away from EdDSA, we need to set the algo back to ECDSA. */ if (*algo == PUBKEY_ALGO_ECDSA || *algo == PUBKEY_ALGO_EDDSA) { if (curves[idx].eddsa_curve) { if (subkey_algo && *subkey_algo == PUBKEY_ALGO_ECDSA) *subkey_algo = PUBKEY_ALGO_EDDSA; *algo = PUBKEY_ALGO_EDDSA; result = curves[idx].eddsa_curve; } else { if (subkey_algo && *subkey_algo == PUBKEY_ALGO_EDDSA) *subkey_algo = PUBKEY_ALGO_ECDSA; *algo = PUBKEY_ALGO_ECDSA; result = curves[idx].name; } } else result = curves[idx].name; break; } } if (!result) result = curves[0].name; return result; } /**************** * Parse an expire string and return its value in seconds. * Returns (u32)-1 on error. * This isn't perfect since scan_isodatestr returns unix time, and * OpenPGP actually allows a 32-bit time *plus* a 32-bit offset. * Because of this, we only permit setting expirations up to 2106, but * OpenPGP could theoretically allow up to 2242. I think we'll all * just cope for the next few years until we get a 64-bit time_t or * similar. */ u32 parse_expire_string (const char *string) { int mult; u32 seconds; u32 abs_date = 0; u32 curtime = make_timestamp (); uint64_t tt; uint64_t tmp64; if (!string || !*string || !strcmp (string, "none") || !strcmp (string, "never") || !strcmp (string, "-")) seconds = 0; else if (!strncmp (string, "seconds=", 8)) seconds = scan_secondsstr (string+8); else if ((abs_date = scan_isodatestr(string)) && (abs_date+86400/2) > curtime) seconds = (abs_date+86400/2) - curtime; else if ((tt = isotime2epoch_u64 (string)) != (uint64_t)(-1)) { tmp64 = tt - curtime; if (tmp64 >= (u32)(-1)) seconds = (u32)(-1) - 1; /* cap value. */ else seconds = (u32)tmp64; } else if ((mult = check_valid_days (string))) { tmp64 = scan_secondsstr (string) * 86400L * mult; if (tmp64 >= (u32)(-1)) seconds = (u32)(-1) - 1; /* cap value. */ else seconds = (u32)tmp64; } else seconds = (u32)(-1); return seconds; } /* Parse a Creation-Date string which is either "1986-04-26" or "19860426T042640". Returns 0 on error. */ static u32 parse_creation_string (const char *string) { u32 seconds; if (!*string) seconds = 0; else if ( !strncmp (string, "seconds=", 8) ) seconds = scan_secondsstr (string+8); else if ( !(seconds = scan_isodatestr (string))) { uint64_t tmp = isotime2epoch_u64 (string); if (tmp == (uint64_t)(-1)) seconds = 0; else if (tmp > (u32)(-1)) seconds = 0; else seconds = tmp; } return seconds; } /* object == 0 for a key, and 1 for a sig */ u32 ask_expire_interval(int object,const char *def_expire) { u32 interval; char *answer; switch(object) { case 0: if(def_expire) BUG(); tty_printf(_("Please specify how long the key should be valid.\n" " 0 = key does not expire\n" " = key expires in n days\n" " w = key expires in n weeks\n" " m = key expires in n months\n" " y = key expires in n years\n")); break; case 1: if(!def_expire) BUG(); tty_printf(_("Please specify how long the signature should be valid.\n" " 0 = signature does not expire\n" " = signature expires in n days\n" " w = signature expires in n weeks\n" " m = signature expires in n months\n" " y = signature expires in n years\n")); break; default: BUG(); } /* Note: The elgamal subkey for DSA has no expiration date because * it must be signed with the DSA key and this one has the expiration * date */ answer = NULL; for(;;) { u32 curtime; xfree(answer); if(object==0) answer = cpr_get("keygen.valid",_("Key is valid for? (0) ")); else { char *prompt; prompt = xasprintf (_("Signature is valid for? (%s) "), def_expire); answer = cpr_get("siggen.valid",prompt); xfree(prompt); if(*answer=='\0') answer=xstrdup(def_expire); } cpr_kill_prompt(); trim_spaces(answer); curtime = make_timestamp (); interval = parse_expire_string( answer ); if( interval == (u32)-1 ) { tty_printf(_("invalid value\n")); continue; } if( !interval ) { tty_printf((object==0) ? _("Key does not expire at all\n") : _("Signature does not expire at all\n")); } else { tty_printf(object==0 ? _("Key expires at %s\n") : _("Signature expires at %s\n"), asctimestamp((ulong)(curtime + interval) ) ); #if SIZEOF_TIME_T <= 4 && !defined (HAVE_UNSIGNED_TIME_T) if ( (time_t)((ulong)(curtime+interval)) < 0 ) tty_printf (_("Your system can't display dates beyond 2038.\n" "However, it will be correctly handled up to" " 2106.\n")); else #endif /*SIZEOF_TIME_T*/ if ( (time_t)((unsigned long)(curtime+interval)) < curtime ) { tty_printf (_("invalid value\n")); continue; } } if( cpr_enabled() || cpr_get_answer_is_yes("keygen.valid.okay", _("Is this correct? (y/N) ")) ) break; } xfree(answer); return interval; } u32 ask_expiredate() { u32 x = ask_expire_interval(0,NULL); return x? make_timestamp() + x : 0; } static PKT_user_id * uid_from_string (const char *string) { size_t n; PKT_user_id *uid; n = strlen (string); uid = xmalloc_clear (sizeof *uid + n); uid->len = n; strcpy (uid->name, string); uid->ref = 1; return uid; } /* Return true if the user id UID already exists in the keyblock. */ static int uid_already_in_keyblock (kbnode_t keyblock, const char *uid) { PKT_user_id *uidpkt = uid_from_string (uid); kbnode_t node; int result = 0; for (node=keyblock; node && !result; node=node->next) if (!is_deleted_kbnode (node) && node->pkt->pkttype == PKT_USER_ID && !cmp_user_ids (uidpkt, node->pkt->pkt.user_id)) result = 1; free_user_id (uidpkt); return result; } /* Ask for a user ID. With a MODE of 1 an extra help prompt is printed for use during a new key creation. If KEYBLOCK is not NULL the function prevents the creation of an already existing user ID. IF FULL is not set some prompts are not shown. */ static char * ask_user_id (int mode, int full, KBNODE keyblock) { char *answer; char *aname, *acomment, *amail, *uid; if ( !mode ) { /* TRANSLATORS: This is the new string telling the user what gpg is now going to do (i.e. ask for the parts of the user ID). Note that if you do not translate this string, a different string will be used, which might still have a correct translation. */ const char *s1 = N_("\n" "GnuPG needs to construct a user ID to identify your key.\n" "\n"); const char *s2 = _(s1); if (!strcmp (s1, s2)) { /* There is no translation for the string thus we to use the old info text. gettext has no way to tell whether a translation is actually available, thus we need to to compare again. */ /* TRANSLATORS: This string is in general not anymore used but you should keep your existing translation. In case the new string is not translated this old string will be used. */ const char *s3 = N_("\n" "You need a user ID to identify your key; " "the software constructs the user ID\n" "from the Real Name, Comment and Email Address in this form:\n" " \"Heinrich Heine (Der Dichter) \"\n\n"); const char *s4 = _(s3); if (strcmp (s3, s4)) s2 = s3; /* A translation exists - use it. */ } tty_printf ("%s", s2) ; } uid = aname = acomment = amail = NULL; for(;;) { char *p; int fail=0; if( !aname ) { for(;;) { xfree(aname); aname = cpr_get("keygen.name",_("Real name: ")); trim_spaces(aname); cpr_kill_prompt(); if( opt.allow_freeform_uid ) break; if( strpbrk( aname, "<>" ) ) { tty_printf(_("Invalid character in name\n")); tty_printf(_("The characters '%s' and '%s' may not " "appear in name\n"), "<", ">"); } else if( digitp(aname) ) tty_printf(_("Name may not start with a digit\n")); else if (*aname && strlen (aname) < 5) { tty_printf(_("Name must be at least 5 characters long\n")); /* However, we allow an empty name. */ } else break; } } if( !amail ) { for(;;) { xfree(amail); amail = cpr_get("keygen.email",_("Email address: ")); trim_spaces(amail); cpr_kill_prompt(); if( !*amail || opt.allow_freeform_uid ) break; /* no email address is okay */ else if ( !is_valid_mailbox (amail) ) tty_printf(_("Not a valid email address\n")); else break; } } if (!acomment) { if (full) { for(;;) { xfree(acomment); acomment = cpr_get("keygen.comment",_("Comment: ")); trim_spaces(acomment); cpr_kill_prompt(); if( !*acomment ) break; /* no comment is okay */ else if( strpbrk( acomment, "()" ) ) tty_printf(_("Invalid character in comment\n")); else break; } } else { xfree (acomment); acomment = xstrdup (""); } } xfree(uid); uid = p = xmalloc(strlen(aname)+strlen(amail)+strlen(acomment)+12+10); if (!*aname && *amail && !*acomment && !random_is_faked ()) { /* Empty name and comment but with mail address. Use simplified form with only the non-angle-bracketed mail address. */ p = stpcpy (p, amail); } else { p = stpcpy (p, aname ); if (*acomment) p = stpcpy(stpcpy(stpcpy(p," ("), acomment),")"); if (*amail) p = stpcpy(stpcpy(stpcpy(p," <"), amail),">"); } /* Append a warning if the RNG is switched into fake mode. */ if ( random_is_faked () ) strcpy(p, " (insecure!)" ); /* print a note in case that UTF8 mapping has to be done */ for(p=uid; *p; p++ ) { if( *p & 0x80 ) { tty_printf(_("You are using the '%s' character set.\n"), get_native_charset() ); break; } } tty_printf(_("You selected this USER-ID:\n \"%s\"\n\n"), uid); if( !*amail && !opt.allow_freeform_uid && (strchr( aname, '@' ) || strchr( acomment, '@'))) { fail = 1; tty_printf(_("Please don't put the email address " "into the real name or the comment\n") ); } if (!fail && keyblock) { if (uid_already_in_keyblock (keyblock, uid)) { tty_printf (_("Such a user ID already exists on this key!\n")); fail = 1; } } for(;;) { /* TRANSLATORS: These are the allowed answers in lower and uppercase. Below you will find the matching string which should be translated accordingly and the letter changed to match the one in the answer string. n = Change name c = Change comment e = Change email o = Okay (ready, continue) q = Quit */ const char *ansstr = _("NnCcEeOoQq"); if( strlen(ansstr) != 10 ) BUG(); if( cpr_enabled() ) { answer = xstrdup (ansstr + (fail?8:6)); answer[1] = 0; } else if (full) { answer = cpr_get("keygen.userid.cmd", fail? _("Change (N)ame, (C)omment, (E)mail or (Q)uit? ") : _("Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? ")); cpr_kill_prompt(); } else { answer = cpr_get("keygen.userid.cmd", fail? _("Change (N)ame, (E)mail, or (Q)uit? ") : _("Change (N)ame, (E)mail, or (O)kay/(Q)uit? ")); cpr_kill_prompt(); } if( strlen(answer) > 1 ) ; else if( *answer == ansstr[0] || *answer == ansstr[1] ) { xfree(aname); aname = NULL; break; } else if( *answer == ansstr[2] || *answer == ansstr[3] ) { xfree(acomment); acomment = NULL; break; } else if( *answer == ansstr[4] || *answer == ansstr[5] ) { xfree(amail); amail = NULL; break; } else if( *answer == ansstr[6] || *answer == ansstr[7] ) { if( fail ) { tty_printf(_("Please correct the error first\n")); } else { xfree(aname); aname = NULL; xfree(acomment); acomment = NULL; xfree(amail); amail = NULL; break; } } else if( *answer == ansstr[8] || *answer == ansstr[9] ) { xfree(aname); aname = NULL; xfree(acomment); acomment = NULL; xfree(amail); amail = NULL; xfree(uid); uid = NULL; break; } xfree(answer); } xfree(answer); if (!amail && !acomment) break; xfree(uid); uid = NULL; } if( uid ) { char *p = native_to_utf8( uid ); xfree( uid ); uid = p; } return uid; } /* Basic key generation. Here we divert to the actual generation routines based on the requested algorithm. */ static int do_create (int algo, unsigned int nbits, const char *curve, KBNODE pub_root, u32 timestamp, u32 expiredate, int is_subkey, int keygen_flags, const char *passphrase, char **cache_nonce_addr, char **passwd_nonce_addr) { gpg_error_t err; /* Fixme: The entropy collecting message should be moved to a libgcrypt progress handler. */ if (!opt.batch) tty_printf (_( "We need to generate a lot of random bytes. It is a good idea to perform\n" "some other action (type on the keyboard, move the mouse, utilize the\n" "disks) during the prime generation; this gives the random number\n" "generator a better chance to gain enough entropy.\n") ); if (algo == PUBKEY_ALGO_ELGAMAL_E) err = gen_elg (algo, nbits, pub_root, timestamp, expiredate, is_subkey, keygen_flags, passphrase, cache_nonce_addr, passwd_nonce_addr); else if (algo == PUBKEY_ALGO_DSA) err = gen_dsa (nbits, pub_root, timestamp, expiredate, is_subkey, keygen_flags, passphrase, cache_nonce_addr, passwd_nonce_addr); else if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA || algo == PUBKEY_ALGO_ECDH) err = gen_ecc (algo, curve, pub_root, timestamp, expiredate, is_subkey, keygen_flags, passphrase, cache_nonce_addr, passwd_nonce_addr); else if (algo == PUBKEY_ALGO_RSA) err = gen_rsa (algo, nbits, pub_root, timestamp, expiredate, is_subkey, keygen_flags, passphrase, cache_nonce_addr, passwd_nonce_addr); else BUG(); return err; } /* Generate a new user id packet or return NULL if canceled. If KEYBLOCK is not NULL the function prevents the creation of an already existing user ID. If UIDSTR is not NULL the user is not asked but UIDSTR is used to create the user id packet; if the user id already exists NULL is returned. UIDSTR is expected to be utf-8 encoded and should have already been checked for a valid length etc. */ PKT_user_id * generate_user_id (KBNODE keyblock, const char *uidstr) { PKT_user_id *uid; char *p; if (uidstr) { if (uid_already_in_keyblock (keyblock, uidstr)) return NULL; /* Already exists. */ uid = uid_from_string (uidstr); } else { p = ask_user_id (1, 1, keyblock); if (!p) return NULL; /* Canceled. */ uid = uid_from_string (p); xfree (p); } return uid; } /* Helper for parse_key_parameter_string for one part of the * specification string; i.e. ALGO/FLAGS. If STRING is NULL or empty * success is returned. On error an error code is returned. Note * that STRING may be modified by this function. NULL may be passed * for any parameter. FOR_SUBKEY shall be true if this is used as a * subkey. If CLEAR_CERT is set a default CERT usage will be cleared; * this is useful if for example the default algorithm is used for a * subkey. */ static gpg_error_t parse_key_parameter_part (ctrl_t ctrl, char *string, int for_subkey, int clear_cert, int *r_algo, unsigned int *r_size, unsigned int *r_keyuse, char const **r_curve, char **r_keygrip) { gpg_error_t err; char *flags; int algo; char *endp; const char *curve = NULL; int ecdh_or_ecdsa = 0; unsigned int size; int keyuse; int i; const char *s; int from_card = 0; char *keygrip = NULL; if (!string || !*string) return 0; /* Success. */ flags = strchr (string, '/'); if (flags) *flags++ = 0; algo = 0; if (!ascii_strcasecmp (string, "card")) from_card = 1; else if (strlen (string) >= 3 && (digitp (string+3) || !string[3])) { if (!ascii_memcasecmp (string, "rsa", 3)) algo = PUBKEY_ALGO_RSA; else if (!ascii_memcasecmp (string, "dsa", 3)) algo = PUBKEY_ALGO_DSA; else if (!ascii_memcasecmp (string, "elg", 3)) algo = PUBKEY_ALGO_ELGAMAL_E; } if (from_card) ; /* We need the flags before we can figure out the key to use. */ else if (algo) { if (!string[3]) size = get_keysize_range (algo, NULL, NULL); else { size = strtoul (string+3, &endp, 10); if (size < 512 || size > 16384 || *endp) return gpg_error (GPG_ERR_INV_VALUE); } } else if ((curve = openpgp_is_curve_supported (string, &algo, &size))) { if (!algo) { algo = PUBKEY_ALGO_ECDH; /* Default ECC algorithm. */ ecdh_or_ecdsa = 1; /* We may need to switch the algo. */ } } else return gpg_error (GPG_ERR_UNKNOWN_CURVE); /* Parse the flags. */ keyuse = 0; if (flags) { char **tokens = NULL; tokens = strtokenize (flags, ","); if (!tokens) return gpg_error_from_syserror (); for (i=0; (s = tokens[i]); i++) { if (!*s) ; else if (!ascii_strcasecmp (s, "sign")) keyuse |= PUBKEY_USAGE_SIG; else if (!ascii_strcasecmp (s, "encrypt") || !ascii_strcasecmp (s, "encr")) keyuse |= PUBKEY_USAGE_ENC; else if (!ascii_strcasecmp (s, "auth")) keyuse |= PUBKEY_USAGE_AUTH; else if (!ascii_strcasecmp (s, "cert")) keyuse |= PUBKEY_USAGE_CERT; else if (!ascii_strcasecmp (s, "ecdsa") && !from_card) { if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA) algo = PUBKEY_ALGO_ECDSA; else { xfree (tokens); return gpg_error (GPG_ERR_INV_FLAG); } ecdh_or_ecdsa = 0; } else if (!ascii_strcasecmp (s, "ecdh") && !from_card) { if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA) algo = PUBKEY_ALGO_ECDH; else { xfree (tokens); return gpg_error (GPG_ERR_INV_FLAG); } ecdh_or_ecdsa = 0; } else if (!ascii_strcasecmp (s, "eddsa") && !from_card) { /* Not required but we allow it for consistency. */ if (algo == PUBKEY_ALGO_EDDSA) ; else { xfree (tokens); return gpg_error (GPG_ERR_INV_FLAG); } } else { xfree (tokens); return gpg_error (GPG_ERR_UNKNOWN_FLAG); } } xfree (tokens); } /* If not yet decided switch between ecdh and ecdsa unless we want * to read the algo from the current card. */ if (from_card) { strlist_t keypairlist, sl; char *reqkeyref; if (!keyuse) keyuse = (for_subkey? PUBKEY_USAGE_ENC /* */ : (PUBKEY_USAGE_CERT|PUBKEY_USAGE_SIG)); /* Access the card to make sure we have one and to show the S/N. */ { char *serialno; err = agent_scd_serialno (&serialno, NULL); if (err) { log_error (_("error reading the card: %s\n"), gpg_strerror (err)); return err; } if (!opt.quiet) log_info (_("Serial number of the card: %s\n"), serialno); xfree (serialno); } err = agent_scd_keypairinfo (ctrl, &keypairlist); if (err) { log_error (_("error reading the card: %s\n"), gpg_strerror (err)); return err; } agent_scd_getattr_one ((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_CERT)) ? "$SIGNKEYID":"$ENCRKEYID", &reqkeyref); algo = 0; /* Should already be the case. */ for (sl=keypairlist; sl && !algo; sl = sl->next) { gcry_sexp_t s_pkey; char *algostr = NULL; enum gcry_pk_algos algoid = 0; const char *keyref; if (!reqkeyref) continue; /* Card does not provide the info (skip all). */ keyref = strchr (sl->d, ' '); if (!keyref) continue; /* Ooops. */ keyref++; if (strcmp (reqkeyref, keyref)) continue; /* This is not the requested keyref. */ if ((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_CERT)) && (sl->flags & (GCRY_PK_USAGE_SIGN|GCRY_PK_USAGE_CERT))) ; /* Okay */ else if ((keyuse & PUBKEY_USAGE_ENC) && (sl->flags & GCRY_PK_USAGE_ENCR)) ; /* Okay */ else continue; /* Not usable for us. */ if (agent_scd_readkey (keyref, &s_pkey)) continue; /* Could not read the key. */ algostr = pubkey_algo_string (s_pkey, &algoid); gcry_sexp_release (s_pkey); /* Map to OpenPGP algo number. * We need to tweak the algo in case GCRY_PK_ECC is returned * because pubkey_algo_string is not aware of the OpenPGP * algo mapping. FIXME: This is an ugly hack. */ if (algoid == GCRY_PK_ECC && algostr && !strncmp (algostr, "nistp", 5) && !(sl->flags & GCRY_PK_USAGE_ENCR)) algo = PUBKEY_ALGO_ECDSA; else if (algoid == GCRY_PK_ECC && algostr && !strcmp (algostr, "ed25519") && !(sl->flags & GCRY_PK_USAGE_ENCR)) algo = PUBKEY_ALGO_EDDSA; else algo = map_pk_gcry_to_openpgp (algoid); xfree (algostr); xfree (keygrip); keygrip = xtrystrdup (sl->d); if (!keygrip) { err = gpg_error_from_syserror (); xfree (reqkeyref); free_strlist (keypairlist); return err; } if ((endp = strchr (keygrip, ' '))) *endp = 0; } xfree (reqkeyref); free_strlist (keypairlist); if (!algo || !keygrip) { err = gpg_error (GPG_ERR_PUBKEY_ALGO); log_error ("no usable key on the card: %s\n", gpg_strerror (err)); xfree (keygrip); return err; } } else if (ecdh_or_ecdsa && keyuse) algo = (keyuse & PUBKEY_USAGE_ENC)? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA; else if (ecdh_or_ecdsa) algo = for_subkey? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA; /* Set or fix key usage. */ if (!keyuse) { if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA || algo == PUBKEY_ALGO_DSA) keyuse = PUBKEY_USAGE_SIG; else if (algo == PUBKEY_ALGO_RSA) keyuse = for_subkey? PUBKEY_USAGE_ENC : PUBKEY_USAGE_SIG; else keyuse = PUBKEY_USAGE_ENC; } else if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA || algo == PUBKEY_ALGO_DSA) { keyuse &= ~PUBKEY_USAGE_ENC; /* Forbid encryption. */ } else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ELGAMAL_E) { keyuse = PUBKEY_USAGE_ENC; /* Allow only encryption. */ } /* Make sure a primary key can certify. */ if (!for_subkey) keyuse |= PUBKEY_USAGE_CERT; /* But if requested remove th cert usage. */ if (clear_cert) keyuse &= ~PUBKEY_USAGE_CERT; /* Check that usage is actually possible. */ if (/**/((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT)) && !pubkey_get_nsig (algo)) || ((keyuse & PUBKEY_USAGE_ENC) && !pubkey_get_nenc (algo)) || (for_subkey && (keyuse & PUBKEY_USAGE_CERT))) { xfree (keygrip); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } /* Return values. */ if (r_algo) *r_algo = algo; if (r_size) { unsigned int min, def, max; /* Make sure the keysize is in the allowed range. */ def = get_keysize_range (algo, &min, &max); if (!size) size = def; else if (size < min) size = min; else if (size > max) size = max; *r_size = fixup_keysize (size, algo, 1); } if (r_keyuse) *r_keyuse = keyuse; if (r_curve) *r_curve = curve; if (r_keygrip) *r_keygrip = keygrip; else xfree (keygrip); return 0; } /* Parse and return the standard key generation parameter. * The string is expected to be in this format: * * ALGO[/FLAGS][+SUBALGO[/FLAGS]] * * Here ALGO is a string in the same format as printed by the * keylisting. For example: * * rsa3072 := RSA with 3072 bit. * dsa2048 := DSA with 2048 bit. * elg2048 := Elgamal with 2048 bit. * ed25519 := EDDSA using curve Ed25519. * cv25519 := ECDH using curve Curve25519. * nistp256:= ECDSA or ECDH using curve NIST P-256 * * All strings with an unknown prefix are considered an elliptic * curve. Curves which have no implicit algorithm require that FLAGS * is given to select whether ECDSA or ECDH is used; this can either * be done using an algorithm keyword or usage keywords. * * FLAGS is a comma delimited string of keywords: * * cert := Allow usage Certify * sign := Allow usage Sign * encr := Allow usage Encrypt * auth := Allow usage Authentication * encrypt := Alias for "encr" * ecdsa := Use algorithm ECDSA. * eddsa := Use algorithm EdDSA. * ecdh := Use algorithm ECDH. * * There are several defaults and fallbacks depending on the * algorithm. PART can be used to select which part of STRING is * used: * -1 := Both parts * 0 := Only the part of the primary key * 1 := If there is one part parse that one, if there are * two parts parse the part which best matches the * SUGGESTED_USE or in case that can't be evaluated the second part. * Always return using the args for the primary key (R_ALGO,....). * */ gpg_error_t parse_key_parameter_string (ctrl_t ctrl, const char *string, int part, unsigned int suggested_use, int *r_algo, unsigned int *r_size, unsigned int *r_keyuse, char const **r_curve, char **r_keygrip, int *r_subalgo, unsigned int *r_subsize, unsigned int *r_subkeyuse, char const **r_subcurve, char **r_subkeygrip) { gpg_error_t err = 0; char *primary, *secondary; if (r_algo) *r_algo = 0; if (r_size) *r_size = 0; if (r_keyuse) *r_keyuse = 0; if (r_curve) *r_curve = NULL; if (r_keygrip) *r_keygrip = NULL; if (r_subalgo) *r_subalgo = 0; if (r_subsize) *r_subsize = 0; if (r_subkeyuse) *r_subkeyuse = 0; if (r_subcurve) *r_subcurve = NULL; if (r_subkeygrip) *r_subkeygrip = NULL; if (!string || !*string || !ascii_strcasecmp (string, "default") || !strcmp (string, "-")) string = get_default_pubkey_algo (); else if (!ascii_strcasecmp (string, "future-default") || !ascii_strcasecmp (string, "futuredefault")) string = FUTURE_STD_KEY_PARAM; else if (!ascii_strcasecmp (string, "card")) string = "card/cert,sign+card/encr"; primary = xstrdup (string); secondary = strchr (primary, '+'); if (secondary) *secondary++ = 0; if (part == -1 || part == 0) { err = parse_key_parameter_part (ctrl, primary, 0, 0, r_algo, r_size, r_keyuse, r_curve, r_keygrip); if (!err && part == -1) err = parse_key_parameter_part (ctrl, secondary, 1, 0, r_subalgo, r_subsize, r_subkeyuse, r_subcurve, r_subkeygrip); } else if (part == 1) { /* If we have SECONDARY, use that part. If there is only one * part consider this to be the subkey algo. In case a * SUGGESTED_USE has been given and the usage of the secondary * part does not match SUGGESTED_USE try again using the primary * part. Noet thar when falling back to the primary key we need * to force clearing the cert usage. */ if (secondary) { err = parse_key_parameter_part (ctrl, secondary, 1, 0, r_algo, r_size, r_keyuse, r_curve, r_keygrip); if (!err && suggested_use && r_keyuse && !(suggested_use & *r_keyuse)) err = parse_key_parameter_part (ctrl, primary, 1, 1 /*(clear cert)*/, r_algo, r_size, r_keyuse, r_curve, r_keygrip); } else err = parse_key_parameter_part (ctrl, primary, 1, 0, r_algo, r_size, r_keyuse, r_curve, r_keygrip); } xfree (primary); return err; } /* Append R to the linked list PARA. */ static void append_to_parameter (struct para_data_s *para, struct para_data_s *r) { log_assert (para); while (para->next) para = para->next; para->next = r; } /* Release the parameter list R. */ static void release_parameter_list (struct para_data_s *r) { struct para_data_s *r2; for (; r ; r = r2) { r2 = r->next; if (r->key == pPASSPHRASE && *r->u.value) wipememory (r->u.value, strlen (r->u.value)); xfree (r); } } /* Return the N-th parameter of name KEY from PARA. An IDX of 0 * returns the first and so on. */ static struct para_data_s * get_parameter_idx (struct para_data_s *para, enum para_name key, unsigned int idx) { struct para_data_s *r; for(r = para; r; r = r->next) if (r->key == key) { if (!idx) return r; idx--; } return NULL; } /* Return the first parameter of name KEY from PARA. */ static struct para_data_s * get_parameter (struct para_data_s *para, enum para_name key) { return get_parameter_idx (para, key, 0); } static const char * get_parameter_value( struct para_data_s *para, enum para_name key ) { struct para_data_s *r = get_parameter( para, key ); return (r && *r->u.value)? r->u.value : NULL; } /* This is similar to get_parameter_value but also returns the empty string. This is required so that quick_generate_keypair can use an empty Passphrase to specify no-protection. */ static const char * get_parameter_passphrase (struct para_data_s *para) { struct para_data_s *r = get_parameter (para, pPASSPHRASE); return r ? r->u.value : NULL; } static int get_parameter_algo (ctrl_t ctrl, struct para_data_s *para, enum para_name key, int *r_default) { int i; struct para_data_s *r = get_parameter( para, key ); if (r_default) *r_default = 0; if (!r) return -1; /* Note that we need to handle the ECC algorithms specified as strings directly because Libgcrypt folds them all to ECC. */ if (!ascii_strcasecmp (r->u.value, "default")) { /* Note: If you change this default algo, remember to change it * also in gpg.c:gpgconf_list. */ /* FIXME: We only allow the algo here and have a separate thing * for the curve etc. That is a ugly but demanded for backward * compatibility with the batch key generation. It would be * better to make full use of parse_key_parameter_string. */ parse_key_parameter_string (ctrl, NULL, 0, 0, &i, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (r_default) *r_default = 1; } else if (digitp (r->u.value)) i = atoi( r->u.value ); else if (!strcmp (r->u.value, "ELG-E") || !strcmp (r->u.value, "ELG")) i = PUBKEY_ALGO_ELGAMAL_E; else if (!ascii_strcasecmp (r->u.value, "EdDSA")) i = PUBKEY_ALGO_EDDSA; else if (!ascii_strcasecmp (r->u.value, "ECDSA")) i = PUBKEY_ALGO_ECDSA; else if (!ascii_strcasecmp (r->u.value, "ECDH")) i = PUBKEY_ALGO_ECDH; else i = map_pk_gcry_to_openpgp (gcry_pk_map_name (r->u.value)); if (i == PUBKEY_ALGO_RSA_E || i == PUBKEY_ALGO_RSA_S) i = 0; /* we don't want to allow generation of these algorithms */ return i; } /* Parse a usage string. The usage keywords "auth", "sign", "encr" * may be delimited by space, tab, or comma. On error -1 is returned * instead of the usage flags. */ static int parse_usagestr (const char *usagestr) { gpg_error_t err; char **tokens = NULL; const char *s; int i; unsigned int use = 0; tokens = strtokenize (usagestr, " \t,"); if (!tokens) { err = gpg_error_from_syserror (); log_error ("strtokenize failed: %s\n", gpg_strerror (err)); return -1; } for (i=0; (s = tokens[i]); i++) { if (!*s) ; else if (!ascii_strcasecmp (s, "sign")) use |= PUBKEY_USAGE_SIG; else if (!ascii_strcasecmp (s, "encrypt") || !ascii_strcasecmp (s, "encr")) use |= PUBKEY_USAGE_ENC; else if (!ascii_strcasecmp (s, "auth")) use |= PUBKEY_USAGE_AUTH; else if (!ascii_strcasecmp (s, "cert")) use |= PUBKEY_USAGE_CERT; else if (!ascii_strcasecmp (s, "renc")) use |= PUBKEY_USAGE_RENC; else if (!ascii_strcasecmp (s, "time")) use |= PUBKEY_USAGE_TIME; else if (!ascii_strcasecmp (s, "group")) use |= PUBKEY_USAGE_GROUP; else { xfree (tokens); return -1; /* error */ } } xfree (tokens); return use; } /* * Parse the usage parameter and set the keyflags. Returns -1 on * error, 0 for no usage given or 1 for usage available. */ static int parse_parameter_usage (const char *fname, struct para_data_s *para, enum para_name key) { struct para_data_s *r = get_parameter( para, key ); int i; if (!r) return 0; /* none (this is an optional parameter)*/ i = parse_usagestr (r->u.value); if (i == -1) { log_error ("%s:%d: invalid usage list\n", fname, r->lnr ); return -1; /* error */ } r->u.usage = i; return 1; } /* Parse the revocation key specified by NAME, check that the public * key exists (so that we can get the required public key algorithm), * and return a parameter wit the revocation key information. On * error print a diagnostic and return NULL. */ static struct para_data_s * prepare_desig_revoker (ctrl_t ctrl, const char *name) { gpg_error_t err; struct para_data_s *para = NULL; KEYDB_SEARCH_DESC desc; int sensitive = 0; struct revocation_key revkey; PKT_public_key *revoker_pk = NULL; size_t fprlen; if (!ascii_strncasecmp (name, "sensitive:", 10) && !spacep (name+10)) { name += 10; sensitive = 1; } if (classify_user_id (name, &desc, 1) || desc.mode != KEYDB_SEARCH_MODE_FPR) { log_info (_("\"%s\" is not a fingerprint\n"), name); err = gpg_error (GPG_ERR_INV_NAME); goto leave; } revoker_pk = xcalloc (1, sizeof *revoker_pk); revoker_pk->req_usage = PUBKEY_USAGE_CERT; err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, NULL, revoker_pk, name, NULL, NULL, 1); if (err) goto leave; fingerprint_from_pk (revoker_pk, revkey.fpr, &fprlen); if (fprlen != 20) { log_info (_("cannot appoint a PGP 2.x style key as a " "designated revoker\n")); err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY); goto leave; } revkey.class = 0x80; if (sensitive) revkey.class |= 0x40; revkey.algid = revoker_pk->pubkey_algo; para = xcalloc (1, sizeof *para); para->key = pREVOKER; memcpy (¶->u.revkey, &revkey, sizeof revkey); leave: if (err) log_error ("invalid revocation key '%s': %s\n", name, gpg_strerror (err)); free_public_key (revoker_pk); return para; } /* Parse a pREVOKER parameter into its dedicated parts. */ static int parse_revocation_key (const char *fname, struct para_data_s *para, enum para_name key) { struct para_data_s *r = get_parameter( para, key ); struct revocation_key revkey; char *pn; int i; if( !r ) return 0; /* none (this is an optional parameter) */ pn = r->u.value; revkey.class=0x80; revkey.algid=atoi(pn); if(!revkey.algid) goto fail; /* Skip to the fpr */ while(*pn && *pn!=':') pn++; if(*pn!=':') goto fail; pn++; for(i=0;iu.revkey,&revkey,sizeof(struct revocation_key)); return 0; fail: log_error("%s:%d: invalid revocation key\n", fname, r->lnr ); return -1; /* error */ } static u32 get_parameter_u32( struct para_data_s *para, enum para_name key ) { struct para_data_s *r = get_parameter( para, key ); if( !r ) return 0; if( r->key == pKEYCREATIONDATE ) return r->u.creation; if( r->key == pKEYEXPIRE || r->key == pSUBKEYEXPIRE ) return r->u.expire; if( r->key == pKEYUSAGE || r->key == pSUBKEYUSAGE ) return r->u.usage; return (unsigned int)strtoul( r->u.value, NULL, 10 ); } static unsigned int get_parameter_uint( struct para_data_s *para, enum para_name key ) { return get_parameter_u32( para, key ); } static struct revocation_key * get_parameter_revkey (struct para_data_s *para, enum para_name key, unsigned int idx) { struct para_data_s *r = get_parameter_idx (para, key, idx); return r? &r->u.revkey : NULL; } static int proc_parameter_file (ctrl_t ctrl, struct para_data_s *para, const char *fname, struct output_control_s *outctrl, int card ) { struct para_data_s *r; const char *s1, *s2, *s3; size_t n; char *p; strlist_t sl; int is_default = 0; int have_user_id = 0; int err, algo; /* Check that we have all required parameters. */ r = get_parameter( para, pKEYTYPE ); if(r) { algo = get_parameter_algo (ctrl, para, pKEYTYPE, &is_default); if (openpgp_pk_test_algo2 (algo, PUBKEY_USAGE_SIG)) { log_error ("%s:%d: invalid algorithm\n", fname, r->lnr ); return -1; } } else { log_error ("%s: no Key-Type specified\n",fname); return -1; } err = parse_parameter_usage (fname, para, pKEYUSAGE); if (!err) { /* Default to algo capabilities if key-usage is not provided and no default algorithm has been requested. */ r = xmalloc_clear(sizeof(*r)); r->key = pKEYUSAGE; r->u.usage = (is_default ? (PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG) : openpgp_pk_algo_usage(algo)); append_to_parameter (para, r); } else if (err == -1) return -1; else { r = get_parameter (para, pKEYUSAGE); if (r && (r->u.usage & ~openpgp_pk_algo_usage (algo))) { log_error ("%s:%d: specified Key-Usage not allowed for algo %d\n", fname, r->lnr, algo); return -1; } } is_default = 0; r = get_parameter( para, pSUBKEYTYPE ); if(r) { algo = get_parameter_algo (ctrl, para, pSUBKEYTYPE, &is_default); if (openpgp_pk_test_algo (algo)) { log_error ("%s:%d: invalid algorithm\n", fname, r->lnr ); return -1; } err = parse_parameter_usage (fname, para, pSUBKEYUSAGE); if (!err) { /* Default to algo capabilities if subkey-usage is not provided */ r = xmalloc_clear (sizeof(*r)); r->key = pSUBKEYUSAGE; r->u.usage = (is_default ? PUBKEY_USAGE_ENC : openpgp_pk_algo_usage (algo)); append_to_parameter (para, r); } else if (err == -1) return -1; else { r = get_parameter (para, pSUBKEYUSAGE); if (r && (r->u.usage & ~openpgp_pk_algo_usage (algo))) { log_error ("%s:%d: specified Subkey-Usage not allowed" " for algo %d\n", fname, r->lnr, algo); return -1; } } } if( get_parameter_value( para, pUSERID ) ) have_user_id=1; else { /* create the formatted user ID */ s1 = get_parameter_value( para, pNAMEREAL ); s2 = get_parameter_value( para, pNAMECOMMENT ); s3 = get_parameter_value( para, pNAMEEMAIL ); if( s1 || s2 || s3 ) { n = (s1?strlen(s1):0) + (s2?strlen(s2):0) + (s3?strlen(s3):0); r = xmalloc_clear( sizeof *r + n + 20 ); r->key = pUSERID; p = r->u.value; if( s1 ) p = stpcpy(p, s1 ); if( s2 ) p = stpcpy(stpcpy(stpcpy(p," ("), s2 ),")"); if( s3 ) { /* If we have only the email part, do not add the space * and the angle brackets. */ if (*r->u.value) p = stpcpy(stpcpy(stpcpy(p," <"), s3 ),">"); else p = stpcpy (p, s3); } append_to_parameter (para, r); have_user_id=1; } } if(!have_user_id) { log_error("%s: no User-ID specified\n",fname); return -1; } /* Set preferences, if any. */ keygen_set_std_prefs(get_parameter_value( para, pPREFERENCES ), 0); /* Set keyserver, if any. */ s1=get_parameter_value( para, pKEYSERVER ); if(s1) { struct keyserver_spec *spec; spec = parse_keyserver_uri (s1, 1); if(spec) { free_keyserver_spec(spec); opt.def_keyserver_url=s1; } else { r = get_parameter (para, pKEYSERVER); log_error("%s:%d: invalid keyserver url\n", fname, r->lnr ); return -1; } } /* Set revoker from parameter file, if any. Must be done first so * that we don't find a parameter set via prepare_desig_revoker. */ if (parse_revocation_key (fname, para, pREVOKER)) return -1; /* Check and append revokers from the config file. */ for (sl = opt.desig_revokers; sl; sl = sl->next) { r = prepare_desig_revoker (ctrl, sl->d); if (!r) return -1; append_to_parameter (para, r); } /* Make KEYCREATIONDATE from Creation-Date. */ r = get_parameter (para, pCREATIONDATE); if (r && *r->u.value) { u32 seconds; seconds = parse_creation_string (r->u.value); if (!seconds) { log_error ("%s:%d: invalid creation date\n", fname, r->lnr ); return -1; } r->u.creation = seconds; r->key = pKEYCREATIONDATE; /* Change that entry. */ } /* Make KEYEXPIRE from Expire-Date. */ r = get_parameter( para, pEXPIREDATE ); if( r && *r->u.value ) { u32 seconds; seconds = parse_expire_string( r->u.value ); if( seconds == (u32)-1 ) { log_error("%s:%d: invalid expire date\n", fname, r->lnr ); return -1; } r->u.expire = seconds; r->key = pKEYEXPIRE; /* change hat entry */ /* also set it for the subkey */ r = xmalloc_clear( sizeof *r + 20 ); r->key = pSUBKEYEXPIRE; r->u.expire = seconds; append_to_parameter (para, r); } do_generate_keypair (ctrl, para, outctrl, card ); return 0; } /**************** * Kludge to allow non interactive key generation controlled * by a parameter file. * Note, that string parameters are expected to be in UTF-8 */ static void read_parameter_file (ctrl_t ctrl, const char *fname ) { static struct { const char *name; enum para_name key; } keywords[] = { { "Key-Type", pKEYTYPE}, { "Key-Length", pKEYLENGTH }, { "Key-Curve", pKEYCURVE }, { "Key-Usage", pKEYUSAGE }, { "Subkey-Type", pSUBKEYTYPE }, { "Subkey-Length", pSUBKEYLENGTH }, { "Subkey-Curve", pSUBKEYCURVE }, { "Subkey-Usage", pSUBKEYUSAGE }, { "Name-Real", pNAMEREAL }, { "Name-Email", pNAMEEMAIL }, { "Name-Comment", pNAMECOMMENT }, { "Expire-Date", pEXPIREDATE }, { "Creation-Date", pCREATIONDATE }, { "Passphrase", pPASSPHRASE }, { "Preferences", pPREFERENCES }, { "Revoker", pREVOKER }, { "Handle", pHANDLE }, { "Keyserver", pKEYSERVER }, { "Keygrip", pKEYGRIP }, { "Key-Grip", pKEYGRIP }, { "Subkey-grip", pSUBKEYGRIP }, { NULL, 0 } }; IOBUF fp; byte *line; unsigned int maxlen, nline; char *p; int lnr; const char *err = NULL; struct para_data_s *para, *r; int i; struct output_control_s outctrl; memset( &outctrl, 0, sizeof( outctrl ) ); outctrl.pub.afx = new_armor_context (); if( !fname || !*fname) fname = "-"; fp = iobuf_open (fname); if (fp && is_secured_file (iobuf_get_fd (fp))) { iobuf_close (fp); fp = NULL; gpg_err_set_errno (EPERM); } if (!fp) { log_error (_("can't open '%s': %s\n"), fname, strerror(errno) ); return; } iobuf_ioctl (fp, IOBUF_IOCTL_NO_CACHE, 1, NULL); lnr = 0; err = NULL; para = NULL; maxlen = 1024; line = NULL; nline = 0; while ( iobuf_read_line (fp, &line, &nline, &maxlen) ) { char *keyword, *value; lnr++; if( !maxlen ) { err = "line too long"; break; } for( p = line; isspace(*(byte*)p); p++ ) ; if( !*p || *p == '#' ) continue; keyword = p; if( *keyword == '%' ) { for( ; !isspace(*(byte*)p); p++ ) ; if( *p ) *p++ = 0; for( ; isspace(*(byte*)p); p++ ) ; value = p; trim_trailing_ws( value, strlen(value) ); if( !ascii_strcasecmp( keyword, "%echo" ) ) log_info("%s\n", value ); else if( !ascii_strcasecmp( keyword, "%dry-run" ) ) outctrl.dryrun = 1; else if( !ascii_strcasecmp( keyword, "%ask-passphrase" ) ) ; /* Dummy for backward compatibility. */ else if( !ascii_strcasecmp( keyword, "%no-ask-passphrase" ) ) ; /* Dummy for backward compatibility. */ else if( !ascii_strcasecmp( keyword, "%no-protection" ) ) outctrl.keygen_flags |= KEYGEN_FLAG_NO_PROTECTION; else if( !ascii_strcasecmp( keyword, "%transient-key" ) ) outctrl.keygen_flags |= KEYGEN_FLAG_TRANSIENT_KEY; else if( !ascii_strcasecmp( keyword, "%commit" ) ) { outctrl.lnr = lnr; if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 )) print_status_key_not_created (get_parameter_value (para, pHANDLE)); release_parameter_list( para ); para = NULL; } else if( !ascii_strcasecmp( keyword, "%pubring" ) ) { if( outctrl.pub.fname && !strcmp( outctrl.pub.fname, value ) ) ; /* still the same file - ignore it */ else { xfree( outctrl.pub.newfname ); outctrl.pub.newfname = xstrdup( value ); outctrl.use_files = 1; } } else if( !ascii_strcasecmp( keyword, "%secring" ) ) { /* Ignore this command. */ } else log_info("skipping control '%s' (%s)\n", keyword, value ); continue; } if( !(p = strchr( p, ':' )) || p == keyword ) { err = "missing colon"; break; } if( *p ) *p++ = 0; for( ; isspace(*(byte*)p); p++ ) ; if( !*p ) { err = "missing argument"; break; } value = p; trim_trailing_ws( value, strlen(value) ); for(i=0; keywords[i].name; i++ ) { if( !ascii_strcasecmp( keywords[i].name, keyword ) ) break; } if( !keywords[i].name ) { err = "unknown keyword"; break; } if( keywords[i].key != pKEYTYPE && !para ) { err = "parameter block does not start with \"Key-Type\""; break; } if( keywords[i].key == pKEYTYPE && para ) { outctrl.lnr = lnr; if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 )) print_status_key_not_created (get_parameter_value (para, pHANDLE)); release_parameter_list( para ); para = NULL; } else { for( r = para; r; r = r->next ) { if( r->key == keywords[i].key ) break; } if( r ) { err = "duplicate keyword"; break; } } r = xmalloc_clear( sizeof *r + strlen( value ) ); r->lnr = lnr; r->key = keywords[i].key; strcpy( r->u.value, value ); r->next = para; para = r; } if( err ) log_error("%s:%d: %s\n", fname, lnr, err ); else if( iobuf_error (fp) ) { log_error("%s:%d: read error\n", fname, lnr); } else if( para ) { outctrl.lnr = lnr; if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 )) print_status_key_not_created (get_parameter_value (para, pHANDLE)); } if( outctrl.use_files ) { /* close open streams */ iobuf_close( outctrl.pub.stream ); /* Must invalidate that ugly cache to actually close it. */ if (outctrl.pub.fname) iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)outctrl.pub.fname); xfree( outctrl.pub.fname ); xfree( outctrl.pub.newfname ); } xfree (line); release_parameter_list( para ); iobuf_close (fp); release_armor_context (outctrl.pub.afx); } /* Helper for quick_generate_keypair. */ static struct para_data_s * quickgen_set_para (struct para_data_s *para, int for_subkey, int algo, int nbits, const char *curve, unsigned int use, const char *keygrip) { struct para_data_s *r; r = xmalloc_clear (sizeof *r + 50); r->key = for_subkey? pSUBKEYUSAGE : pKEYUSAGE; if (use) snprintf (r->u.value, 30, "%s%s%s%s%s%s%s", (use & PUBKEY_USAGE_ENC)? "encr " : "", (use & PUBKEY_USAGE_SIG)? "sign " : "", (use & PUBKEY_USAGE_AUTH)? "auth " : "", (use & PUBKEY_USAGE_CERT)? "cert " : "", (use & PUBKEY_USAGE_RENC)? "renc " : "", (use & PUBKEY_USAGE_TIME)? "time " : "", (use & PUBKEY_USAGE_GROUP)?"group ": ""); else strcpy (r->u.value, for_subkey ? "encr" : "sign"); r->next = para; para = r; r = xmalloc_clear (sizeof *r + 20); r->key = for_subkey? pSUBKEYTYPE : pKEYTYPE; snprintf (r->u.value, 20, "%d", algo); r->next = para; para = r; if (keygrip) { r = xmalloc_clear (sizeof *r + strlen (keygrip)); r->key = for_subkey? pSUBKEYGRIP : pKEYGRIP; strcpy (r->u.value, keygrip); r->next = para; para = r; } else if (curve) { r = xmalloc_clear (sizeof *r + strlen (curve)); r->key = for_subkey? pSUBKEYCURVE : pKEYCURVE; strcpy (r->u.value, curve); r->next = para; para = r; } else { r = xmalloc_clear (sizeof *r + 20); r->key = for_subkey? pSUBKEYLENGTH : pKEYLENGTH; sprintf (r->u.value, "%u", nbits); r->next = para; para = r; } return para; } /* * Unattended generation of a standard key. */ void quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr, const char *usagestr, const char *expirestr) { gpg_error_t err; struct para_data_s *para = NULL; struct para_data_s *r; struct output_control_s outctrl; int use_tty; memset (&outctrl, 0, sizeof outctrl); use_tty = (!opt.batch && !opt.answer_yes && !*algostr && !*usagestr && !*expirestr && !cpr_enabled () && gnupg_isatty (fileno (stdin)) && gnupg_isatty (fileno (stdout)) && gnupg_isatty (fileno (stderr))); r = xmalloc_clear (sizeof *r + strlen (uid)); r->key = pUSERID; strcpy (r->u.value, uid); r->next = para; para = r; uid = trim_spaces (r->u.value); if (!*uid || (!opt.allow_freeform_uid && !is_valid_user_id (uid))) { log_error (_("Key generation failed: %s\n"), gpg_strerror (GPG_ERR_INV_USER_ID)); goto leave; } /* If gpg is directly used on the console ask whether a key with the given user id shall really be created. */ if (use_tty) { tty_printf (_("About to create a key for:\n \"%s\"\n\n"), uid); if (!cpr_get_answer_is_yes_def ("quick_keygen.okay", _("Continue? (Y/n) "), 1)) goto leave; } /* Check whether such a user ID already exists. */ { KEYDB_HANDLE kdbhd; KEYDB_SEARCH_DESC desc; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_EXACT; desc.u.name = uid; kdbhd = keydb_new (); if (!kdbhd) goto leave; err = keydb_search (kdbhd, &desc, 1, NULL); keydb_release (kdbhd); if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) { log_info (_("A key for \"%s\" already exists\n"), uid); if (opt.answer_yes) ; else if (!use_tty || !cpr_get_answer_is_yes_def ("quick_keygen.force", _("Create anyway? (y/N) "), 0)) { write_status_error ("genkey", gpg_error (304)); log_inc_errorcount (); /* we used log_info */ goto leave; } log_info (_("creating anyway\n")); } } if (!*expirestr || strcmp (expirestr, "-") == 0) expirestr = default_expiration_interval; if ((!*algostr || !ascii_strcasecmp (algostr, "default") || !ascii_strcasecmp (algostr, "future-default") || !ascii_strcasecmp (algostr, "futuredefault") || !ascii_strcasecmp (algostr, "card")) && (!*usagestr || !ascii_strcasecmp (usagestr, "default") || !strcmp (usagestr, "-"))) { /* Use default key parameters. */ int algo, subalgo; unsigned int size, subsize; unsigned int keyuse, subkeyuse; const char *curve, *subcurve; char *keygrip, *subkeygrip; err = parse_key_parameter_string (ctrl, algostr, -1, 0, &algo, &size, &keyuse, &curve, &keygrip, &subalgo, &subsize, &subkeyuse, &subcurve, &subkeygrip); if (err) { log_error (_("Key generation failed: %s\n"), gpg_strerror (err)); goto leave; } para = quickgen_set_para (para, 0, algo, size, curve, keyuse, keygrip); if (subalgo) para = quickgen_set_para (para, 1, subalgo, subsize, subcurve, subkeyuse, subkeygrip); if (*expirestr) { u32 expire; expire = parse_expire_string (expirestr); if (expire == (u32)-1 ) { err = gpg_error (GPG_ERR_INV_VALUE); log_error (_("Key generation failed: %s\n"), gpg_strerror (err)); goto leave; } r = xmalloc_clear (sizeof *r + 20); r->key = pKEYEXPIRE; r->u.expire = expire; r->next = para; para = r; } xfree (keygrip); xfree (subkeygrip); } else { /* Extended unattended mode. Creates only the primary key. */ int algo; unsigned int use; u32 expire; unsigned int nbits; const char *curve; char *keygrip; err = parse_algo_usage_expire (ctrl, 0, algostr, usagestr, expirestr, &algo, &use, &expire, &nbits, &curve, &keygrip); if (err) { log_error (_("Key generation failed: %s\n"), gpg_strerror (err) ); goto leave; } para = quickgen_set_para (para, 0, algo, nbits, curve, use, keygrip); r = xmalloc_clear (sizeof *r + 20); r->key = pKEYEXPIRE; r->u.expire = expire; r->next = para; para = r; xfree (keygrip); } /* If the pinentry loopback mode is not and we have a static passphrase (i.e. set with --passphrase{,-fd,-file} while in batch mode), we use that passphrase for the new key. */ if (opt.pinentry_mode != PINENTRY_MODE_LOOPBACK && have_static_passphrase ()) { const char *s = get_static_passphrase (); r = xmalloc_clear (sizeof *r + strlen (s)); r->key = pPASSPHRASE; strcpy (r->u.value, s); r->next = para; para = r; } proc_parameter_file (ctrl, para, "[internal]", &outctrl, 0); leave: release_parameter_list (para); } /* * Generate a keypair (fname is only used in batch mode) If * CARD_SERIALNO is not NULL the function will create the keys on an * OpenPGP Card. If CARD_BACKUP_KEY has been set and CARD_SERIALNO is * NOT NULL, the encryption key for the card is generated on the host, * imported to the card and a backup file created by gpg-agent. If * FULL is not set only the basic prompts are used (except for batch * mode). */ void generate_keypair (ctrl_t ctrl, int full, const char *fname, const char *card_serialno, int card_backup_key) { gpg_error_t err; unsigned int nbits; char *uid = NULL; int algo; unsigned int use; int both = 0; u32 expire; struct para_data_s *para = NULL; struct para_data_s *r; struct output_control_s outctrl; #ifndef ENABLE_CARD_SUPPORT (void)card_backup_key; #endif memset( &outctrl, 0, sizeof( outctrl ) ); if (opt.batch && card_serialno) { /* We don't yet support unattended key generation with a card * serial number. */ log_error (_("can't do this in batch mode\n")); print_further_info ("key generation with card serial number"); return; } if (opt.batch) { read_parameter_file (ctrl, fname); return; } if (card_serialno) { #ifdef ENABLE_CARD_SUPPORT struct agent_card_info_s info; memset (&info, 0, sizeof (info)); err = agent_scd_getattr ("KEY-ATTR", &info); if (err) { log_error (_("error getting current key info: %s\n"), gpg_strerror (err)); return; } r = xcalloc (1, sizeof *r + strlen (card_serialno) ); r->key = pSERIALNO; strcpy( r->u.value, card_serialno); r->next = para; para = r; r = xcalloc (1, sizeof *r + 20 ); r->key = pKEYTYPE; sprintf( r->u.value, "%d", info.key_attr[0].algo ); r->next = para; para = r; r = xcalloc (1, sizeof *r + 20 ); r->key = pKEYUSAGE; strcpy (r->u.value, "sign"); r->next = para; para = r; r = xcalloc (1, sizeof *r + 20 ); r->key = pSUBKEYTYPE; sprintf( r->u.value, "%d", info.key_attr[1].algo ); r->next = para; para = r; r = xcalloc (1, sizeof *r + 20 ); r->key = pSUBKEYUSAGE; strcpy (r->u.value, "encrypt"); r->next = para; para = r; if (info.key_attr[1].algo == PUBKEY_ALGO_RSA) { r = xcalloc (1, sizeof *r + 20 ); r->key = pSUBKEYLENGTH; sprintf( r->u.value, "%u", info.key_attr[1].nbits); r->next = para; para = r; } else if (info.key_attr[1].algo == PUBKEY_ALGO_ECDSA || info.key_attr[1].algo == PUBKEY_ALGO_EDDSA || info.key_attr[1].algo == PUBKEY_ALGO_ECDH) { r = xcalloc (1, sizeof *r + strlen (info.key_attr[1].curve)); r->key = pSUBKEYCURVE; strcpy (r->u.value, info.key_attr[1].curve); r->next = para; para = r; } r = xcalloc (1, sizeof *r + 20 ); r->key = pAUTHKEYTYPE; sprintf( r->u.value, "%d", info.key_attr[2].algo ); r->next = para; para = r; if (card_backup_key) { r = xcalloc (1, sizeof *r + 1); r->key = pCARDBACKUPKEY; strcpy (r->u.value, "1"); r->next = para; para = r; } #endif /*ENABLE_CARD_SUPPORT*/ } else if (full) /* Full featured key generation. */ { int subkey_algo; char *key_from_hexgrip = NULL; algo = ask_algo (ctrl, 0, &subkey_algo, &use, &key_from_hexgrip); if (key_from_hexgrip) { r = xmalloc_clear( sizeof *r + 20 ); r->key = pKEYTYPE; sprintf( r->u.value, "%d", algo); r->next = para; para = r; if (use) { r = xmalloc_clear( sizeof *r + 25 ); r->key = pKEYUSAGE; sprintf( r->u.value, "%s%s%s", (use & PUBKEY_USAGE_SIG)? "sign ":"", (use & PUBKEY_USAGE_ENC)? "encrypt ":"", (use & PUBKEY_USAGE_AUTH)? "auth":"" ); r->next = para; para = r; } r = xmalloc_clear( sizeof *r + 40 ); r->key = pKEYGRIP; strcpy (r->u.value, key_from_hexgrip); r->next = para; para = r; xfree (key_from_hexgrip); } else { const char *curve = NULL; if (subkey_algo) { /* Create primary and subkey at once. */ both = 1; if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA || algo == PUBKEY_ALGO_ECDH) { curve = ask_curve (&algo, &subkey_algo, NULL); r = xmalloc_clear( sizeof *r + 20 ); r->key = pKEYTYPE; sprintf( r->u.value, "%d", algo); r->next = para; para = r; nbits = 0; r = xmalloc_clear (sizeof *r + strlen (curve)); r->key = pKEYCURVE; strcpy (r->u.value, curve); r->next = para; para = r; } else { r = xmalloc_clear( sizeof *r + 20 ); r->key = pKEYTYPE; sprintf( r->u.value, "%d", algo); r->next = para; para = r; nbits = ask_keysize (algo, 0); r = xmalloc_clear( sizeof *r + 20 ); r->key = pKEYLENGTH; sprintf( r->u.value, "%u", nbits); r->next = para; para = r; } r = xmalloc_clear( sizeof *r + 20 ); r->key = pKEYUSAGE; strcpy( r->u.value, "sign" ); r->next = para; para = r; r = xmalloc_clear( sizeof *r + 20 ); r->key = pSUBKEYTYPE; sprintf( r->u.value, "%d", subkey_algo); r->next = para; para = r; r = xmalloc_clear( sizeof *r + 20 ); r->key = pSUBKEYUSAGE; strcpy( r->u.value, "encrypt" ); r->next = para; para = r; if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA || algo == PUBKEY_ALGO_ECDH) { if (algo == PUBKEY_ALGO_EDDSA && subkey_algo == PUBKEY_ALGO_ECDH) { /* Need to switch to a different curve for the encryption key. */ curve = "Curve25519"; } r = xmalloc_clear (sizeof *r + strlen (curve)); r->key = pSUBKEYCURVE; strcpy (r->u.value, curve); r->next = para; para = r; } } else /* Create only a single key. */ { /* For ECC we need to ask for the curve before storing the algo because ask_curve may change the algo. */ if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA || algo == PUBKEY_ALGO_ECDH) { curve = ask_curve (&algo, NULL, NULL); r = xmalloc_clear (sizeof *r + strlen (curve)); r->key = pKEYCURVE; strcpy (r->u.value, curve); r->next = para; para = r; } r = xmalloc_clear( sizeof *r + 20 ); r->key = pKEYTYPE; sprintf( r->u.value, "%d", algo ); r->next = para; para = r; if (use) { r = xmalloc_clear( sizeof *r + 25 ); r->key = pKEYUSAGE; sprintf( r->u.value, "%s%s%s", (use & PUBKEY_USAGE_SIG)? "sign ":"", (use & PUBKEY_USAGE_ENC)? "encrypt ":"", (use & PUBKEY_USAGE_AUTH)? "auth":"" ); r->next = para; para = r; } nbits = 0; } if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA || algo == PUBKEY_ALGO_ECDH) { /* The curve has already been set. */ } else { nbits = ask_keysize (both? subkey_algo : algo, nbits); r = xmalloc_clear( sizeof *r + 20 ); r->key = both? pSUBKEYLENGTH : pKEYLENGTH; sprintf( r->u.value, "%u", nbits); r->next = para; para = r; } } } else /* Default key generation. */ { int subalgo; unsigned int size, subsize; unsigned int keyuse, subkeyuse; const char *curve, *subcurve; char *keygrip, *subkeygrip; tty_printf ( _("Note: Use \"%s %s\"" " for a full featured key generation dialog.\n"), #if USE_GPG2_HACK GPG_NAME "2" #else GPG_NAME #endif , "--full-generate-key" ); err = parse_key_parameter_string (ctrl, NULL, -1, 0, &algo, &size, &keyuse, &curve, &keygrip, &subalgo, &subsize, &subkeyuse, &subcurve, &subkeygrip); if (err) { log_error (_("Key generation failed: %s\n"), gpg_strerror (err)); return; } para = quickgen_set_para (para, 0, algo, size, curve, keyuse, keygrip); if (subalgo) para = quickgen_set_para (para, 1, subalgo, subsize, subcurve, subkeyuse, subkeygrip); xfree (keygrip); xfree (subkeygrip); } expire = full? ask_expire_interval (0, NULL) : parse_expire_string (default_expiration_interval); r = xcalloc (1, sizeof *r + 20); r->key = pKEYEXPIRE; r->u.expire = expire; r->next = para; para = r; r = xcalloc (1, sizeof *r + 20); r->key = pSUBKEYEXPIRE; r->u.expire = expire; r->next = para; para = r; uid = ask_user_id (0, full, NULL); if (!uid) { log_error(_("Key generation canceled.\n")); release_parameter_list( para ); return; } r = xcalloc (1, sizeof *r + strlen (uid)); r->key = pUSERID; strcpy (r->u.value, uid); r->next = para; para = r; proc_parameter_file (ctrl, para, "[internal]", &outctrl, !!card_serialno); release_parameter_list (para); } /* Create and delete a dummy packet to start off a list of kbnodes. */ static void start_tree(KBNODE *tree) { PACKET *pkt; pkt=xmalloc_clear(sizeof(*pkt)); pkt->pkttype=PKT_NONE; *tree=new_kbnode(pkt); delete_kbnode(*tree); } /* Write the *protected* secret key to the file. */ static gpg_error_t card_write_key_to_backup_file (PKT_public_key *sk, const char *backup_dir) { gpg_error_t err = 0; int rc; char keyid_buffer[2 * 8 + 1]; char name_buffer[50]; char *fname; IOBUF fp; mode_t oldmask; PACKET *pkt = NULL; format_keyid (pk_keyid (sk), KF_LONG, keyid_buffer, sizeof (keyid_buffer)); snprintf (name_buffer, sizeof name_buffer, "sk_%s.gpg", keyid_buffer); fname = make_filename (backup_dir, name_buffer, NULL); /* Note that the umask call is not anymore needed because iobuf_create now takes care of it. However, it does not harm and thus we keep it. */ oldmask = umask (077); if (is_secured_filename (fname)) { fp = NULL; gpg_err_set_errno (EPERM); } else fp = iobuf_create (fname, 1); umask (oldmask); if (!fp) { err = gpg_error_from_syserror (); log_error (_("can't create backup file '%s': %s\n"), fname, strerror (errno) ); goto leave; } pkt = xcalloc (1, sizeof *pkt); pkt->pkttype = PKT_SECRET_KEY; pkt->pkt.secret_key = sk; rc = build_packet (fp, pkt); if (rc) { log_error ("build packet failed: %s\n", gpg_strerror (rc)); iobuf_cancel (fp); } else { char *fprbuf; iobuf_close (fp); iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname); log_info (_("Note: backup of card key saved to '%s'\n"), fname); fprbuf = hexfingerprint (sk, NULL, 0); if (!fprbuf) { err = gpg_error_from_syserror (); goto leave; } write_status_text_and_buffer (STATUS_BACKUP_KEY_CREATED, fprbuf, fname, strlen (fname), 0); xfree (fprbuf); } leave: xfree (pkt); xfree (fname); return err; } /* Store key to card and make a backup file in OpenPGP format. */ static gpg_error_t card_store_key_with_backup (ctrl_t ctrl, PKT_public_key *sub_psk, const char *backup_dir) { PKT_public_key *sk; gnupg_isotime_t timestamp; gpg_error_t err; char *hexgrip; int rc; struct agent_card_info_s info; gcry_cipher_hd_t cipherhd = NULL; char *cache_nonce = NULL; void *kek = NULL; size_t keklen; char *ecdh_param_str = NULL; sk = copy_public_key (NULL, sub_psk); if (!sk) return gpg_error_from_syserror (); epoch2isotime (timestamp, (time_t)sk->timestamp); if (sk->pubkey_algo == PUBKEY_ALGO_ECDH) { ecdh_param_str = ecdh_param_str_from_pk (sk); if (!ecdh_param_str) { free_public_key (sk); return gpg_error_from_syserror (); } } err = hexkeygrip_from_pk (sk, &hexgrip); if (err) { xfree (ecdh_param_str); free_public_key (sk); return err; } memset(&info, 0, sizeof (info)); rc = agent_scd_getattr ("SERIALNO", &info); if (rc) { xfree (ecdh_param_str); free_public_key (sk); return (gpg_error_t)rc; } rc = agent_keytocard (hexgrip, 2, 1, info.serialno, timestamp, ecdh_param_str); xfree (info.serialno); if (rc) { err = (gpg_error_t)rc; goto leave; } err = agent_keywrap_key (ctrl, 1, &kek, &keklen); if (err) { log_error ("error getting the KEK: %s\n", gpg_strerror (err)); goto leave; } err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, 0); if (!err) err = gcry_cipher_setkey (cipherhd, kek, keklen); if (err) { log_error ("error setting up an encryption context: %s\n", gpg_strerror (err)); goto leave; } err = receive_seckey_from_agent (ctrl, cipherhd, 0, &cache_nonce, hexgrip, sk); if (err) { log_error ("error getting secret key from agent: %s\n", gpg_strerror (err)); goto leave; } err = card_write_key_to_backup_file (sk, backup_dir); if (err) log_error ("writing card key to backup file: %s\n", gpg_strerror (err)); else - /* Remove secret key data in agent side. */ - agent_scd_learn (NULL, 1); + { + /* Remove secret key data in agent side. We use force 2 here to + * allow overwriting of the temporary private key. */ + agent_scd_learn (NULL, 2); + } leave: xfree (ecdh_param_str); xfree (cache_nonce); gcry_cipher_close (cipherhd); xfree (kek); xfree (hexgrip); free_public_key (sk); return err; } static void do_generate_keypair (ctrl_t ctrl, struct para_data_s *para, struct output_control_s *outctrl, int card) { gpg_error_t err; KBNODE pub_root = NULL; const char *s; PKT_public_key *pri_psk = NULL; PKT_public_key *sub_psk = NULL; struct revocation_key *revkey; int did_sub = 0; u32 timestamp; char *cache_nonce = NULL; int algo; u32 expire; const char *key_from_hexgrip = NULL; unsigned int idx; if (outctrl->dryrun) { log_info("dry-run mode - key generation skipped\n"); return; } if ( outctrl->use_files ) { if ( outctrl->pub.newfname ) { iobuf_close(outctrl->pub.stream); outctrl->pub.stream = NULL; if (outctrl->pub.fname) iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)outctrl->pub.fname); xfree( outctrl->pub.fname ); outctrl->pub.fname = outctrl->pub.newfname; outctrl->pub.newfname = NULL; if (is_secured_filename (outctrl->pub.fname) ) { outctrl->pub.stream = NULL; gpg_err_set_errno (EPERM); } else outctrl->pub.stream = iobuf_create (outctrl->pub.fname, 0); if (!outctrl->pub.stream) { log_error(_("can't create '%s': %s\n"), outctrl->pub.newfname, strerror(errno) ); return; } if (opt.armor) { outctrl->pub.afx->what = 1; push_armor_filter (outctrl->pub.afx, outctrl->pub.stream); } } log_assert( outctrl->pub.stream ); if (opt.verbose) log_info (_("writing public key to '%s'\n"), outctrl->pub.fname ); } /* We create the packets as a tree of kbnodes. Because the structure we create is known in advance we simply generate a linked list. The first packet is a dummy packet which we flag as deleted. The very first packet must always be a KEY packet. */ start_tree (&pub_root); timestamp = get_parameter_u32 (para, pKEYCREATIONDATE); if (!timestamp) timestamp = make_timestamp (); /* Note that, depending on the backend (i.e. the used scdaemon version), the card key generation may update TIMESTAMP for each key. Thus we need to pass TIMESTAMP to all signing function to make sure that the binding signature is done using the timestamp of the corresponding (sub)key and not that of the primary key. An alternative implementation could tell the signing function the node of the subkey but that is more work than just to pass the current timestamp. */ algo = get_parameter_algo (ctrl, para, pKEYTYPE, NULL ); expire = get_parameter_u32( para, pKEYEXPIRE ); key_from_hexgrip = get_parameter_value (para, pKEYGRIP); if (key_from_hexgrip) err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip, pub_root, timestamp, expire, 0); else if (!card) err = do_create (algo, get_parameter_uint( para, pKEYLENGTH ), get_parameter_value (para, pKEYCURVE), pub_root, timestamp, expire, 0, outctrl->keygen_flags, get_parameter_passphrase (para), &cache_nonce, NULL); else err = gen_card_key (1, algo, 1, pub_root, ×tamp, expire); /* Get the pointer to the generated public key packet. */ if (!err) { pri_psk = pub_root->next->pkt->pkt.public_key; log_assert (pri_psk); /* Make sure a few fields are correctly set up before going further. */ pri_psk->flags.primary = 1; keyid_from_pk (pri_psk, NULL); /* We don't use pk_keyid to get keyid, because it also asserts that main_keyid is set! */ keyid_copy (pri_psk->main_keyid, pri_psk->keyid); } /* Write all signatures specifying designated revokers. */ for (idx=0; !err && (revkey = get_parameter_revkey (para, pREVOKER, idx)); idx++) err = write_direct_sig (ctrl, pub_root, pri_psk, revkey, timestamp, cache_nonce); if (!err && (s = get_parameter_value (para, pUSERID))) { err = write_uid (pub_root, s ); if (!err) err = write_selfsigs (ctrl, pub_root, pri_psk, get_parameter_uint (para, pKEYUSAGE), timestamp, cache_nonce); } /* Write the auth key to the card before the encryption key. This is a partial workaround for a PGP bug (as of this writing, all versions including 8.1), that causes it to try and encrypt to the most recent subkey regardless of whether that subkey is actually an encryption type. In this case, the auth key is an RSA key so it succeeds. */ if (!err && card && get_parameter (para, pAUTHKEYTYPE)) { err = gen_card_key (3, get_parameter_algo (ctrl, para, pAUTHKEYTYPE, NULL ), 0, pub_root, ×tamp, expire); if (!err) err = write_keybinding (ctrl, pub_root, pri_psk, NULL, PUBKEY_USAGE_AUTH, timestamp, cache_nonce); } if (!err && get_parameter (para, pSUBKEYTYPE)) { int subkey_algo = get_parameter_algo (ctrl, para, pSUBKEYTYPE, NULL); s = NULL; key_from_hexgrip = get_parameter_value (para, pSUBKEYGRIP); if (key_from_hexgrip) err = do_create_from_keygrip (ctrl, subkey_algo, key_from_hexgrip, pub_root, timestamp, get_parameter_u32 (para, pSUBKEYEXPIRE), 1); else if (!card || (s = get_parameter_value (para, pCARDBACKUPKEY))) { err = do_create (subkey_algo, get_parameter_uint (para, pSUBKEYLENGTH), get_parameter_value (para, pSUBKEYCURVE), pub_root, timestamp, get_parameter_u32 (para, pSUBKEYEXPIRE), 1, s ? KEYGEN_FLAG_NO_PROTECTION : outctrl->keygen_flags, get_parameter_passphrase (para), &cache_nonce, NULL); /* Get the pointer to the generated public subkey packet. */ if (!err) { kbnode_t node; for (node = pub_root; node; node = node->next) if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) sub_psk = node->pkt->pkt.public_key; log_assert (sub_psk); if (s) err = card_store_key_with_backup (ctrl, sub_psk, gnupg_homedir ()); } } else { err = gen_card_key (2, subkey_algo, 0, pub_root, ×tamp, expire); } if (!err) err = write_keybinding (ctrl, pub_root, pri_psk, sub_psk, get_parameter_uint (para, pSUBKEYUSAGE), timestamp, cache_nonce); did_sub = 1; } if (!err && outctrl->use_files) /* Direct write to specified files. */ { err = write_keyblock (outctrl->pub.stream, pub_root); if (err) log_error ("can't write public key: %s\n", gpg_strerror (err)); } else if (!err) /* Write to the standard keyrings. */ { KEYDB_HANDLE pub_hd; pub_hd = keydb_new (); if (!pub_hd) err = gpg_error_from_syserror (); else { err = keydb_locate_writable (pub_hd); if (err) log_error (_("no writable public keyring found: %s\n"), gpg_strerror (err)); } if (!err && opt.verbose) { log_info (_("writing public key to '%s'\n"), keydb_get_resource_name (pub_hd)); } if (!err) { err = keydb_insert_keyblock (pub_hd, pub_root); if (err) log_error (_("error writing public keyring '%s': %s\n"), keydb_get_resource_name (pub_hd), gpg_strerror (err)); } keydb_release (pub_hd); if (!err) { int no_enc_rsa; PKT_public_key *pk; no_enc_rsa = ((get_parameter_algo (ctrl, para, pKEYTYPE, NULL) == PUBKEY_ALGO_RSA) && get_parameter_uint (para, pKEYUSAGE) && !((get_parameter_uint (para, pKEYUSAGE) & PUBKEY_USAGE_ENC)) ); pk = find_kbnode (pub_root, PKT_PUBLIC_KEY)->pkt->pkt.public_key; update_ownertrust (ctrl, pk, ((get_ownertrust (ctrl, pk) & ~TRUST_MASK) | TRUST_ULTIMATE )); gen_standard_revoke (ctrl, pk, cache_nonce); /* Get rid of the first empty packet. */ commit_kbnode (&pub_root); if (!opt.batch) { tty_printf (_("public and secret key created and signed.\n") ); tty_printf ("\n"); merge_keys_and_selfsig (ctrl, pub_root); list_keyblock_direct (ctrl, pub_root, 0, 1, opt.fingerprint || opt.with_fingerprint, 1); } if (!opt.batch && (get_parameter_algo (ctrl, para, pKEYTYPE, NULL) == PUBKEY_ALGO_DSA || no_enc_rsa ) && !get_parameter (para, pSUBKEYTYPE) ) { tty_printf(_("Note that this key cannot be used for " "encryption. You may want to use\n" "the command \"--edit-key\" to generate a " "subkey for this purpose.\n") ); } } } if (err) { if (opt.batch) log_error ("key generation failed: %s\n", gpg_strerror (err) ); else tty_printf (_("Key generation failed: %s\n"), gpg_strerror (err) ); write_status_error (card? "card_key_generate":"key_generate", err); print_status_key_not_created ( get_parameter_value (para, pHANDLE) ); } else { PKT_public_key *pk = find_kbnode (pub_root, PKT_PUBLIC_KEY)->pkt->pkt.public_key; print_status_key_created (did_sub? 'B':'P', pk, get_parameter_value (para, pHANDLE)); } release_kbnode (pub_root); xfree (cache_nonce); } static gpg_error_t parse_algo_usage_expire (ctrl_t ctrl, int for_subkey, const char *algostr, const char *usagestr, const char *expirestr, int *r_algo, unsigned int *r_usage, u32 *r_expire, unsigned int *r_nbits, const char **r_curve, char **r_keygrip) { gpg_error_t err; int algo; unsigned int use, nbits; u32 expire; int wantuse; const char *curve = NULL; *r_curve = NULL; if (r_keygrip) *r_keygrip = NULL; nbits = 0; /* Parse the algo string. */ if (algostr && *algostr == '&' && strlen (algostr) == 41) { /* Take algo from existing key. */ algo = check_keygrip (ctrl, algostr+1); /* FIXME: We need the curve name as well. */ return gpg_error (GPG_ERR_NOT_IMPLEMENTED); } err = parse_key_parameter_string (ctrl, algostr, for_subkey? 1 : 0, usagestr? parse_usagestr (usagestr):0, &algo, &nbits, &use, &curve, r_keygrip, NULL, NULL, NULL, NULL, NULL); if (err) { if (r_keygrip) { xfree (*r_keygrip); *r_keygrip = NULL; } return err; } /* Parse the usage string. */ if (!usagestr || !*usagestr || !ascii_strcasecmp (usagestr, "default") || !strcmp (usagestr, "-")) ; /* Keep usage from parse_key_parameter_string. */ else if ((wantuse = parse_usagestr (usagestr)) != -1) use = wantuse; else { if (r_keygrip) { xfree (*r_keygrip); *r_keygrip = NULL; } return gpg_error (GPG_ERR_INV_VALUE); } /* Make sure a primary key has the CERT usage. */ if (!for_subkey) use |= PUBKEY_USAGE_CERT; /* Check that usage is possible. NB: We have the same check in * parse_key_parameter_string but need it here again in case the * separate usage value has been given. */ if (/**/((use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT)) && !pubkey_get_nsig (algo)) || ((use & PUBKEY_USAGE_ENC) && !pubkey_get_nenc (algo)) || (for_subkey && (use & PUBKEY_USAGE_CERT))) { if (r_keygrip) { xfree (*r_keygrip); *r_keygrip = NULL; } return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } /* Parse the expire string. */ expire = parse_expire_string (expirestr); if (expire == (u32)-1 ) { if (r_keygrip) { xfree (*r_keygrip); *r_keygrip = NULL; } return gpg_error (GPG_ERR_INV_VALUE); } if (curve) *r_curve = curve; *r_algo = algo; *r_usage = use; *r_expire = expire; *r_nbits = nbits; return 0; } /* Add a new subkey to an existing key. Returns 0 if a new key has been generated and put into the keyblocks. If any of ALGOSTR, USAGESTR, or EXPIRESTR is NULL interactive mode is used. */ gpg_error_t generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr, const char *usagestr, const char *expirestr) { gpg_error_t err = 0; int interactive; kbnode_t node; PKT_public_key *pri_psk = NULL; PKT_public_key *sub_psk = NULL; int algo; unsigned int use; u32 expire; unsigned int nbits = 0; const char *curve = NULL; u32 cur_time; char *key_from_hexgrip = NULL; char *hexgrip = NULL; char *serialno = NULL; char *cache_nonce = NULL; char *passwd_nonce = NULL; interactive = (!algostr || !usagestr || !expirestr); /* Break out the primary key. */ node = find_kbnode (keyblock, PKT_PUBLIC_KEY); if (!node) { log_error ("Oops; primary key missing in keyblock!\n"); err = gpg_error (GPG_ERR_BUG); goto leave; } pri_psk = node->pkt->pkt.public_key; cur_time = make_timestamp (); if (pri_psk->timestamp > cur_time) { ulong d = pri_psk->timestamp - cur_time; log_info ( d==1 ? _("key has been created %lu second " "in future (time warp or clock problem)\n") : _("key has been created %lu seconds " "in future (time warp or clock problem)\n"), d ); if (!opt.ignore_time_conflict) { err = gpg_error (GPG_ERR_TIME_CONFLICT); goto leave; } } if (pri_psk->version < 4) { log_info (_("Note: creating subkeys for v3 keys " "is not OpenPGP compliant\n")); err = gpg_error (GPG_ERR_CONFLICT); goto leave; } err = hexkeygrip_from_pk (pri_psk, &hexgrip); if (err) goto leave; if (agent_get_keyinfo (NULL, hexgrip, &serialno, NULL)) { if (interactive) tty_printf (_("Secret parts of primary key are not available.\n")); else log_info ( _("Secret parts of primary key are not available.\n")); err = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } if (serialno) { if (interactive) tty_printf (_("Secret parts of primary key are stored on-card.\n")); else log_info ( _("Secret parts of primary key are stored on-card.\n")); } if (interactive) { algo = ask_algo (ctrl, 1, NULL, &use, &key_from_hexgrip); log_assert (algo); if (key_from_hexgrip) nbits = 0; else if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA || algo == PUBKEY_ALGO_ECDH) curve = ask_curve (&algo, NULL, NULL); else nbits = ask_keysize (algo, 0); expire = ask_expire_interval (0, NULL); if (!cpr_enabled() && !cpr_get_answer_is_yes("keygen.sub.okay", _("Really create? (y/N) "))) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } else /* Unattended mode. */ { err = parse_algo_usage_expire (ctrl, 1, algostr, usagestr, expirestr, &algo, &use, &expire, &nbits, &curve, &key_from_hexgrip); if (err) goto leave; } /* Verify the passphrase now so that we get a cache item for the * primary key passphrase. The agent also returns a passphrase * nonce, which we can use to set the passphrase for the subkey to * that of the primary key. */ { char *desc = gpg_format_keydesc (ctrl, pri_psk, FORMAT_KEYDESC_NORMAL, 1); err = agent_passwd (ctrl, hexgrip, desc, 1 /*=verify*/, &cache_nonce, &passwd_nonce); xfree (desc); if (gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED && gpg_err_source (err) == GPG_ERR_SOURCE_GPGAGENT) err = 0; /* Very likely that the key is on a card. */ if (err) goto leave; } /* Start creation. */ if (key_from_hexgrip) { err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip, keyblock, cur_time, expire, 1); } else { const char *passwd; /* If the pinentry loopback mode is not and we have a static passphrase (i.e. set with --passphrase{,-fd,-file} while in batch mode), we use that passphrase for the new subkey. */ if (opt.pinentry_mode != PINENTRY_MODE_LOOPBACK && have_static_passphrase ()) passwd = get_static_passphrase (); else passwd = NULL; err = do_create (algo, nbits, curve, keyblock, cur_time, expire, 1, 0, passwd, &cache_nonce, &passwd_nonce); } if (err) goto leave; /* Get the pointer to the generated public subkey packet. */ for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) sub_psk = node->pkt->pkt.public_key; /* Write the binding signature. */ err = write_keybinding (ctrl, keyblock, pri_psk, sub_psk, use, cur_time, cache_nonce); if (err) goto leave; print_status_key_created ('S', sub_psk, NULL); leave: xfree (key_from_hexgrip); xfree (hexgrip); xfree (serialno); xfree (cache_nonce); xfree (passwd_nonce); if (err) log_error (_("Key generation failed: %s\n"), gpg_strerror (err) ); return err; } #ifdef ENABLE_CARD_SUPPORT /* Generate a subkey on a card. */ gpg_error_t generate_card_subkeypair (ctrl_t ctrl, kbnode_t pub_keyblock, int keyno, const char *serialno) { gpg_error_t err = 0; kbnode_t node; PKT_public_key *pri_pk = NULL; unsigned int use; u32 expire; u32 cur_time; struct para_data_s *para = NULL; PKT_public_key *sub_pk = NULL; int algo; struct agent_card_info_s info; log_assert (keyno >= 1 && keyno <= 3); memset (&info, 0, sizeof (info)); err = agent_scd_getattr ("KEY-ATTR", &info); if (err) { log_error (_("error getting current key info: %s\n"), gpg_strerror (err)); return err; } algo = info.key_attr[keyno-1].algo; para = xtrycalloc (1, sizeof *para + strlen (serialno) ); if (!para) { err = gpg_error_from_syserror (); goto leave; } para->key = pSERIALNO; strcpy (para->u.value, serialno); /* Break out the primary secret key */ node = find_kbnode (pub_keyblock, PKT_PUBLIC_KEY); if (!node) { log_error ("Oops; public key lost!\n"); err = gpg_error (GPG_ERR_INTERNAL); goto leave; } pri_pk = node->pkt->pkt.public_key; cur_time = make_timestamp(); if (pri_pk->timestamp > cur_time) { ulong d = pri_pk->timestamp - cur_time; log_info (d==1 ? _("key has been created %lu second " "in future (time warp or clock problem)\n") : _("key has been created %lu seconds " "in future (time warp or clock problem)\n"), d ); if (!opt.ignore_time_conflict) { err = gpg_error (GPG_ERR_TIME_CONFLICT); goto leave; } } if (pri_pk->version < 4) { log_info (_("Note: creating subkeys for v3 keys " "is not OpenPGP compliant\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } expire = ask_expire_interval (0, NULL); if (keyno == 1) use = PUBKEY_USAGE_SIG; else if (keyno == 2) use = PUBKEY_USAGE_ENC; else use = PUBKEY_USAGE_AUTH; if (!cpr_enabled() && !cpr_get_answer_is_yes("keygen.cardsub.okay", _("Really create? (y/N) "))) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } /* Note, that depending on the backend, the card key generation may update CUR_TIME. */ err = gen_card_key (keyno, algo, 0, pub_keyblock, &cur_time, expire); /* Get the pointer to the generated public subkey packet. */ if (!err) { for (node = pub_keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) sub_pk = node->pkt->pkt.public_key; log_assert (sub_pk); err = write_keybinding (ctrl, pub_keyblock, pri_pk, sub_pk, use, cur_time, NULL); } leave: if (err) log_error (_("Key generation failed: %s\n"), gpg_strerror (err) ); else print_status_key_created ('S', sub_pk, NULL); release_parameter_list (para); return err; } #endif /* !ENABLE_CARD_SUPPORT */ /* * Write a keyblock to an output stream */ static int write_keyblock( IOBUF out, KBNODE node ) { for( ; node ; node = node->next ) { if(!is_deleted_kbnode(node)) { int rc = build_packet( out, node->pkt ); if( rc ) { log_error("build_packet(%d) failed: %s\n", node->pkt->pkttype, gpg_strerror (rc) ); return rc; } } } return 0; } /* Note that timestamp is an in/out arg. */ static gpg_error_t gen_card_key (int keyno, int algo, int is_primary, kbnode_t pub_root, u32 *timestamp, u32 expireval) { #ifdef ENABLE_CARD_SUPPORT gpg_error_t err; PACKET *pkt; PKT_public_key *pk; char keyid[10]; unsigned char *public; gcry_sexp_t s_key; snprintf (keyid, DIM(keyid), "OPENPGP.%d", keyno); pk = xtrycalloc (1, sizeof *pk ); if (!pk) return gpg_error_from_syserror (); pkt = xtrycalloc (1, sizeof *pkt); if (!pkt) { xfree (pk); return gpg_error_from_syserror (); } /* Note: SCD knows the serialnumber, thus there is no point in passing it. */ err = agent_scd_genkey (keyno, 1, timestamp); /* The code below is not used because we force creation of * the a card key (3rd arg). * if (gpg_err_code (rc) == GPG_ERR_EEXIST) * { * tty_printf ("\n"); * log_error ("WARNING: key does already exists!\n"); * tty_printf ("\n"); * if ( cpr_get_answer_is_yes( "keygen.card.replace_key", * _("Replace existing key? "))) * rc = agent_scd_genkey (keyno, 1, timestamp); * } */ if (err) { log_error ("key generation failed: %s\n", gpg_strerror (err)); xfree (pkt); xfree (pk); return err; } /* Send the READKEY command so that the agent creates a shadow key for card key. We need to do that now so that we are able to create the self-signatures. */ err = agent_readkey (NULL, 1, keyid, &public); if (err) return err; err = gcry_sexp_sscan (&s_key, NULL, public, gcry_sexp_canon_len (public, 0, NULL, NULL)); xfree (public); if (err) return err; if (algo == PUBKEY_ALGO_RSA) err = key_from_sexp (pk->pkey, s_key, "public-key", "ne"); else if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA || algo == PUBKEY_ALGO_ECDH ) err = ecckey_from_sexp (pk->pkey, s_key, algo); else err = gpg_error (GPG_ERR_PUBKEY_ALGO); gcry_sexp_release (s_key); if (err) { log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) ); free_public_key (pk); return err; } pk->timestamp = *timestamp; pk->version = 4; if (expireval) pk->expiredate = pk->timestamp + expireval; pk->pubkey_algo = algo; pkt->pkttype = is_primary ? PKT_PUBLIC_KEY : PKT_PUBLIC_SUBKEY; pkt->pkt.public_key = pk; add_kbnode (pub_root, new_kbnode (pkt)); return 0; #else (void)keyno; (void)is_primary; (void)pub_root; (void)timestamp; (void)expireval; return gpg_error (GPG_ERR_NOT_SUPPORTED); #endif /*!ENABLE_CARD_SUPPORT*/ }