diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:14:06 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:14:06 +0000 |
commit | eee068778cb28ecf3c14e1bf843a95547d72c42d (patch) | |
tree | 0e07b30ddc5ea579d682d5dbe57998200d1c9ab7 /agent | |
parent | Initial commit. (diff) | |
download | gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.tar.xz gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.zip |
Adding upstream version 2.2.40.upstream/2.2.40
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'agent')
-rw-r--r-- | agent/ChangeLog-2011 | 3107 | ||||
-rw-r--r-- | agent/Makefile.am | 118 | ||||
-rw-r--r-- | agent/Makefile.in | 1408 | ||||
-rw-r--r-- | agent/agent.h | 650 | ||||
-rw-r--r-- | agent/all-tests.scm | 35 | ||||
-rw-r--r-- | agent/cache.c | 530 | ||||
-rw-r--r-- | agent/call-pinentry.c | 2091 | ||||
-rw-r--r-- | agent/call-scd.c | 1340 | ||||
-rw-r--r-- | agent/command-ssh.c | 3870 | ||||
-rw-r--r-- | agent/command.c | 3648 | ||||
-rw-r--r-- | agent/cvt-openpgp.c | 1413 | ||||
-rw-r--r-- | agent/cvt-openpgp.h | 37 | ||||
-rw-r--r-- | agent/divert-scd.c | 734 | ||||
-rw-r--r-- | agent/findkey.c | 1827 | ||||
-rw-r--r-- | agent/genkey.c | 650 | ||||
-rw-r--r-- | agent/gpg-agent-w32info.rc | 52 | ||||
-rw-r--r-- | agent/gpg-agent.c | 3266 | ||||
-rw-r--r-- | agent/gpg-agent.w32-manifest.in | 18 | ||||
-rw-r--r-- | agent/learncard.c | 452 | ||||
-rw-r--r-- | agent/pkdecrypt.c | 147 | ||||
-rw-r--r-- | agent/pksign.c | 572 | ||||
-rw-r--r-- | agent/preset-passphrase.c | 272 | ||||
-rw-r--r-- | agent/protect-tool.c | 837 | ||||
-rw-r--r-- | agent/protect.c | 1761 | ||||
-rw-r--r-- | agent/t-protect.c | 351 | ||||
-rw-r--r-- | agent/trans.c | 41 | ||||
-rw-r--r-- | agent/trustlist.c | 852 |
27 files changed, 30079 insertions, 0 deletions
diff --git a/agent/ChangeLog-2011 b/agent/ChangeLog-2011 new file mode 100644 index 0000000..d32d69c --- /dev/null +++ b/agent/ChangeLog-2011 @@ -0,0 +1,3107 @@ +2011-12-01 Werner Koch <wk@g10code.com> + + NB: ChangeLog files are no longer manually maintained. Starting + on December 1st, 2011 we put change information only in the GIT + commit log, and generate a top-level ChangeLog file from logs at + "make dist". See doc/HACKING for details. + +2011-11-28 Werner Koch <wk@g10code.com> + + * command-ssh.c (card_key_available): Change wording of no key + diagnostic. + (ssh_handler_request_identities): Do not call card_key_available + if the scdaemon is disabled. + +2011-09-12 Ben Kibbey <bjk@luxsci.net> + + * genkey.c (agent_ask_new_passphrase): Allow for an empty passphrase + (no protection) in PINENTRY_MODE_LOOPBACK. + +2011-09-10 Ben Kibbey <bjk@luxsci.net> + + * agent.h (pinentry_loopback): New prototype. + * command.c (pinentry_loopback): New function to inquire a passphrase + from the client. For use with pinentry-mode=loopback. + * call-pinentry.c (agent_askpin): Handle PINENTRY_MODE_LOOPBACK. + * call-pinentry.c (agent_get_passphrase): Ditto. + * genkey.c (agent_ask_new_passphrase): Ditto. + +2011-08-10 Werner Koch <wk@g10code.com> + + * genkey.c (check_passphrase_pattern): Use gpg_strerror instead of + strerror. + * command-ssh.c (ssh_receive_mpint_list): Remove unused var + ELEMS_PUBLIC_N. + * gpg-agent.c (main): Remove unused var MAY_COREDUMP. + +2011-08-09 Ben Kibbey <bjk@luxsci.net> + + * command.c (option_handler): Have option s2k-count match the + documentation. + +2011-07-27 Werner Koch <wk@g10code.com> + + * call-scd.c (struct inq_needpin_s): Add field ANY_INQ_SEEN. + (inq_needpin): Set it. + (agent_card_scd): Send the cancel only if an inquire was actually + used. + +2011-07-09 Ben Kibbey <bjk@luxsci.net> + + * call-scd.c (agent_card_scd): Send the CANCEL command back to SCD + when the SCD command is cancelled from the client. + +2011-07-22 Werner Koch <wk@g10code.com> + + * command-ssh.c (ssh_receive_key): Do not init comment to an empty + static string; in the error case it would be freed. + +2011-07-20 Werner Koch <wk@g10code.com> + + * command.c (do_one_keyinfo, cmd_keyinfo): Support option --ssh-fpr. + + * command-ssh.c (ssh_identity_register): Display the ssh + fingerprint in the prompt. + (add_control_entry): Add arg FMTFPR and use it as comment in + sshcontrol. + (confirm_flag_from_sshcontrol): New. + (data_sign): Ask for confirmaton if requested. + (search_control_file): Add new arg R_CONFIRM and enhance parser. + * findkey.c (agent_raw_key_from_file): New. + (modify_description): Add format letter %F. + * findkey.c (agent_key_from_file): Simplify comment extraction by + using gcry_sexp_nth_string. + +2011-06-28 Ben Kibbey <bjk@luxsci.net> + + * command.c (option_handler): Add option s2k-count. + * agent.h (server_control_s): Add member s2k_count. + * genkey.c (store_key): Add parameter s2k_count. + * protect.c (agent_protect): Add parameter s2k_count. + * protect.c (do_encryption): Add parameter s2k_count. + +2011-06-01 Marcus Brinkmann <mb@g10code.com> + + * cvt-openpgp.c (convert_to_openpgp): Change type of N to unsigned + int. + +2011-04-26 Werner Koch <wk@g10code.com> + + * cvt-openpgp.c (convert_to_openpgp): Use rfc4880 encoded S2K count. + * protect.c (get_standard_s2k_count_rfc4880): New. + (S2K_DECODE_COUNT): New. + (s2k_hash_passphrase): Use the new macro. + +2011-04-21 Werner Koch <wk@g10code.com> + + * agent.h (server_control_s): Add field cache_ttl_opt_preset. + * gpg-agent.c (agent_init_default_ctrl): Init this field. + * genkey.c (agent_genkey): Use this new variable. + * command.c (cmd_passwd): Ditto. + (option_handler): Add new option cache-ttl-opt-preset. + +2011-04-20 Marcus Brinkmann <mb@g10code.com> + + * command.c (cmd_import_key): Release key from failed import + before converting openpgp private key in the openpgp-private-key + case. + +2011-04-17 Ben Kibbey <bjk@luxsci.net> + + * command.c (cmd_passwd): Check for an error before presetting. + +2011-04-12 Ben Kibbey <bjk@luxsci.net> + + * command.c (cmd_passwd): Fixed --preset when not previously cached. + +2011-04-12 Werner Koch <wk@g10code.com> + + * agent.h (CACHE_TTL_NONCE, CACHE_TTL_OPT_PRESET): New. + * command.c (cmd_passwd, cmd_import_key): Use new macros. + * genkey.c (agent_genkey): Ditto. + +2011-04-10 Ben Kibbey <bjk@luxsci.net> + + * command.c (cmd_passwd): Add option --preset. + * command.c (cmd_genkey): Add option --preset. + * genkey.c (agent_genkey): Add parameter preset. + +2011-04-06 Ben Kibbey <bjk@luxsci.net> + + * command.c (do_one_keyinfo): Add protection type field. + +2011-03-10 Werner Koch <wk@g10code.com> + + * protect.c (hash_passphrase): Use the new gcry_kdf_derive. + +2011-03-08 Werner Koch <wk@g10code.com> + + * cvt-openpgp.c (GCRY_PK_ECDH) [!HAVE_GCRY_PK_ECDH]: Remove. + +2011-03-03 Ben Kibbey <bjk@luxsci.net> + + * command.c (cmd_preset_passphrase): Add option --inquire. + +2011-03-03 Werner Koch <wk@g10code.com> + + * gpg-agent.c: Add option --allow-loopback-pinentry. + * command.c (option_handler): Add option pinentry-mode. + * agent.h (pinentry_mode_t): New enum. + (struct server_local_s): Add PINENTRY_MODE. + (struct opt): Add ALLOW_LOOPBACK_PINENTRY. + * call-pinentry.c (agent_askpin): Implement ask, cancel and error + pinentry modes. + (agent_get_passphrase, agent_get_confirmation): Ditto. + (agent_show_message): Return cancel if pinentry mode is not "ask". + (agent_popup_message_start): Ditto. + +2011-03-02 Werner Koch <wk@g10code.com> + + * call-scd.c (hash_algo_option): New. + (agent_card_pksign): Use it with PKSIGN. + +2011-03-02 Ben Kibbey <bjk@luxsci.net> (wk) + + * command.c (cmd_clear_passphrase): Add option --mode=normal. + (cmd_keyinfo): Add option --data. + (do_one_keyinfo): Return CACHED status. Add arg DATA. + +2011-02-07 Werner Koch <wk@g10code.com> + + * pksign.c (do_encode_dsa): Enforce multipe of 8 bits only for DSA. + +2011-02-03 Werner Koch <wk@g10code.com> + + * protect.c (protect_info): Support ECC algos. + + * pksign.c (do_encode_dsa): Map public key algo number. Extend + DSA size check for ECDSA. + + * gpg-agent.c: Include cipher.h. + (map_pk_openpgp_to_gcry): New. + + * findkey.c (key_parms_from_sexp): Support ECDH. + + * cvt-openpgp.c (get_keygrip): Support ECC algorithms. + (convert_secret_key): Ditto. + (do_unprotect): Ditto. + +2011-02-02 Werner Koch <wk@g10code.com> + + * cvt-openpgp.c (convert_secret_key): Remove algo mapping. + +2011-01-31 Werner Koch <wk@g10code.com> + + * cvt-openpgp.c (convert_to_openpgp): Adjust to reverted Libgcrypt + ABI. + + * protect.c (protect_info): Adjust ECDSA and ECDH parameter names. + Add "ecc". + * findkey.c (key_parms_from_sexp): Ditto. + +2011-01-19 Werner Koch <wk@g10code.com> + + * trustlist.c (read_one_trustfile): Also chop an CR. + +2011-01-21 Werner Koch <wk@g10code.com> + + * pksign.c (do_encode_dsa): Compare MDLEN to bytes. + + * cvt-openpgp.c (GCRY_PK_ECDH) [!HAVE_GCRY_PK_ECDH]: New. + +2010-12-02 Werner Koch <wk@g10code.com> + + * gpg-agent.c (CHECK_OWN_SOCKET_INTERVAL) [W32CE]: Set to 60 + seconds. + +2010-11-29 Werner Koch <wk@g10code.com> + + * cache.c (initialize_module_cache): Factor code out to ... + (init_encryption): new. + (new_data, agent_get_cache): Init encryption on on the fly. + +2010-11-26 Werner Koch <wk@g10code.com> + + * gpg-agent.c (CHECK_OWN_SOCKET_INTERVAL): New. + (handle_tick) [W32CE]: Don't check own socket. + +2010-11-23 Werner Koch <wk@g10code.com> + + * Makefile.am (gpg_agent_LDFLAGS): Add extra_bin_ldflags. + +2010-11-11 Werner Koch <wk@g10code.com> + + * agent.h (opt): Add field SIGUSR2_ENABLED. + * gpg-agent.c (handle_connections): Set that flag. + * call-scd.c (start_scd): Enable events depending on this flag. + +2010-10-27 Werner Koch <wk@g10code.com> + + * gpg-agent.c (create_socket_name): Use TMPDIR. Change callers. + +2010-10-26 Werner Koch <wk@g10code.com> + + * cache.c (agent_put_cache): Allow deletion even if TTL is passwd + as 0. + + * genkey.c (agent_protect_and_store): Add arg PASSPHRASE_ADDR. + * command.c (cmd_passwd): Add option --passwd-nonce. + (struct server_local_s): Add LAST_CACHE_NONCE and LAST_PASSWD_NONCE. + (clear_nonce_cache): New. + (reset_notify): Clear the nonce cache. + (start_command_handler): Ditto. + +2010-10-25 Werner Koch <wk@g10code.com> + + * command.c (cmd_export_key): Free CACHE_NONCE. + (cmd_passwd): Add option --cache-nonce. + +2010-10-18 Werner Koch <wk@g10code.com> + + * call-pinentry.c (start_pinentry): Print name of pinentry on + connect error. + + * call-scd.c (agent_card_pksign): Make sure to return an unsigned + number. + +2010-10-14 Werner Koch <wk@g10code.com> + + * command.c (cmd_genkey): Add option --no-protection. + * genkey.c (agent_genkey): Add arg NO_PROTECTION. + +2010-10-13 Werner Koch <wk@g10code.com> + + * call-pinentry.c (agent_get_passphrase): Support the close_button. + + * gpg-agent.c (create_server_socket): Switch back to stderr + logging if we are not starting a agent. + + * command.c (cmd_passwd, cmd_export_key): Move mapping of + GPG_ERR_FULLY_CANCELED to .. + (leave_cmd): .. here. + (option_handler): Add option agent-awareness. + * protect-tool.c (get_passphrase): Take care of + GPG_ERR_FULLY_CANCELED. + * findkey.c (try_unprotect_cb): Ditto. + (unprotect): Remove the fully_canceled hack. + * call-pinentry.c (start_pinentry): Ditto. + (agent_askpin): Ditto. + * pkdecrypt.c (agent_pkdecrypt): Ditto + * pksign.c (agent_pksign_do): Ditto. + * genkey.c (agent_ask_new_passphrase): Remove arg CANCEL_ALL. + +2010-10-06 Werner Koch <wk@g10code.com> + + * cvt-openpgp.c (convert_secret_key): Add missing break. + +2010-10-05 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): Don't set SSH_AGENT_PID so that ssh-agent -k + won't kill out gpg-agent. + +2010-09-30 Werner Koch <wk@g10code.com> + + * gpg-agent.c (agent_exit): Run cleanup. + (cleanup): Run only once. + + * call-pinentry.c (close_button_status_cb): New. + (agent_askpin): Add arg R_CANCEL_ALL. Change all callers. + * genkey.c (agent_ask_new_passphrase): Ditto. + * findkey.c (unprotect): Return GPG_ERR_FULLY_CANCELED if needed. + + * command.c (cmd_export_key): Add support for OpenPGP keys. + * findkey.c (unprotect): Add optional arg R_PASSPHRASE. + (agent_key_from_file): Ditto. Change all callers. + + * findkey.c (unprotect): Do not put the passphrase into the cache + if it has been changed. + + * cvt-openpgp.c (convert_to_openpgp, apply_protection) + (key_from_sexp): New. + +2010-09-29 Werner Koch <wk@g10code.com> + + * cvt-openpgp.c (convert_openpgp): Rename to convert_from_openpgp. + + * command.c (has_option): Stop at "--". + (has_option_name, option_value): Ditto. + (skip_options): Skip initial spaces. + +2010-09-24 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main, reread_configuration): Always test whether + the default configuration file has been created in the meantime. + Fixes bug#1285. + +2010-09-17 Werner Koch <wk@g10code.com> + + * command.c (cmd_havekey): Allow testing of several keygrips. + +2010-09-15 Werner Koch <wk@g10code.com> + + * protect.c (calculate_mic): Take care of shared secret format. + + * agent.h (PROTECTED_SHARED_SECRET): New. + +2010-09-02 Werner Koch <wk@g10code.com> + + * cache.c (new_data): Change arg and callers to use a string and + explicity return an error code. We never used raw binary data and + thus it is easier to use a string. Adjust callers. + (initialize_module_cache, deinitialize_module_cache): New. + (new_data): Encrypt the cached data. + (struct cache_item_s): Remove field LOCKCOUNT. Change all users + accordingly. + (agent_unlock_cache_entry): Remove. + (agent_get_cache): Return an allocated string and remove CACHE_ID. + * genkey.c (agent_genkey): Remove cache marker stuff. + * findkey.c (unprotect): Ditto. + * cvt-openpgp.c (convert_openpgp): Ditto. + * command.c (cmd_get_passphrase): Ditto. + * gpg-agent.c (main, cleanup): Initialize and deinitialize the + cache module. + +2010-09-01 Werner Koch <wk@g10code.com> + + * call-pinentry.c (start_pinentry): Disable pinentry logging. + + * command.c (cmd_import_key, cmd_genkey, cmd_pksign): Add CACHE + handling. + * cvt-openpgp.c (convert_openpgp): Add arg CACHE_NONCE and try the + cached nonce first. + * genkey.c (agent_genkey): Add arg CACHE_NONCE. + * cache.c (agent_get_cache): Require user and nonce cache modes + to match the requested mode. + (agent_put_cache): Ditto. + * agent.h (CACHE_MODE_NONCE): New. + * pksign.c (agent_pksign_do, agent_pksign): Add arg CACHE_NONCE. + * findkey.c (agent_key_from_file): Ditto. + (unprotect): Implement it. + +2010-08-31 Werner Koch <wk@g10code.com> + + * pksign.c (do_encode_dsa): Fix sign problem. + * findkey.c (agent_is_dsa_key): Adjust to actual usage. + +2010-08-30 Werner Koch <wk@g10code.com> + + * protect.c (s2k_hash_passphrase): New public function. + +2010-08-27 Werner Koch <wk@g10code.com> + + * command.c (cmd_import_key): Support OpenPGP keys. + * cvt-openpgp.h, cvt-openpgp.c: New. Some of the code is based on + code taken from g10/seckey-cert.c. + +2010-08-26 Werner Koch <wk@g10code.com> + + * command-ssh.c (open_control_file): Use estream to create the file. + + * findkey.c (agent_write_private_key): Explicitly create file with + mode 600. + * gpg-agent.c (main): Ditto. + * trustlist.c (agent_marktrusted): Explicitly create file with + mode 640. + +2010-08-16 Werner Koch <wk@g10code.com> + + * gpg-agent.c: Replace remaining printf by es_printf. + +2010-08-11 Werner Koch <wk@g10code.com> + + * call-pinentry.c (agent_get_passphrase, agent_askpin): Fix + setting of confidential flag. + + * call-scd.c (agent_card_scd): Pass assuan comment lines to the + caller. + (ASSUAN_CONVEY_COMMENTS): Provide replacement if needed. + +2010-08-09 Werner Koch <wk@g10code.com> + + * Makefile.am (t_common_ldadd): Add NETLIBS for sake of the TCP + logging. + +2010-06-24 Werner Koch <wk@g10code.com> + + * genkey.c (check_passphrase_pattern): Use HANG option for + gnupg_wait_progress. Fixes regression from 2010-06-09. + +2010-06-21 Werner Koch <wk@g10code.com> + + * protect-tool.c (export_p12_file, import_p12_cert_cb) + (import_p12_file, sexp_to_kparms, store_private_key): Remove + unused code. + +2010-06-18 Werner Koch <wk@g10code.com> + + * protect-tool.c (store_private_key, rsa_key_check): Remove. + + * command.c (cmd_export_key): New. + +2010-06-15 Werner Koch <wk@g10code.com> + + * command.c (cmd_keywrap_key, cmd_import_key): New. + + * genkey.c (agent_genkey, agent_protect_and_store): Factor common + code out to... + (agent_ask_new_passphrase): .. new. + + * findkey.c (agent_write_private_key): Return GPG_ERR_EEXIST + instead of GPG_ERR_GENERAL. + +2010-06-14 Werner Koch <wk@g10code.com> + + * protect-tool.c: Remove commands --p12-import and --p12-export. + * minip12.c, minip12.h: Move to ../sm. + * Makefile.am (gpg_protect_tool_SOURCES): Remove them. + * preset-passphrase.c: Remove unneeded minip12.h. + + * command.c (cmd_keywrap_key): New. + + * command.c (leave_cmd): New. + (cmd_istrusted, cmd_listtrusted, cmd_marktrusted, cmd_pksign) + (cmd_pkdecrypt, cmd_genkey, cmd_readkey, cmd_keyinfo) + (cmd_get_passphrase, cmd_get_confirmation, cmd_learn) + (cmd_passwd, cmd_preset_passphrase, cmd_getval, cmd_putval): Use it. + +2010-05-12 Werner Koch <wk@g10code.com> + + * preset-passphrase.c (forget_passphrase): Actually implement + this. Fixes bug#1198. + +2010-05-11 Werner Koch <wk@g10code.com> + + * agent.h (opt): Add field USE_STANDARD_SOCKET. + * gpg-agent.c (use_standard_socket): Remove. Use new option instead. + + * command.c (cmd_killagent, cmd_reloadagent): Provide command also + for non-W32 platforms. + (cmd_getinfo): New subcommands std_session_env and std_startup_env. + +2010-05-03 Werner Koch <wk@g10code.com> + + * gpg-agent.c (check_own_socket_thread): Do not release SOCKNAME + too early. + +2010-04-30 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): Add command --use-standard-socket-p. + +2010-04-26 Werner Koch <wk@g10code.com> + + * gpg-agent.c (create_server_socket) [W32]: Also check for EEXIST. + +2010-04-19 Werner Koch <wk@g10code.com> + + * pksign.c (get_dsa_qbits, do_encode_dsa): New. + (agent_pksign_do): Detect DSA keys and use do_encode_dsa. + * findkey.c (agent_public_key_from_file): Factor some code out to .. + (key_parms_from_sexp): New. + (agent_is_dsa_key): New. + + * command.c (cmd_sethash): Clear digeest.RAW_VALUE. + +2010-04-14 Werner Koch <wk@g10code.com> + + * Makefile.am (libexec_PROGRAMS) [W32CE]: Do not build + gpg-preset-passphrase for now. + (pwquery_libs) [W32CE]: Set to empty. + + * trustlist.c (read_one_trustfile): Use estream. + +2010-04-13 Werner Koch <wk@g10code.com> + + * findkey.c (read_key_file): Use estream. + (agent_write_private_key): Ditto. + +2010-04-07 Werner Koch <wk@g10code.com> + + * gpg-agent.c (handle_connections) [W32]: Assume that PTh support + the handle event. Use a dummy event for W32CE. + (get_agent_scd_notify_event) [W32CE]: Do not build. + + * call-pinentry.c: Remove setenv.h. Include sysutils.h. + (atfork_cb): s/setenv/gnupg_setenv/. + + * gpg-agent.c: Do not include setenv.h. + (main): s/unsetenv/gnupg_unsetenv/. + + * protect.c (calibrate_get_time) [W32CE]: Use GetThreadTimes. + +2010-04-06 Werner Koch <wk@g10code.com> + + * call-scd.c [!HAVE_SIGNAL_H]: Do not include signal.h. + + * findkey.c (agent_write_private_key): s/remove/gnupg_remove/. + + * command-ssh.c (search_control_file): Replace rewind by fseek and + clearerr. + * genkey.c (check_passphrase_pattern): Ditto. + + * gpg-agent.c [!HAVE_SIGNAL_H]: Do not include signal.h. + (remove_socket): s/remove/gnupg_remove/. + (create_private_keys_directory): Use gnupg_mkdir. + +2010-03-11 Werner Koch <wk@g10code.com> + + * gpg-agent.c: Include "asshelp.h". + (main): Remove assuan_set_assuan_log_prefix. Add + assuan_set_log_cb. + (handle_signal): Disable pth ctrl dumping. + (parse_rereadable_options, main): Remove assuan_set_assuan_log_stream. + * call-scd.c (start_scd): Remove assuan_set_log_stream. + +2010-03-10 Werner Koch <wk@g10code.com> + + * Makefile.am (common_libs): Remove libjnlib.a. + + * trustlist.c, protect-tool.c, command-ssh.c: Remove estream.h. + +2010-02-17 Werner Koch <wk@g10code.com> + + * call-pinentry.c (start_pinentry): Always free OPTSTR. Send + default-xxx strings. + +2010-01-26 Werner Koch <wk@g10code.com> + + * protect.c (do_encryption): Encode the s2kcount and no not use a + static value of 96. + +2009-12-21 Werner Koch <wk@g10code.com> + + * command.c (cmd_getinfo): Add sub-command s2k_count. + +2009-12-14 Werner Koch <wk@g10code.com> + + * protect.c (agent_unprotect): Decode the S2K count here and take + care of the new unencoded values. Add a lower limit sanity check. + (hash_passphrase): Do not decode here. + (get_standard_s2k_count, calibrate_s2k_count): New. + (calibrate_get_time, calibrate_elapsed_time): New. + (do_encryption): Use get_standard_s2k_count. + +2009-12-08 Werner Koch <wk@g10code.com> + + * protect.c (agent_unprotect): Avoid compiler warning. + +2009-12-08 Marcus Brinkmann <marcus@g10code.de> + + * call-pinentry.c (start_pinentry): Convert posix fd to assuan fd. + * call-scd.c (start_scd): Likewise. + +2009-12-03 Werner Koch <wk@g10code.com> + + * gpg-agent.c (set_debug): Allow for numerical debug leveles. Print + active debug flags. + +2009-12-02 Werner Koch <wk@g10code.com> + + * trustlist.c (read_trustfiles): Store the pointer returned from + shrinking the memory and not the orginal one. Fixes bug#1163. + Reported by TAKAHASHI Tamotsu. Also return correct error after + memory failure. + +2009-11-27 Marcus Brinkmann <marcus@g10code.de> + + * command.c (start_command_handler): Do not call + assuan_set_log_stream anymore. + * gpg-agent.c (main): But call assuan_set_assuan_log_stream here. + +2009-11-25 Marcus Brinkmann <marcus@g10code.de> + + * command.c (start_command_handler): Use assuan_fd_t and + assuan_fdopen on fds. + +2009-11-05 Marcus Brinkmann <marcus@g10code.de> + + * call-pinentry.c (start_pinentry): Call assuan_pipe_connect, not + assuan_pipe_connect_ext. + * command.c (start_command_handler): Change + assuan_init_socket_server_ext into assuan_init_socket_server. + * call-scd.c (start_scd): Update use of assuan_socket_connect and + assuan_pipe_connect. + * gpg-agent.c (check_own_socket_thread, check_for_running_agent): + Update use of assuan_socket_connect. + +2009-11-04 Werner Koch <wk@g10code.com> + + * command.c (register_commands): Add help arg to + assuan_register_command. Convert all command comments to help + strings. + +2009-11-02 Marcus Brinkmann <marcus@g10code.de> + + * command.c (reset_notify): Take LINE arg and return error. + (register_commands): Use assuan_handler_t type. + +2009-10-16 Marcus Brinkmann <marcus@g10code.com> + + * gpg_agent_CFLAGS, gpg_agent_LDADD: Use libassuan instead of + libassuan-pth. + * gpg-agent.c: Invoke ASSUAN_SYSTEM_PTH_IMPL. + (main): Call assuan_set_system_hooks and assuan_sock_init. + Fix invocation of assuan_socket_connect. + +2009-09-23 Werner Koch <wk@g10code.com> + + * command.c (register_commands) [HAVE_ASSUAN_SET_IO_MONITOR]: + Remove cpp condition. + (start_command_handler) [HAVE_ASSUAN_SET_IO_MONITOR]: Ditto. + +2009-09-23 Marcus Brinkmann <marcus@g10code.de> + + * gpg-agent.c (parse_rereadable_options): Don't set global assuan + log file (there ain't one anymore). + (main): Update to new API. + (check_own_socket_pid_cb): Return gpg_error_t instead of int. + (check_own_socket_thread, check_for_running_agent): Create assuan + context before connecting to server. + * command.c: Include "scdaemon.h" before <assuan.h> because of + GPG_ERR_SOURCE_DEFAULT check. + (write_and_clear_outbuf): Use gpg_error_t instead of + assuan_error_t. + (cmd_geteventcounter, cmd_istrusted, cmd_listtrusted) + (cmd_marktrusted, cmd_havekey, cmd_sigkey, cmd_setkeydesc) + (cmd_sethash, cmd_pksign, cmd_pkdecrypt, cmd_genkey, cmd_readkey) + (cmd_keyinfo, cmd_get_passphrase, cmd_clear_passphrase) + (cmd_get_confirmation, cmd_learn, cmd_passwd) + (cmd_preset_passphrase, cmd_scd, cmd_getval, cmd_putval) + (cmd_updatestartuptty, cmd_killagent, cmd_reloadagent) + (cmd_getinfo, option_handler): Return gpg_error_t instead of int. + (post_cmd_notify): Change type of ERR to gpg_error_t from int. + (io_monitor): Add hook argument. Use symbols for constants. + (register_commands): Change return type of HANDLER to gpg_error_t. + (start_command_handler): Allocate assuan context before starting + server. + * call-pinentry.c: Include "scdaemon.h" before <assuan.h> because + of GPG_ERR_SOURCE_DEFAULT check. + (unlock_pinentry): Call assuan_release instead of + assuan_disconnect. + (getinfo_pid_cb, getpin_cb): Return gpg_error_t instead of int. + (start_pinentry): Allocate assuan context before connecting to + server. + * call-scd.c (membuf_data_cb, learn_status_cb, get_serialno_cb) + (membuf_data_cb, inq_needpin, card_getattr_cb, pass_status_thru) + (pass_data_thru): Change return type to gpg_error_t. + (start_scd): Allocate assuan context before connecting to server. + +2009-09-04 Marcus Brinkmann <marcus@g10code.com> + + * command.c (start_command_handler): Add comment about gap in + implementation (in dead code), for future reference. + +2009-08-11 Werner Koch <wk@g10code.com> + + * divert-scd.c (ask_for_card): I18n a prompt string. + +2009-07-06 Werner Koch <wk@g10code.com> + + * agent.h: Include session-env.h. + (opt): Replace most of the startup_xxx fields by a session_env_t. + (struct server_control_s): Likewise. + * gpg-agent.c (main): Rewrite setting of the startup fields. + (handle_connections, main): Allocate SESSION_ENV. + (agent_init_default_ctrl, agent_deinit_default_ctrl): Change + accordingly. + * command.c (option_handler): Ditto. + (cmd_updatestartuptty): Change accordingly. Protect old values + from out of core failures. + * command-ssh.c (start_command_handler_ssh): Ditto. + (start_command_handler_ssh): Replace strdup by xtrystrdup. + * call-pinentry.c (atfork_cb): Pass new envrinmnet variables. + (start_pinentry): Use session_env stuff. + * protect-tool.c (main): Adjust call to gnupg_prepare_get_passphrase. + +2009-06-24 Werner Koch <wk@g10code.com> + + * genkey.c (agent_protect_and_store): Return RC and not 0. + * protect.c (do_encryption): Fix ignored error code from malloc. + Reported by Fabian Keil. + +2009-06-17 Werner Koch <wk@g10code.com> + + * call-pinentry.c (agent_get_confirmation): Add arg WITH_CANCEL. + Change all callers. + * trustlist.c (agent_marktrusted): Use WITH_CANCEL + +2009-06-09 Werner Koch <wk@g10code.com> + + * learncard.c (send_cert_back): Ignore certain error codes. + +2009-06-05 Werner Koch <wk@g10code.com> + + * protect-tool.c (store_private_key): Fix last change by appending + a ".key". + +2009-06-03 Werner Koch <wk@g10code.com> + + * protect-tool.c: Include estream.h. + (store_private_key): Replace stdio streams by estream functions + for a portable use of the "x" mode. + * trustlist.c: Include estream.h. + (agent_marktrusted): Replace stdio stream by estream functions. + + * protect-tool.c (store_private_key): Use bin2hex. + +2009-06-02 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): Run pth_kill after fork. Fixes bug#1066. + +2009-05-19 Werner Koch <wk@g10code.com> + + * gpg-agent.c (JNLIB_NEED_AFLOCAL): Define. + (create_server_socket): Use SUN_LEN macro. + +2009-05-15 Werner Koch <wk@g10code.com> + + Fix bug #1053. + + * agent.h (lookup_ttl_t): New. + * findkey.c (unprotect): Add arg LOOKUP_TTL. + (agent_key_from_file): Ditto. + * pksign.c (agent_pksign_do): Ditto. + * command-ssh.c (ttl_from_sshcontrol): New. + (data_sign): Pass new function to agent_pksign_do. + (search_control_file): Add new arg R_TTL. + +2009-05-14 Werner Koch <wk@g10code.com> + + * command.c (cmd_get_passphrase): Add option --qualitybar. + * call-pinentry.c (agent_askpin): Factor some code out to ... + (setup_qualitybar): .. new. + (agent_get_passphrase): Add arg WITH_QUALITYBAR and implement it. + +2009-04-14 Marcus Brinkmann <marcus@g10code.de> + + * call-pinentry.c (agent_get_confirmation): Try SETNOTOK command + with pinentry. + +2009-04-01 Werner Koch <wk@g10code.com> + + * protect-tool.c (pe_opt): New. + (opts): Add option --agent-program. Use ARGPARSE macros. + (get_new_passphrase): Remove. + (get_passphrase): Use gpg-agent directly. Remove arg OPT_CHECK and + change all callers. + * Makefile.am (gpg_protect_tool_LDADD): Replace pwquery_libs by + LIBASSUAN_LIBS. + (gpg_protect_tool_CFLAGS): New. + + * command.c (percent_plus_unescape): Remove. + (cmd_putval): Use percent_plus_unescape_inplace. + * call-scd.c (unescape_status_string): Remove. + (card_getattr_cb): Use percent_plus_unescape. + * protect-tool.c (main): Use percent_plus_unescape from common/. + (percent_plus_unescape, percent_plus_unescape_string): Remove. + +2009-03-27 Werner Koch <wk@g10code.com> + + * learncard.c (agent_handle_learn): Add new certtype 111. + +2009-03-26 Werner Koch <wk@g10code.com> + + * agent.h (MAX_DIGEST_LEN): Change to 64. + * command.c (cmd_sethash): Allow digest length of 48 and 64. + (cmd_sethash): Allow more hash algos. + + * trustlist.c (reformat_name): New. + (agent_marktrusted): Use a reformatted name. Reload the table + before the update and always reload it at the end. + (agent_istrusted): Check early for the disabled flag. + +2009-03-25 Werner Koch <wk@g10code.com> + + * pkdecrypt.c (agent_pkdecrypt): Return a specific error message + if the key is not available. + + * gpg-agent.c (main): Print a started message to show the real pid. + +2009-03-20 Werner Koch <wk@g10code.com> + + * learncard.c (struct kpinfo_cp_parm_s): Add field CTRL. + (struct certinfo_cb_parm_s): Ditto. + (agent_handle_learn): Set CTRL field. + (kpinfo_cb, certinfo_cb): Send progress status. + + * agent.h (agent_write_status): Flag with GNUPG_GCC_A_SENTINEL. + +2009-03-19 Werner Koch <wk@g10code.com> + + * trustlist.c (struct trustitem_s): Add field DISABLED. + (read_one_trustfile): Parse the '!' flag. + (agent_istrusted, agent_listtrusted): Check flag. + (agent_istrusted): Add arg R_DISABLED. Change all callers. + (agent_marktrusted): Do not ask if flagged as disabled. Reverse + the order of the questions. Store the disabled flag. + + * gpg-agent.c (main): Save signal mask and open fds. Restore mask + and close all fds prior to the exec. Fixes bug#1013. + +2009-03-17 Werner Koch <wk@g10code.com> + + * command.c (cmd_get_passphrase): Break repeat loop on error. + Show error message. + (cmd_getinfo): Add subcommand "cmd_has_option". + (command_has_option): New. + +2009-03-17 Daiki Ueno <ueno@unixuser.org> + + * command.c (option_value): New function. + (cmd_get_passphrase): Accept new option --repeat, which makes + gpg-agent to ask passphrase several times. + +2009-03-06 Werner Koch <wk@g10code.com> + + * command.c (cmd_keyinfo): New command. + (register_commands): Register it. + (agent_write_status): Make sure not to print LR or CR. + * divert-scd.c (ask_for_card): Factor shadow info parsing out to ... + * protect.c (parse_shadow_info): New. + * findkey.c (agent_key_from_file): Use make_canon_sexp. + (agent_write_private_key, unprotect, read_key_file) + (agent_key_available): Use bin2hex. + (agent_key_info_from_file): New. + (read_key_file): Log no error message for ENOENT. + +2009-03-05 Werner Koch <wk@g10code.com> + + * divert-scd.c (getpin_cb): Support flag 'P'. Change max_digits + from 8 to 16. Append a message about keypads. + * findkey.c (unprotect): Change max digits to 16. + +2009-03-02 Werner Koch <wk@g10code.com> + + * command.c (cmd_getinfo): Add subcommand "scd_running". + + * call-scd.c (agent_scd_check_running): New. + + * gpg-agent.c: Add missing option strings for "--batch" and + "--homedir". Reported by Petr Uzel. + + * protect-tool.c (import_p12_file): Take care of canceled + passphrase entry. Fixes bug#1003. + (export_p12_file): Ditto. + +2008-12-17 Werner Koch <wk@g10code.com> + + * gpg-agent.c (handle_connections): Set action of all pth event + handled signals to SIG_IGN. Use a different pth_sigmask strategy. + +2008-12-10 Werner Koch <wk@g10code.com> + + * command.c (cmd_get_passphrase): Implement option --no-ask. + +2008-12-09 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): Call i18n_init before init_common_subsystems. + * preset-passphrase.c (main): Ditto. + * protect-tool.c (main): Ditto. + + * command.c (cmd_preset_passphrase): Allow an arbitrary string for + the cache id. + +2008-12-08 Werner Koch <wk@g10code.com> + + * gpg-agent.c (handle_connections): Sync the ticker to the next + full second. This is bug#871. + +2008-12-05 Werner Koch <wk@g10code.com> + + * minip12.c (decrypt_block): Fix const modified of CHARSETS. + * learncard.c (sinfo_cb_parm_s): Remove superflous semicolon. + Reported by Stoyan Angelov. + +2008-11-18 Werner Koch <wk@g10code.com> + + * gpg-agent.c (make_libversion): New. + (my_strusage): Print libgcrypt version + +2008-11-11 Werner Koch <wk@g10code.com> + + * call-scd.c (membuf_data_cb): Change return type to + assuan_error_t to avoid warnings with newer libassuan versions. + +2008-11-04 Werner Koch <wk@g10code.com> + + * command.c (cmd_killagent): Stop the agent immediately. + (start_command_handler): Take care of GPG_ERR_EOF. + +2008-10-29 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): Move USE_STANDARD_SOCKET to the outer scope. + (create_socket_name): Remove arg USE_STANDARD_SOCKET. Change all + callers. + (create_server_socket): Remove IS_STANDARD_NAME and replace it by + USE_STANDARD_SOCKET. Change all callers. + (check_own_socket_running): New. + (check_own_socket, check_own_socket_thread): New. + (handle_tick): Check server socket once a minute. + (handle_connections): Remove the extra pth_wait in the shutdown + case. + +2008-10-20 Werner Koch <wk@g10code.com> + + * command.c (cmd_geteventcounter): Mark unused arg. + (cmd_listtrusted, cmd_pksign, cmd_pkdecrypt, cmd_genkey): Ditto. + (cmd_updatestartuptty, post_cmd_notify): Ditto. + * command-ssh.c (add_control_entry) + (ssh_handler_request_identities, ssh_handler_remove_identity) + (ssh_handler_remove_all_identities, ssh_handler_lock) + (ssh_handler_unlock): Ditto. + * call-pinentry.c (pinentry_active_p, popup_message_thread) + (agent_popup_message_stop): Ditto. + * findkey.c (agent_public_key_from_file): Ditto. + * genkey.c (check_passphrase_pattern): Ditto. + * call-scd.c (atfork_cb): Ditto. + * protect-tool.c (import_p12_cert_cb): Ditto. + * t-protect.c (main): Ditto. + +2008-10-17 Werner Koch <wk@g10code.com> + + * call-scd.c (start_scd) [W32]: Use snprintf again because we now + always use the estream variant. + +2008-10-15 Werner Koch <wk@g10code.com> + + * call-scd.c (start_scd): Enable assuan loggging if requested. + (agent_scd_check_aliveness) [W32]: Fix use of GetExitCodeProcess. + +2008-10-14 Werner Koch <wk@g10code.com> + + * gpg-agent.c (get_agent_scd_notify_event): Need to use a manual + reset event. + +2008-09-29 Werner Koch <wk@g10code.com> + + * agent.h (GCRY_MD_USER): Rename to GCRY_MODULE_ID_USER. + (GCRY_MD_USER_TLS_MD5SHA1): Rename to MD_USER_TLS_MD5SHA1 and + change all users. + +2008-09-25 Werner Koch <wk@g10code.com> + + * divert-scd.c (getpin_cb): Support a Reset Code style PINs.. + +2008-09-03 Werner Koch <wk@g10code.com> + + * command.c (parse_keygrip): Use hex2bin. + (cmd_preset_passphrase): Decode the passphrase. Reported by Kiss + Gabor. Fixes #679 again. + * preset-passphrase.c (make_hexstring): Remove. + (preset_passphrase): Use bin2hex. + +2008-05-27 Werner Koch <wk@g10code.com> + + * trustlist.c (insert_colons): Fix stupidly wrong allocation size + computation. + +2008-05-26 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): Re-initialize default assuan log stream if a + log file is used. + + * trustlist.c (agent_marktrusted): Use xtryasprintf and xfree. + + * gpg-agent.c (main, agent_deinit_default_ctrl): Always use xfree + because our asprintf is mapped to an xmalloc style function in + util.h. Replace xstrdup by xtrystrdup. + * w32main.c (build_argv): Ditto. + * preset-passphrase.c (preset_passphrase): Ditto. + * divert-scd.c (ask_for_card): Ditto. + * command.c (option_handler): Ditto. + * command-ssh.c (ssh_handler_request_identities): Ditto. + * call-pinentry.c (start_pinentry): Ditto. + + * gpg-agent.c (start_connection_thread) + (start_connection_thread_ssh): Use pth_thread_id for useful output + under W32. + (pth_thread_id) [!PTH_HAVE_PTH_THREAD_ID]: New. + +2008-03-17 Werner Koch <wk@g10code.com> + + * agent.h (agent_inq_pinentry_launched): New prototype. + + * call-pinentry.c: Include sys/types.h and signal.h. + +2008-02-14 Werner Koch <wk@g10code.com> + + * command.c (agent_inq_pinentry_launched): New. + (option_handler): Add option allow-pinentry-notify. + * call-pinentry.c (getinfo_pid_cb): New. + (start_pinentry): Ask for the PID and notify the client. + +2008-01-15 Marcus Brinkmann <marcus@g10code.de> + + * call-pinentry.c (start_pinentry): Start pinentry in detached + mode. + +2007-12-04 Werner Koch <wk@g10code.com> + + * call-pinentry.c (agent_askpin): Use gnupg_get_help_string. + +2007-12-03 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): s/standard_socket/use_standard_socket/ for + clarity. + (create_server_socket): New arg IS_SSH to avoid testing with + assuan commands. + +2007-11-20 Werner Koch <wk@g10code.com> + + * gpg-agent.c (get_agent_scd_notify_event): New. + (handle_signal): Factor SIGUSR2 code out to: + (agent_sigusr2_action): .. New. + (agent_sighup_action): Print info message here and not in + handle_signal. + (handle_connections) [PTH_EVENT_HANDLE]: Call agent_sigusr2_action. + + * call-scd.c (agent_scd_check_aliveness) [W32]: Implemented. + (start_scd) [W32]: Send event-signal option. + +2007-11-19 Werner Koch <wk@g10code.com> + + * call-pinentry.c (agent_askpin): Set the tooltip for the quality + bar. + +2007-11-15 Werner Koch <wk@g10code.com> + + * agent.h (struct server_control_s): Add XAUTHORITY and + PINENTRY_USER_DATA. + * gpg-agent.c: New option --xauthority. + (main, agent_init_default_ctrl) + (agent_deinit_default_ctrl): Implemented + * command.c (cmd_updatestartuptty): Ditto. + * command-ssh.c (start_command_handler_ssh): Ditto. + * call-pinentry.c (atfork_cb): Set the environment. + (start_pinentry): Pass CTRL as arg to atfork_cb. + +2007-11-14 Werner Koch <wk@g10code.com> + + * call-scd.c (start_scd) [W32]: Take care of fflush peculiarities. + +2007-11-07 Werner Koch <wk@g10code.com> + + * agent.h: Remove errors.h. + +2007-10-24 Werner Koch <wk@g10code.com> + + * genkey.c (check_passphrase_constraints): Changed the wording of + the warning messages. + +2007-10-19 Werner Koch <wk@g10code.com> + + * protect-tool.c (get_passphrase): Use new utf8 switch fucntions. + +2007-10-15 Daiki Ueno <ueno@unixuser.org> (wk) + + * command-ssh.c (reenter_compare_cb): New function; imported from + genkey.c. + (ssh_identity_register): Ask initial passphrase twice. + +2007-10-02 Werner Koch <wk@g10code.com> + + * command.c (cmd_getinfo): Add "pid" subcommand. + +2007-10-01 Werner Koch <wk@g10code.com> + + * agent.h (struct server_control_s): Remove unused CONNECTION_FD. + + * gpg-agent.c: Remove w32-afunix.h. Include mkdtemp.h. + (socket_nonce, socket_nonce_ssh): New. + (create_server_socket): Use assuan socket wrappers. Remove W32 + specific stuff. Save the server nonce. + (check_nonce): New. + (start_connection_thread, start_connection_thread_ssh): Call it. + (handle_connections): Change args to gnupg_fd_t. + * command.c (start_command_handler): Change LISTEN_FD to gnupg_fd_t. + * command-ssh.c (start_command_handler_ssh): Ditto. + +2007-09-18 Werner Koch <wk@g10code.com> + + * agent.h (struct pin_entry_info_s): Add element WITH_QUALITYBAR. + * genkey.c (check_passphrase_constraints): New arg SILENT. + Changed all callers. + (agent_protect_and_store, agent_genkey): Enable qualitybar. + * call-pinentry.c (agent_askpin): Send that option. + (unescape_passphrase_string): New. + (inq_quality): New. + (estimate_passphrase_quality): New. + +2007-09-14 Marcus Brinkmann <marcus@g10code.de> + + * call-pinentry.c (agent_popup_message_stop): Implement kill for + Windows. + +2007-08-28 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): Add option --faked-system-time. + + * protect-tool.c (read_and_unprotect): Print the protected-at date. + + * agent.h (struct server_control_s): Add member IN_PASSWD. + * command.c (cmd_passwd): Set it. + * findkey.c (try_unprotect_cb): Use it. + + * protect.c (do_encryption): Replace asprintf by xtryasprint. + (agent_protect): Create the protected-at item. + (agent_unprotect): Add optional arg PROTECTED_AT. + (merge_lists): Add args CUTOFF and CUTLEN. + (agent_unprotect): Use them. + * findkey.c (try_unprotect_cb): Add code to test for expired keys. + (unprotect): Allow changing the passphrase. + +2007-08-27 Werner Koch <wk@g10code.com> + + * gpg-agent.c: Add options --min-passphrase-nonalpha, + --check-passphrase-pattern and --enforce-passphrase-constraints. + (MIN_PASSPHRASE_NONALPHA): Init nonalpha option to 1. + (main): Declare options for gpgconf. + * agent.h (struct): Add members MIN_PASSPHRASE_NONALPHA, + ENFORCE_PASSPHRASE_CONSTRAINTS and CHECK_PASSPHRASE_PATTERN. + * genkey.c (nonalpha_charcount): New. + (check_passphrase_pattern): New. + (check_passphrase_constraints): Implement. Factor some code out... + (take_this_one_anyway, take_this_one_anyway2): .. New. + + * call-pinentry.c (agent_show_message): New. + (agent_askpin): We better reset the pin buffer before asking. + + * trustlist.c (insert_colons): New. + (agent_marktrusted): Pretty print the fpr. + +2007-08-22 Werner Koch <wk@g10code.com> + + * findkey.c (O_BINARY): Make sure it is defined. + (agent_write_private_key): Use O_BINARY + + * protect-tool.c (import_p12_file): Add hack to allow importing of + gnupg 2.0.4 generated files. + +2007-08-06 Werner Koch <wk@g10code.com> + + * trustlist.c (read_one_trustfile): Add flag "cm". + (agent_istrusted): Ditto. + +2007-08-02 Werner Koch <wk@g10code.com> + + * gpg-agent.c: Include gc-opt-flags.h and remove their definition + here. + +2007-07-13 Werner Koch <wk@g10code.com> + + * genkey.c (check_passphrase_constraints): Require a confirmation + for an empty passphrase. + (agent_genkey, agent_protect_and_store): No need to repeat an + empty passphrase. + +2007-07-05 Werner Koch <wk@g10code.com> + + * call-scd.c (struct inq_needpin_s): New. + (inq_needpin): Pass unknown inquiries up. + +2007-07-04 Werner Koch <wk@g10code.com> + + * gpg-agent.c (TIMERTICK_INTERVAL): New. + (fixed_gcry_pth_init, main): Kludge to fix Pth initialization. + +2007-07-03 Werner Koch <wk@g10code.com> + + * gpg-agent.c (handle_connections): Do not use FD_SETSIZE for + select but compute the correct number. + +2007-07-02 Werner Koch <wk@g10code.com> + + * command.c (cmd_reloadagent) [W32]: New. + (register_commands) [W32]: New command RELOADAGENT. + + * Makefile.am (gpg_agent_SOURCES): Remove w32main.c and w32main.h. + (gpg_agent_res_ldflags): Remove icon file as we don't have a + proper icon yet. + * gpg-agent.c (main): do not include w32main.h. Remove all calls + to w32main.c. + (agent_sighup_action): New. + (handle_signal): Use it. + +2007-06-26 Werner Koch <wk@g10code.com> + + * gpg-agent.c (create_directories) [W32]: Made it work. + +2007-06-21 Werner Koch <wk@g10code.com> + + * agent.h (ctrl_t): Remove. It is now declared in ../common/util.h. + + * gpg-agent.c (check_for_running_agent): New arg SILENT. Changed + all callers. + (create_server_socket): If the standard socket is in use check + whether a agent is running and avoid starting another one. + +2007-06-18 Marcus Brinkmann <marcus@g10code.de> + + * gpg-agent.c (main): Percent escape pathname in --gpgconf-list + output. + +2007-06-18 Werner Koch <wk@g10code.com> + + * w32main.c (build_argv): New. + (WinMain): Use it. + + * command.c (cmd_killagent) [W32]: New. + (cmd_getinfo): New. + * gpg-agent.c (get_agent_ssh_socket_name): New. + (no_force_standard_socket) New. + (create_server_socket): Use it. + * Makefile.am (gpg_agent_res_ldflags): Pass windows option to ld. + +2007-06-14 Werner Koch <wk@g10code.com> + + * protect-tool.c (main): Setup default socket name for + simple-pwquery. + (MAP_SPWQ_ERROR_IMPL): New. Use map_spwq_error for spqw related + error codes. + * preset-passphrase.c (main): Setup default socket name for + simple-pwquery. + (map_spwq_error): Remove. + (MAP_SPWQ_ERROR_IMPL): New. + + * call-pinentry.c (start_pinentry): Use gnupg_module_name. + * call-scd.c (start_scd): Ditto. + +2007-06-12 Werner Koch <wk@g10code.com> + + * taskbar.c: New. + + * trustlist.c (read_one_trustfile): Replace GNUPG_SYSCONFDIR by a + function call. + (read_trustfiles): Ditto. + + * gpg-agent.c (main): Replace some calls by init_common_subsystems. + * preset-passphrase.c (main): Ditto. + * protect-tool.c (main): Ditto. + +2007-06-11 Werner Koch <wk@g10code.com> + + * Makefile.am (common_libs): Use libcommonstd macro. + (commonpth_libs): Use libcommonpth macro. + + * protect-tool.c (main) [W32]: Call pth_init. + + * preset-passphrase.c (main) [W32]: Replace the explicit Winsocket + init by a call to pth_init. + + * trustlist.c (initialize_module_trustlist): New. + * gpg-agent.c (main): Call it. + + * call-pinentry.c (initialize_module_query): Rename to + initialize_module_call_pinentry. + + * minip12.c: Remove iconv.h. Add utf8conf.h. Changed all iconv + calss to use these jnlib wrappers. + +2007-06-06 Werner Koch <wk@g10code.com> + + * minip12.c (enum): Rename CONTEXT to ASNCONTEXT as winnt.h + defines such a symbol to access the process context. + + * call-pinentry.c (dump_mutex_state) [W32]: Handle the W32Pth case. + * call-scd.c (dump_mutex_state): Ditto. + + * protect-tool.c (i18n_init): Remove. + * preset-passphrase.c (i18n_init): Remove. + * gpg-agent.c (i18n_init): Remove. + +2007-05-19 Marcus Brinkmann <marcus@g10code.de> + + * protect-tool.c (get_passphrase): Free ORIG_CODESET on error. + +2007-05-14 Werner Koch <wk@g10code.com> + + * protect.c (make_shadow_info): Replace sprintf by smklen. + +2007-04-20 Werner Koch <wk@g10code.com> + + * gpg-agent.c (my_gcry_logger, my_gcry_outofcore_handler): Removed. + (main): Call the setup_libgcrypt_logging helper. + * protect-tool.c (my_gcry_logger): Removed. + (main): Call the setup_libgcrypt_logging helper. + +2007-04-03 Werner Koch <wk@g10code.com> + + * trustlist.c (read_trustfiles): Take a missing trustlist as an + empty one. + +2007-03-20 Werner Koch <wk@g10code.com> + + * protect-tool.c: New option --p12-charset. + * minip12.c (p12_build): Implement it. + +2007-03-19 Werner Koch <wk@g10code.com> + + * minip12.c: Include iconv.h. + (decrypt_block): New. + (parse_bag_encrypted_data, parse_bag_data): Use it here. + (bag_data_p, bag_decrypted_data_p): New helpers. + +2007-03-06 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main) <gpgconf>: Add entries for all ttl options. + +2007-02-20 Werner Koch <wk@g10code.com> + + * call-pinentry.c (start_pinentry): Fix for OS X to allow loading + of the bundle. Tested by Benjamin Donnachie. + +2007-02-14 Werner Koch <wk@g10code.com> + + * gpg-agent.c: New option --pinentry-touch-file. + (get_agent_socket_name): New. + * agent.h (opt): Add pinentry_touch_file. + * call-pinentry.c (start_pinentry): Send new option to the + pinentry. + +2007-01-31 Moritz Schulte <moritz@g10code.com> (wk) + + * command-ssh.c (stream_read_string): Initialize LENGTH to zero. + (start_command_handler_ssh): Use es_fgetc/es_ungetc to check if + EOF has been reached before trying to process another request. + +2007-01-31 Werner Koch <wk@g10code.com> + + * command-ssh.c (start_command_handler_ssh): + + * Makefile.am (t_common_ldadd): Add LIBICONV. + +2007-01-25 Werner Koch <wk@g10code.com> + + * genkey.c (check_passphrase_constraints): Get ngettext call right + and use UTF-8 aware strlen. + + * protect-tool.c (get_passphrase): New arg OPT_CHECK. + (get_new_passphrase): Enable OPT_CHECK on the first call. + * command.c (cmd_get_passphrase): Implement option --check. + +2007-01-24 Werner Koch <wk@g10code.com> + + * gpg-agent.c (MIN_PASSPHRASE_LEN): New + (parse_rereadable_options): New option --min-passphrase-len. + * genkey.c (check_passphrase_constraints): New. + (agent_genkey, agent_protect_and_store): Call new function. Fix + memory leak. + + * call-pinentry.c (agent_askpin): Allow translation of the displayed + error message. + (agent_popup_message_start): Remove arg CANCEL_BTN. + (popup_message_thread): Use --one-button option. + + * command.c (cmd_passwd): Now that we don't distinguish between + assuan and regular error codes we can jump to the end on error. + +2006-12-07 David Shaw <dshaw@jabberwocky.com> + + * Makefile.am: Link to iconv for jnlib dependency. + +2006-11-20 Werner Koch <wk@g10code.com> + + * call-pinentry.c (agent_popup_message_stop): Use SIGKILL. + * call-scd.c (inq_needpin): Implement POPUPKEYPADPROMPT and + DISMISSKEYPADPROMPT. + +2006-11-15 Werner Koch <wk@g10code.com> + + * protect.c (make_shadow_info): Cast printf arg to unsigned int. + * minip12.c (parse_bag_encrypted_data): Ditto. + (parse_bag_data, p12_parse): Ditto. + * command-ssh.c (ssh_identity_register): Changed buffer_n to + size_t. + + * agent.h (struct server_control_s): New field thread_startup. + * command.c (start_command_handler): Moved CTRL init code to .. + * gpg-agent.c (start_connection_thread): .. here. + (agent_deinit_default_ctrl): New. + (agent_init_default_ctrl): Made static. + (handle_connections): Allocate CTRL and pass it pth_spawn. + * command-ssh.c (start_command_handler_ssh): Moved CTRL init code + to .. + * gpg-agent.c (start_connection_thread_ssh): .. here. + +2006-11-14 Werner Koch <wk@g10code.com> + + * command.c (bump_key_eventcounter): New. + (bump_card_eventcounter): New. + (cmd_geteventcounter): New command. + * gpg-agent.c (handle_signal): Call bump_card_eventcounter. + * findkey.c (agent_write_private_key): Call bump_key_eventcounter. + * trustlist.c (agent_reload_trustlist): Ditto. + + * command.c (post_cmd_notify, io_monitor): New. + (register_commands, start_command_handler): Register them. + +2006-11-09 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): In detached mode connect standard + descriptors to /dev/null. + + * trustlist.c (read_trustfiles): Make sure not to pass a zero size + to realloc as the C standards says that this behaves like free. + +2006-11-06 Werner Koch <wk@g10code.com> + + * protect-tool.c (my_strusage): Fixed typo. + +2006-10-23 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): New command --gpgconf-test. + + * minip12.c (parse_bag_encrypted_data, parse_bag_data): Allow for + a salt of 20 bytes. + +2006-10-20 Werner Koch <wk@g10code.com> + + * Makefile.am (t_common_ldadd): Use GPG_ERROR_LIBS instead -o just -l + +2006-10-19 Werner Koch <wk@g10code.com> + + * findkey.c (unprotect): Use it to avoid unnecessary calls to + agent_askpin. + * call-pinentry.c (pinentry_active_p): New. + +2006-10-17 Werner Koch <wk@g10code.com> + + * Makefile.am (gpg_agent_LDADD): Link to libcommonpth. + (gpg_agent_CFLAGS): New. This allows to only link this with Pth. + +2006-10-16 Werner Koch <wk@g10code.com> + + * call-pinentry.c (agent_get_confirmation): Map Cancel code here too. + * trustlist.c (agent_marktrusted): Return Cancel instead of + Not_Confirmed for the first question. + +2006-10-12 Werner Koch <wk@g10code.com> + + * protect-tool.c (get_passphrase): Fix if !HAVE_LANGINFO_CODESET. + +2006-10-06 Werner Koch <wk@g10code.com> + + * Makefile.am (AM_CFLAGS): Use PTH version of libassuan. + (gpg_agent_LDADD): Ditto. + + * divert-scd.c (divert_pksign): Use PKAUTH for the TLS algo. + +2006-10-05 Werner Koch <wk@g10code.com> + + * command.c (has_option_name): New. + (cmd_sethash): New --hash option. + * pksign.c (do_encode_raw_pkcs1): New. + (agent_pksign_do): Use it here for the TLS algo. + * agent.h (GCRY_MD_USER_TLS_MD5SHA1): New. + * divert-scd.c (pksign): Add case for tls-md5sha1. + + * divert-scd.c (encode_md_for_card): Check that the algo is valid. + +2006-10-04 Werner Koch <wk@g10code.com> + + * call-pinentry.c (agent_get_passphrase): Changed to return the + unencoded passphrase. + (agent_askpin, agent_get_passphrase, agent_get_confirmation): Need + to map the cancel error. + * command.c (send_back_passphrase): New. + (cmd_get_passphrase): Use it here. Also implement --data option. + (skip_options): New. + +2006-09-26 Werner Koch <wk@g10code.com> + + * learncard.c (agent_handle_learn): Send back the keypair + information. + +2006-09-25 Werner Koch <wk@g10code.com> + + * trustlist.c (read_one_trustfile): Allow extra flags. + (struct trustitem_s): Replaced KEYFLAGS by a FLAGS struct. + Changed all code to use this. + (agent_istrusted): New arg CTRL. Changed all callers. Send back + flags. + * command.c (agent_write_status): New. + +2006-09-20 Werner Koch <wk@g10code.com> + + * Makefile.am: Changes to allow parallel make runs. + +2006-09-15 Werner Koch <wk@g10code.com> + + * trustlist.c: Entirely rewritten. + (agent_trustlist_housekeeping): Removed and removed all calls. + +2006-09-14 Werner Koch <wk@g10code.com> + + Replaced all call gpg_error_from_errno(errno) by + gpg_error_from_syserror(). + + * call-pinentry.c (start_pinentry): Replaced pipe_connect2 by + pipe_connect_ext. + * call-scd.c (start_scd): Ditto. + * command.c (start_command_handler): Replaced + init_connected_socket_server by init_socket_server_ext. + +2006-09-13 Werner Koch <wk@g10code.com> + + * preset-passphrase.c (main) [W32]: Check for WSAStartup error. + +2006-09-08 Werner Koch <wk@g10code.com> + + * call-scd.c: Add signal.h as we are referencing SIGUSR2. + +2006-09-06 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (AM_CFLAGS): Add $(GPG_ERR_CFLAGS). + (gpg_agent_LDADD): Replace -lgpg-error with $(GPG_ERROR_LIBS). + +2006-09-06 Werner Koch <wk@g10code.com> + + * query.c: Renamed to .. + * call-pinentry.c: .. this. + + * agent.h (out_of_core): Removed. + (CTRL): Removed and changed everywhere to ctrl_t. + + Replaced all Assuan error codes by libgpg-error codes. Removed + all map_to_assuan_status and map_assuan_err. + + * gpg-agent.c (main): Call assuan_set_assuan_err_source to have Assuan + switch to gpg-error codes. + * command.c (set_error): Adjusted. + +2006-09-04 Werner Koch <wk@g10code.com> + + * command.c (percent_plus_unescape): New. + (cmd_get_val, cmd_putval): New. + +2006-08-29 Werner Koch <wk@g10code.com> + + * command-ssh.c (stream_read_mpi): Sanity check for early + detecting of too large keys. + * gpg-agent.c (my_gcry_outofcore_handler): New. + (main): Register it. + (main): No allocate 32k secure memory (was 16k). + +2006-07-31 Werner Koch <wk@g10code.com> + + * preset-passphrase.c (make_hexstring): For consistency use + xtrymalloc and changed caller to use xfree. Fixed function + comment. + +2006-07-29 Marcus Brinkmann <marcus@g10code.de> + + * preset-passphrase.c (preset_passphrase): Do not strip off last + character of passphrase. + (make_hexstring): New function. + * command.c (cmd_preset_passphrase): Use parse_hexstring to syntax + check passphrase argument. Truncate passphrase at delimiter. + +2006-07-24 Werner Koch <wk@g10code.com> + + * minip12.c (build_key_bag): New args SHA1HASH and + KEYIDSTR. Append bag Attributes if these args are given. + (build_cert_sequence): ditto. + (p12_build): Calculate certificate hash and pass to build + functions. + +2006-07-21 Werner Koch <wk@g10code.com> + + * minip12.c (oid_pkcs_12_keyBag): New. + (parse_bag_encrypted_data): New arg R_RESULT. Support keybags and + return the key object. + (p12_parse): Take new arg into account. Free RESULT on error. + +2006-06-26 Werner Koch <wk@g10code.com> + + * gpg-agent.c (handle_signal): Print info for SIGUSR2 only in + verbose mode. + +2006-06-22 Werner Koch <wk@g10code.com> + + * command-ssh.c (make_cstring): Use memcpy instead of strncpy. + (ssh_receive_mpint_list, sexp_key_extract, data_sign): Use + xtrycalloc instead of xtrymalloc followed by memset. + +2006-06-20 Werner Koch <wk@g10code.com> + + * minip12.c (create_final): New arg PW. Add code to calculate the + MAC. + +2006-06-09 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (gpg_agent_LDADD): Add $(NETLIBS). + (gpg_protect_tool_LDADD): Likewise. + (gpg_preset_passphrase_LDADD): Likewise. + +2006-04-09 Moritz Schulte <moritz@g10code.com> + + * command-ssh.c (ssh_request_process): Removed FIXME mentioning a + possible DoS attack. + +2006-04-01 Moritz Schulte <moritz@g10code.com> + + * command-ssh.c (ssh_identity_register): Make KEY_GRIP_RAW be 20 + instead of 21 bytes long; do not fill KEY_GRIP_RAW[20] with NUL + byte - KEY_GRIP_RAW is a raw binary string anyway. + +2006-02-09 Werner Koch <wk@g10code.com> + + * call-scd.c (struct scd_local_s): New field next_local. + (scd_local_list): New. + (start_scd): Put new local into list. + (agent_reset_scd): Remove it from the list. + (agent_scd_check_aliveness): Here is the actual reason why we need + all this stuff. + (agent_reset_scd): Send the new command RESTART instead of RESET. + +2005-12-16 Werner Koch <wk@g10code.com> + + * minip12.c (cram_octet_string): New + (p12_parse): Use it for NDEFed bags. + (parse_bag_data): Ditto. + (string_to_key, set_key_iv, crypt_block): New arg SALTLEN. + (p12_build): Use old value 8 for new arg. + (parse_bag_encrypted_data, parse_bag_data): Allow for salts of 8 + to 16 bytes. Add new arg R_CONSUMED. + +2005-11-24 Werner Koch <wk@g10code.com> + + * minip12.c (p12_parse): Fixed for case that the key object comes + prior to the certificate. + +2005-10-19 Werner Koch <wk@g10code.com> + + * divert-scd.c (getpin_cb): Hack to use it for a keypad message. + + * call-scd.c (inq_needpin): Reworked to support the new KEYPADINFO. + + * query.c (start_pinentry): Keep track of the owner. + (popup_message_thread, agent_popup_message_start) + (agent_popup_message_stop, agent_reset_query): New. + * command.c (start_command_handler): Make sure a popup window gets + closed. + +2005-10-08 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (gpg_protect_tool_LDADD): Add ../gl/libgnu.a. + (gpg_preset_passphrase_LDADD, t_common_ldadd): Likewise. + (gpg_agent_LDADD): Add ../gl/libgnu.a after ../common/libcommon.a. + +2005-09-16 Werner Koch <wk@g10code.com> + + * minip12.c (build_key_sequence, build_cert_sequence): Fixed + padding. + +2005-09-15 Moritz Schulte <moritz@g10code.com> + + * t-protect.c (test_agent_protect): Implemented. + (main): Disable use of secure memory. + +2005-09-09 Werner Koch <wk@g10code.com> + + * minip12.c (p12_build): Oops, array needs to be larger for the + certificate. + (build_cert_bag): Fixed yesterdays change. + + * command-ssh.c (card_key_available): Let the card handler decide + whether the card is supported here. Also get a short serial + number to return from the card handler. + +2005-09-08 Werner Koch <wk@g10code.com> + + * minip12.c (build_cert_bag): Use a non constructed object. + i.e. 0x80 and not 0xa0. + +2005-08-16 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): Use a default file name for --write-env-file. + +2005-07-25 Werner Koch <wk@g10code.com> + + * findkey.c (agent_public_key_from_file): Fixed array assignment. + This was the cause for random segvs. + +2005-06-29 Werner Koch <wk@g10code.com> + + * command-ssh.c (data_sign): Removed empty statement. + +2005-06-21 Werner Koch <wk@g10code.com> + + * minip12.c (create_final): Cast size_t to ulong for printf. + (build_key_bag, build_cert_bag, build_cert_sequence): Ditto. + +2005-06-16 Werner Koch <wk@g10code.com> + + * protect-tool.c (make_advanced): Makde RESULT a plain char. + * call-scd.c (unescape_status_string): Need to cast unsigned char* + for strcpy. + (agent_card_pksign): Made arg R_BUF an unsigned char**. + * divert-scd.c (divert_pksign): Made SIGVAL unsigned char*. + (encode_md_for_card): Initialize R_VAL and R_LEN. + * genkey.c (store_key): Made BUF unsigned. + * protect.c (do_encryption): Ditto. + (do_encryption): Made arg PROTBEGIN unsigned. Initialize RESULT + and RESULTLEN even on error. + (merge_lists): Need to cast unsigned char * for strcpy. Initialize + RESULTand RESULTLEN even on error. + (agent_unprotect): Likewise for strtoul. + (make_shadow_info): Made P and INFO plain char. + (agent_shadow_key): Made P plain char. + +2005-06-15 Werner Koch <wk@g10code.com> + + * query.c (agent_get_passphrase): Made HEXSTRING a char*. + * command-ssh.c (ssh_key_grip): Made arg BUFFER unsigned. + (ssh_key_grip): Simplified. + (data_sign): Initialize variables with the definition. + (ssh_convert_key_to_blob): Make sure that BLOB and BLOB_SIZE + are set to NULL on error. Cool, gcc-4 detects uninitialized stuff + beyond function boundaries; well it can't know that we do error + proper error handling so that this was not a real error. + (file_to_buffer): Likewise for BUFFER and BUFFER_N. + (data_sign): Likewise for SIG and SIG_N. + (stream_read_byte): Set B to a value even on error. + * command.c (cmd_genkey): Changed VALUE to char. + (cmd_readkey): Cast arg for gcry_sexp_sprint. + * agent.h (struct server_control_s): Made KEYGRIP unsigned. + +2005-06-13 Werner Koch <wk@g10code.com> + + * command-ssh.c (start_command_handler_ssh): Reset the SCD. + +2005-06-09 Werner Koch <wk@g10code.com> + + * gpg-agent.c (create_socket_name): New option --max-cache-ttl-ssh. + * cache.c (housekeeping): Use it. + (agent_put_cache): Use a switch to get the default ttl so that it + is easier to add more cases. + +2005-06-06 Werner Koch <wk@g10code.com> + + * gpg-agent.c: New option --default-cache-ttl-ssh. + * agent.h (cache_mode_t): New. + * pksign.c (agent_pksign_do): New arg CACHE_MODE to replace the + ARG IGNORE_CACHE. Changed all callers. + (agent_pksign): Ditto. + * findkey.c (agent_key_from_file): Ditto. Canged all callers. + (unprotect): Ditto. + * command-ssh.c (data_sign): Use CACHE_MODE_SSH. + * cache.c (agent_get_cache): New arg CACHE_MODE. + (agent_put_cache): Ditto. Store it in the cache. + + * query.c (agent_query_dump_state, dump_mutex_state): New. + (unlock_pinentry): Reset the global context before releasing the + mutex. + * gpg-agent.c (handle_signal): Dump query.c info on SIGUSR1. + + * call-scd.c (agent_scd_check_aliveness): Always do a waitpid and + add a timeout to the locking. + +2005-06-03 Werner Koch <wk@g10code.com> + + * command.c (cmd_updatestartuptty): New. + + * gpg-agent.c: New option --write-env-file. + + * gpg-agent.c (handle_connections): Make sure that the signals we + are handling are not blocked.Block signals while creating new + threads. + +2005-06-02 Werner Koch <wk@g10code.com> + + * call-scd.c (agent_scd_dump_state, dump_mutex_state): New. + * gpg-agent.c (handle_signal): Print it on SIGUSR1. + (handle_connections): Include the file descriptor into the + threadnames. + +2005-06-01 Werner Koch <wk@g10code.com> + + * gpg-agent.c: Include setenv.h. + +2005-05-31 Werner Koch <wk@g10code.com> + + * agent.h (out_of_core): s/__inline__/inine. Noted by Ray Link. + +2005-05-25 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): Do not unset the DISPLAY when we are + continuing as child. + +2005-05-24 Werner Koch <wk@g10code.com> + + * call-scd.c (inq_needpin): Skip leading spaces in of PIN + description. + * divert-scd.c (getpin_cb): Enhanced to cope with description + flags. + * query.c (agent_askpin): Add arg PROMPT_TEXT. Changed all + callers. + +2005-05-21 Werner Koch <wk@g10code.com> + + * call-scd.c (start_scd): Don't test for an alive scdaemon here. + (agent_scd_check_aliveness): New. + * gpg-agent.c (handle_tick): Test for an alive scdaemon. + (handle_signal): Print thread info on SIGUSR1. + +2005-05-20 Werner Koch <wk@g10code.com> + + * protect-tool.c: New option --canonical. + (show_file): Implement it. + + * keyformat.txt: Define the created-at attribute for keys. + +2005-05-18 Werner Koch <wk@g10code.com> + + * divert-scd.c (ask_for_card): Removed the card reset kludge. + +2005-05-17 Werner Koch <wk@g10code.com> + + * call-scd.c (unlock_scd): Add new arg CTRL. Changed all callers. + (start_scd): Reoworked to allow for additional connections. + * agent.h (ctrl_t): Add local data for the SCdaemon. + * command.c (start_command_handler): Release SERVER_LOCAL. + + * gpg-agent.c (create_server_socket): Use xmalloc. + (main): Removed option --disable-pth a dummy. Removed non-pth + code path. + (cleanup_sh): Removed. Not needed anymore. + +2005-05-05 Moritz Schulte <moritz@g10code.com> + + * command-ssh.c (ssh_key_to_buffer): Rename to ... + (ssh_key_to_protected_buffer): ... this; change callers. + Improved documentation. + Use ssh_key_grip(), where gcry_pk_get_keygrip() has been used + before. + (ssh_handler_sign_request): Removed unusued variable P. + +2005-04-20 Moritz Schulte <moritz@g10code.com> + + * command-ssh.c (ssh_handler_request_identities): Removed + debugging code (sleep call), which was commited unintenionally. + +2005-04-20 Werner Koch <wk@g10code.com> + + * minip12.c (parse_bag_encrypted_data): Fix the unpadding hack. + + * gpg-agent.c: New option --disable-scdaemon. + (handle_connections): Add time event to drive ... + (handle_tick): New function. + (main): Record the parent PID. Fixed segv when using ssh and a + command. + + * call-scd.c (start_scd): Take care of this option. + +2005-04-03 Moritz Schulte <moritz@g10code.com> + + * command-ssh.c (ssh_request_spec): New member: secret_input. + (REQUEST_SPEC_DEFINE): New argument: secret_input. + (request_specs): Add secret_input flag. + (request_spec_lookup): New function ... + (ssh_request_process): ... use it here; depending on secret_input + flag allocate secure or non-secure memory. + +2005-03-02 Moritz Schulte <moritz@g10code.com> + + * command-ssh.c (sexp_key_extract): Removed FIXME, since + xtrymallos does set errno correctly by now. + (sexp_extract_identifier): Remove const attribute from identifier. + (ssh_handler_request_identities): Remove const attribute from + key_type; removes ugly casts and FIXME. + (sexp_key_extract): Remove const attribute from comment. + (ssh_send_key_public): Remove const attribute from + key_type/comment; removes ugly cast. + (data_sign): Remove const attribute from identifier; removes ugly + cast. + (key_secret_to_public): Remove const attribute from comment; + removes ugly cast. + (ssh_handler_sign_request): Remove const attribute from p. + (sexp_key_extract): Use make_cstring(). + (ssh_key_extract_comment): Likewise. + (ssh_key_to_buffer): Use secure memory for memory area to hold the + key S-Expression. + Added more comments. + +2005-02-25 Werner Koch <wk@g10code.com> + + * findkey.c (modify_description): Keep invalid % escapes, so that + %0A may pass through. + + * agent.h (server_control_s): New field USE_AUTH_CALL. + * call-scd.c (agent_card_pksign): Make use of it. + * command-ssh.c (data_sign): Set the flag. + (ssh_send_key_public): New arg OVERRIDE_COMMENT. + (card_key_available): Add new arg CARDSN. + (ssh_handler_request_identities): Use the card s/n as comment. + (sexp_key_extract): Use GCRYMPI_FMT_STD. + (data_sign): Ditto. + + * learncard.c (make_shadow_info): Moved to .. + * protect.c (make_shadow_info): .. here. Return NULL on malloc + failure. Made global. + * agent.h: Add prototype. + +2005-02-24 Werner Koch <wk@g10code.com> + + * call-scd.c (unescape_status_string): New. Actual a copy of + ../g10/call-agent.c + (card_getattr_cb, agent_card_getattr): New. + + * command-ssh.c (card_key_available): New. + (ssh_handler_request_identities): First see whether a card key is + available. + + * gpg-agent.c (handle_connections): Need to check for events if + select returns with -1. + +2005-02-23 Werner Koch <wk@g10code.com> + + * command-ssh.c (get_passphrase): Removed. + (ssh_identity_register): Partly rewritten. + (open_control_file, search_control_file, add_control_entry): New. + (ssh_handler_request_identities): Return only files listed in our + control file. + + * findkey.c (unprotect): Check for allocation error. + + * agent.h (opt): Add fields to record the startup terminal + settings. + * gpg-agent.c (main): Record them and do not force keep display + with --enable-ssh-support. + * command-ssh.c (start_command_handler_ssh): Use them here. + + * gpg-agent.c: Renamed option --ssh-support to + --enable-ssh-support. + + * command.c (cmd_readkey): New. + (register_commands): Register new command "READKEY". + + * command-ssh.c (ssh_request_process): Improved logging. + + * findkey.c (agent_write_private_key): Always use plain open. + Don't depend on an umask for permissions. + (agent_key_from_file): Factored file reading code out to .. + (read_key_file): .. new function. + (agent_public_key_from_file): New. + +2005-02-22 Werner Koch <wk@g10code.com> + + * command-ssh.c (stream_read_string): Removed call to abort on + memory error because the CVS version of libgcrypt makes sure + that ERRNO gets always set on error even with a faulty user + supplied function. + +2005-02-19 Moritz Schulte <moritz@g10code.com> + + * command-ssh.c (ssh_receive_mpint_list): Slightly rewritten, do + not use elems_secret member of key_spec. + (ssh_key_type_spec): Removed member: elems_secret. + (ssh_key_types): Removed elems_secret data. + (ssh_sexp_construct): Renamed to ... + (sexp_key_construct): ... this; changed callers. + (ssh_sexp_extract): Renamed to ... + (sexp_key_extract): ... this; changed callers. + (ssh_sexp_extract_key_type): Renamed to ... + (sexp_extract_identifier): ... this; changed callers; use + make_cstring(). + Added more comments. + +2005-02-18 Moritz Schulte <moritz@g10code.com> + + * command-ssh.c (ssh_sexp_construct): Rewritten generation of sexp + template, clarified. + (ssh_sexp_extract): Support shadowed-private-key-sexp; treat + protected-private key and shadowed-private-key as public keys. + (key_secret_to_public): Rewritten: simply use ssh_sexp_extract() + and ssh_sexp_construct(). + +2005-02-15 Werner Koch <wk@g10code.com> + + * findkey.c (modify_description): Don't increment OUT_LEN during + the second pass. + +2005-02-14 Moritz Schulte <moritz@g10code.com> + + * command-ssh.c (es_read_byte): Renamed to ... + (stream_es_read_byte): ... this; changed callers. + (es_write_byte): Renamed to ... + (stream_write_byte): ... this; changed callers. + (es_read_uint32): Renamed to ... + (stream_read_uint32): ... this; changed callers. + (es_write_uint32): Renamed to ... + (stream_write_uint32): ... this; changed callers. + (es_read_data): Renamed to ... + (stream_read_data): ... this; changed callers. + (es_write_data): Renamed to ... + (stream_write_data): ... this; changed callers. + (es_read_string): Renamed to ... + (stream_read_string): ... this; changed callers. + (es_read_cstring): Renamed to ... + (stream_read_cstring): ... this; changed callers. + (es_write_string): Renamed to ... + (stream_write_string): ... this; changed callers. + (es_write_cstring): Renamed to ... + (stream_write_cstring): ... this; changed callers. + (es_read_mpi): Renamed to ... + (stream_read_mpi): ... this; changed callers. + (es_write_mpi): Renamed to ... + (stream_write_mpi): ... this; changed callers. + (es_copy): Renamed to ... + (stream_copy): ... this; changed callers. + (es_read_file): Renamed to ... + (file_to_buffer): ... this; changed callers. + (ssh_identity_register): Removed variable description_length; + changed code to use asprintf for description. + (stream_write_uint32): Do not filter out the last byte of shift + expression. + (uint32_construct): New macro ... + (stream_read_uint32): ... use it; removed unnecessary cast. + +2005-02-03 Werner Koch <wk@g10code.com> + + * agent.h (agent_exit): Add JNLIB_GCC_A_NR to indicate that this + function won't return. + + * gpg-agent.c (check_for_running_agent): Initialize pid to a + default value if not needed. + + * command-ssh.c: Removed stdint.h. s/byte_t/unsigned char/, + s/uint32/u32/ becuase that is what we have always used in GnuPG. + (ssh_request_specs): Moved to top of file. + (ssh_key_types): Ditto. + (make_cstring): Ditto. + (data_sign): Don't use a variable for the passphrase prompt, make + it translatable. + (ssh_request_process): + + + * findkey.c (modify_description): Renamed arguments for clarity, + polished documentation. Make comment a C-string. Fixed case of + DESCRIPTION being just "%". + (agent_key_from_file): Make sure comment string to a C-string. + + * gpg-agent.c (create_socket_name): Cleanup the implemntation, use + DIMof, agent_exit, removed superflous args and return the + allocated string as value. Documented. Changed callers. + (create_server_socket): Cleanups similar to above. Changed callers. + (cleanup_do): Renamed to .. + (remove_socket): .. this. Changed caller. + (handle_connections): The signals are to be handled in the select + and not in the accept. Test all FDs after returning from a + select. Remove the event tests from the accept calls. The select + already assured that the accept won't block. + +2005-01-29 Moritz Schulte <moritz@g10code.com> + + * command-ssh.c (ssh_handler_request_identities) + (ssh_handler_sign_request, ssh_handler_add_identity) + (ssh_handler_remove_identity, ssh_handler_remove_all_identities) + (ssh_handler_lock, ssh_handler_unlock): Changed to return an error + code instead of a boolean. + (ssh_request_process): Changed to return a boolean instead of an + error; adjust caller. + (ssh_request_handle_t): Adjusted type. + (ssh_request_spec): New member: identifier. + (REQUEST_SPEC_DEFINE): New macro; use it for initialization of + request_specs[]. + (ssh_request_process): In debugging mode, log identifier of + handler to execute. + (start_command_handler_ssh): Moved most of the stream handling + code ... + (ssh_request_process): ... here. + +2005-01-28 Moritz Schulte <moritz@g10code.com> + + * command-ssh.c (ssh_handler_add_identity): Pass ctrl to + ssh_identity_register(). + (ssh_identity_register): New argument: ctrl; pass ctrl to + get_passphrase(). + (get_passphrase): Pass ctrl instead of NULL to agent_askpin(). + (start_command_handler_ssh): Use agent_init_default_ctrl(); + deallocate structure members, which might be dynamically + allocated. + (lifetime_default): Removed variable. + (ssh_handler_add_identity): Fix ttl handling; renamed variable + `death' to `ttl'. + (ssh_identity_register): Fix key grip handling. + +2005-01-26 Moritz Schulte <moritz@g10code.com> + + * command-ssh.c (ssh_handler_sign_request): Confirm to agent + protocol in case of failure. + + * command-ssh.c: New file. + + * Makefile.am (gpg_agent_SOURCES): New source file: command-ssh.c. + + * findkey.c (modify_description): New function. + (agent_key_from_file): Support comment field in key s-expressions. + + * gpg-agent.c (enum cmd_and_opt_values): New item: oSSHSupport. + (opts) New entry for oSSHSupport. + New variable: socket_name_ssh. + (cleanup_do): New function based on cleanup(). + (cleanup): Use cleanup_do() for socket_name and socket_name_ssh. + (main): New switch case for oSSHSupport. + (main): Move socket name creation code to ... + (create_socket_name): ... this new function. + (main): Use create_socket_name() for creating socket names for + socket_name and for socket_name_ssh in case ssh support is + enabled. + Move socket creation code to ... + (create_server_socket): ... this new function. + (main): Use create_server_socket() for creating sockets. + In case standard_socket is set, do not only store a socket name in + socket_name, but also in socket_name_ssh. + Generate additional environment info strings for ssh support. + Pass additional ssh socket argument to handle_connections. + (start_connection_thread_ssh): New function. + (handle_connections): Use select to multiplex between gpg-agent + and ssh-agent protocol. + + * agent.h (struct opt): New member: ssh_support. + (start_command_handler_ssh): Add prototype. + +2005-01-04 Werner Koch <wk@g10code.com> + + * trustlist.c (agent_marktrusted): Use "Cancel" for the first + confirmation and made the strings translatable. + + * cache.c (agent_put_cache): Fix the test for using the default + TTL. + +2004-12-21 Werner Koch <wk@g10code.com> + + * preset-passphrase.c (preset_passphrase): Handle --passphrase. + + * Makefile.am (gpg_preset_passphrase_LDADD): Reorder libs so that + pwquery may use stuff from jnlib. Conditionally add -lwsock2 + (gpg_protect_tool_LDADD): Ditto. + + * preset-passphrase.c (main): Use default_homedir(). + (main) [W32]: Initialize sockets. + +2004-12-21 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (libexec_PROGRAMS): Add gpg-preset-passphrase. + (gpg_preset_passphrase_SOURCES, gpg_preset_passphrase_LDADD): New + targets. + * agent.h (opt): New member allow_cache_passphrase. + * cache.c (housekeeping): Check if R->ttl is not negative. + (agent_put_cache): Allow ttl to be negative. + * command.c (parse_hexstring): Allow something to follow the + hexstring. + (cmd_cache_passphrase): New function. + (register_commands): Add it. + * gpg-agent.c: Handle --allow-preset-passphrase. + * preset-passphrase.c: New file. + +2004-12-21 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): Use default_homedir(). + * protect-tool.c (main): Ditto. + +2004-12-20 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main) [W32]: Now that Mutexes work we can remove + the pth_init kludge. + (main): Add new options --[no-]use-standard-socket. + (check_for_running_agent): Check whether it is running on the + standard socket. + + * call-scd.c (init_membuf, put_membuf, get_membuf): Removed. We + now use the identical implementation from ../common/membuf.c. + + * pksign.c (agent_pksign): Changed arg OUTFP to OUTBUF and use + membuf functions to return the value. + * pkdecrypt.c (agent_pkdecrypt): Ditto. + * genkey.c (agent_genkey): Ditto. + * command.c (cmd_pksign, cmd_pkdecrypt, cmd_genkey): Replaced + assuan_get_data_fp() by a the membuf scheme. + (clear_outbuf, write_and_clear_outbuf): New. + +2004-12-19 Werner Koch <wk@g10code.com> + + * query.c (initialize_module_query): New. + * call-scd.c (initialize_module_call_scd): New. + * gpg-agent.c (main): Call them. + +2004-12-18 Werner Koch <wk@g10code.com> + + * gpg-agent.c (main): Remove special Pth initialize. + + * agent.h (map_assuan_err): Define in terms of + map_assuan_err_with_source. + +2004-12-17 Moritz Schulte <moritz@g10code.com> + + * query.c: Undo change from 2004-12-05. + +2004-12-15 Werner Koch <wk@g10code.com> + + * gpg-agent.c [W32]: Various hacks to make it work. + + * findkey.c (agent_write_private_key) [W32]: Adjust open call. + + * call-scd.c (start_scd) [W32]: Don't check whether the daemon + didn't died. To hard to do under Windows. + (start_scd) [W32]: Disable sending of the event signal option. + + * protect-tool.c (read_file, export_p12_file) [W32]: Use setmode + to get stdout and stin into binary mode. + +2004-12-05 Moritz Schulte <moritz@g10code.com> + + * query.c (start_pinentry): Allow CTRL be NULL. + +2004-10-22 Werner Koch <wk@g10code.com> + + * gpg-agent.c (parse_rereadable_options): Return "not handled" + when the log file has not beend hadled. This is will let the main + option processing continue. Fixed a bug introduced on 2004-09-4 + resulting in logging to stderr until a HUP has been given. + (main): Don't close the listen FD. + +2004-09-30 Werner Koch <wk@g10code.com> + + * Makefile.am: Adjusted from gettext 1.14. + +2004-09-29 Werner Koch <wk@g10code.com> + + * minip12.c (parse_bag_encrypted_data): Print error if a bad + passphrase has been given. + +2004-09-28 Werner Koch <wk@g10code.com> + + * protect.c (agent_unprotect): Fixed wiping of CLEARTEXT. Thanks + to Moritz for pointing this out. + +2004-09-25 Moritz Schulte <moritz@g10code.com> + + * agent.h: Declare: agent_pksign_do. + (struct server_control_s): New member: raw_value. + + * pksign.c (do_encode_md): New argument: raw_value; support + generation of raw (non-pkcs1) data objects; adjust callers. + (agent_pksign_do): New function, based on code ripped + out from agent_pksign. + (agent_pksign): Use agent_pksign_do. + + * command.c (start_command_handler): Set ctrl.digest.raw_value. + +2004-09-09 Werner Koch <wk@g10code.de> + + * gpg-agent.c (check_for_running_agent): New. + (main): The default action is now to check for an already running + agent. + (parse_rereadable_options): Set logfile only on reread. + (main): Do not print the "is development version" note. + +2004-08-20 Werner Koch <wk@g10code.de> + + * gpg-agent.c: New option --max-cache-ttl. Suggested by Alexander + Belopolsky. + * cache.c (housekeeping): Use it here instead of the hardwired + default of 1 hour. + + * query.c (start_pinentry): Use a timeout for the pinentry lock. + +2004-08-18 Werner Koch <wk@g10code.de> + + * protect-tool.c (get_passphrase): Make sure that the default + prompts passed to gpg-agent are utf-8 encoded. Add new prompt values. + (import_p12_file, import_p12_file, export_p12_file): Changed calls + to get_passphrase so that better prompts are displayed. + (get_new_passphrase): New. + +2004-07-22 Werner Koch <wk@g10code.de> + + * trustlist.c (read_list): Allow colons in the fingerprint. + (headerblurb): Rephrased. + + * gpg-agent.c (handle_connections): Increase the stack size ot 256k. + +2004-06-20 Moritz Schulte <moritz@g10code.com> + + * gpg-agent.c: Include <sys/stat.h> (build fix for BSD). + +2004-05-11 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (handle_signal): Reload the trustlist on SIGHUP. + (start_connection_thread): Hack to simulate a ticker. + * trustlist.c (agent_trustlist_housekeeping) + (agent_reload_trustlist): New. Protected all global functions + here with a simple counter which is sufficient for Pth. + +2004-05-03 Werner Koch <wk@gnupg.org> + + * gpg-agent.c: Remove help texts for options lile --lc-ctype. + (main): New option --allow-mark-trusted. + * trustlist.c (agent_marktrusted): Use it here. + +2004-04-30 Werner Koch <wk@gnupg.org> + + * protect-tool.c: New option --enable-status-msg. + (store_private_key): Print status messages for imported keys. + (read_and_unprotect): Ditto for bad passphrase. + + * gpg-agent.c (parse_rereadable_options): New arg REREAD. Allow + changing oLogFile. + (current_logfile): New. + +2004-04-26 Werner Koch <wk@gnupg.org> + + * call-scd.c (start_scd): Do not register an event signal if we + are running as a pipe server. + +2004-04-21 Werner Koch <wk@gnupg.org> + + * call-scd.c (start_scd): Send event-signal option. Always check + that the scdaemon is still running. + + * gpg-agent.c (handle_signal): Do not use SIGUSR{1,2} anymore for + changing the verbosity. + +2004-04-16 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (main): Tell the logging code that we are running + detached. + +2004-04-06 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (main): Use new libgcrypt thread library register + scheme. + +2004-03-23 Marcus Brinkmann <marcus@g10code.de> + + * gpg-agent.c (main): For now, always print the default config + file name for --gpgconf-list. + +2004-03-17 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (main) <gpgconf>: Fixed default value quoting. + +2004-03-16 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (parse_rereadable_options): Use the new + DEFAULT_CACHE_TTL macro. + (main): Updated --gpgconf-list output. + +2004-02-21 Werner Koch <wk@gnupg.org> + + * command.c (cmd_passwd): Take acount of a key description. + + * genkey.c (reenter_compare_cb): Do not set the error text. + (agent_protect_and_store, agent_genkey): Force a re-enter after a + non-matching passphrase. + * query.c (agent_askpin): Add new arg INITIAL_ERRTEXT; changed + all callers. + +2004-02-19 Werner Koch <wk@gnupg.org> + + * protect-tool.c: New options --have-cert and --prompt. + (export_p12_file): Read a certificate from STDIN and pass it to + p12_build. Detect a keygrip and construct the filename in that + case. Unprotcet a key if needed. Print error messages for key + formats we can't handle. + (release_passphrase): New. + (get_passphrase): New arg PROMPTNO. Return the allocated + string. Changed all callers. + + * minip12.c: Revamped the build part. + (p12_build): New args CERT and CERTLEN. + +2004-02-18 Werner Koch <wk@gnupg.org> + + * protect-tool.c (main): Setup the used character set. + * gpg-agent.c (main): Ditto. + + * gpg-agent.c (set_debug): New. New option --debug-level. + (main): New option --gpgconf-list. + +2004-02-17 Werner Koch <wk@gnupg.org> + + * pksign.c (do_encode_md): Cleaned up by using gcry_sexp_build. + + * Makefile.am (gpg_protect_tool_SOURCES): Removed + simple-pwquery.[ch], as we once moved it to ../common. + +2004-02-13 Werner Koch <wk@gnupg.org> + + * command.c (cmd_setkeydesc): New. + (register_commands): Add command SETKEYDESC. + (cmd_pksign, cmd_pkdecrypt): Use the key description. + (reset_notify): Reset the description. + * findkey.c (unprotect): Add arg DESC_TEXT. + (agent_key_from_file): Ditto. + * pksign.c (agent_pksign): Ditto. + * pkdecrypt.c (agent_pkdecrypt): Ditto. Made CIPHERTEXT an + unsigned char*. + + * protect-tool.c (main): New options --no-fail-on-exist, --homedir. + (store_private_key): Use them here. + +2004-02-12 Werner Koch <wk@gnupg.org> + + * protect-tool.c (read_file, main): Allow reading from stdin. + + * Makefile.am: Include cmacros.am for common flags. + (libexec_PROGRAMS): Put gpg-protect-tool there. + +2004-02-10 Werner Koch <wk@gnupg.org> + + * minip12.c (parse_bag_encrypted_data): Finished implementation. + (p12_parse): Add callback args. + * protect-tool.c (import_p12_cert_cb): New. + (import_p12_file): Use it. + +2004-02-06 Werner Koch <wk@gnupg.org> + + * minip12.c (crypt_block): Add arg CIPHER_ALGO; changed all callers. + (set_key_iv): Add arg KEYBYTES; changed caller. + +2004-02-03 Werner Koch <wk@gnupg.org> + + * findkey.c (agent_key_from_file): Extra paranoid wipe. + * protect.c (agent_unprotect): Ditto. + (merge_lists): Ditto. Add arg RESULTLEN. + * pkdecrypt.c (agent_pkdecrypt): Don't show the secret key even in + debug mode. + + * protect.c: Add DSA and Elgamal description. + +2004-01-29 Werner Koch <wk@gnupg.org> + + * agent.h (server_control_s): Add connection_fd field. + * command.c (start_command_handler): Init it here. + * gpg-agent.c (agent_init_default_ctrl): and here. + * call-scd.c: Add the CTRL arg to all functions calling start_scd + and pass it to start_scd. Changed all callers + (start_scd): Keep track of the current active connection. + (agent_reset_scd): New. + * command.c (start_command_handler): Call it here. + * learncard.c (agent_handle_learn): Add arg CTRL; changed caller. + (send_cert_back): Ditto. + +2004-01-28 Werner Koch <wk@gnupg.org> + + * trustlist.c (agent_marktrusted): Check whether the trustlist is + writable. + +2004-01-27 Werner Koch <wk@gnupg.org> + + * sexp-parse.h: Moved to ../common. + +2004-01-24 Werner Koch <wk@gnupg.org> + + * call-scd.c (atfork_cb): New. + (start_scd): Make sure secmem gets cleared. + * query.c (atfork_cb): New. + (start_pinentry): Make sure secmem gets cleared. + +2004-01-16 Werner Koch <wk@gnupg.org> + + * findkey.c (agent_key_from_file): Now return an error code so + that we have more detailed error messages in the upper layers. + This fixes the handling of pinentry's cancel button. + * pksign.c (agent_pksign): Changed accordingly. + * pkdecrypt.c (agent_pkdecrypt): Ditto. + * command.c (cmd_passwd): Ditto. + +2003-12-16 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (main): Set the prefixes for assuan logging. + +2003-12-15 Werner Koch <wk@gnupg.org> + + * protect.c (do_encryption): Use gcry_create_nonce instad of the + obsolete WEAK_RANDOM. + +2003-11-20 Werner Koch <wk@gnupg.org> + + * sexp-parse.h (snext): Don't use atoi_1 and digitp macros, so + that this file is useful by other applications too. + +2003-10-27 Werner Koch <wk@gnupg.org> + + * command.c (cmd_get_confirmation): New command. + +2003-08-20 Timo Schulz <twoaday@freakmail.de> + + * pksign.c (do_encode_md): Allocate enough space. Cast md + byte to unsigned char to prevent sign extension. + +2003-08-14 Timo Schulz <twoaday@freakmail.de> + + * pksign.c (do_encode_md): Due to the fact pkcs#1 padding + is now in Libgcrypt, use the new interface. + +2003-07-31 Werner Koch <wk@gnupg.org> + + * Makefile.am (gpg_agent_LDADD): Added INTLLIBS. + (gpg_protect_tool_SOURCES): Added simple-pwquery.[ch] + +2003-07-27 Werner Koch <wk@gnupg.org> + + Adjusted for gcry_mpi_print and gcry_mpi_scan API change. + +2003-07-15 Werner Koch <wk@gnupg.org> + + * simple-pwquery.c, simple-pwquery.h: Moved to ../common. + * Makefile.am (gpg_protect_tool_LDADD): Add simple-pwquery.o. + Removed it from xx_SOURCES. + +2003-07-04 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (handle_connections): Kludge to allow use of Pth 1 + and 2. + +2003-06-30 Werner Koch <wk@gnupg.org> + + * call-scd.c (learn_status_cb): Store the serialno in PARM. + +2003-06-26 Werner Koch <wk@gnupg.org> + + * call-scd.c (agent_card_serialno): Don't do a RESET anymore. + +2003-06-25 Werner Koch <wk@gnupg.org> + + * command.c (cmd_scd): New. + * call-scd.c (agent_card_scd): New. + * divert-scd.c (divert_generic_cmd): New + + * call-scd.c (agent_card_learn): New callback args SINFO. + (learn_status_cb): Pass all other status lines to the sinfo + callback. + * learncard.c (release_sinfo, sinfo_cb): New. + (agent_handle_learn): Pass the new cb to the learn function and + pass the collected information back to the client's assuan + connection. + + * gpg-agent.c (main): Moved pth_init before gcry_check_version. + +2003-06-24 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (handle_connections): Adjusted for Pth 2.0 + + Adjusted for changes in the libgcrypt API. Some more fixes for the + libgpg-error stuff. + +2003-06-04 Werner Koch <wk@gnupg.org> + + Renamed error codes from INVALID to INV and removed _ERROR suffixes. + +2003-06-03 Werner Koch <wk@gnupg.org> + + Changed all error codes in all files to the new libgpg-error scheme. + + * agent.h: Include gpg-error.h and errno.h + * Makefile.am: Link with libgpg-error + + * query.c: assuan.h is now a system header. + * genkey.c (agent_genkey): Fixed silly use of xmalloc by + xtrymalloc. + +2003-04-29 Werner Koch <wk@gnupg.org> + + * command.c (register_commands): Adjusted for new Assuan semantics. + + * Makefile.am: Don't override LDFLAGS. + +2002-12-04 Werner Koch <wk@gnupg.org> + + * gpg-agent.c: New variable config_filename. + (parse_rereadable_options): New. + (main): Use it here. Add setting of default values, set + config_filename. + (reread_configuration): Filled with actual code. + +2002-12-03 Werner Koch <wk@gnupg.org> + + * protect-tool.c (read_key): Don't run make_canonical on a NULL + buffer. + + * command.c (parse_hexstring): New. + (cmd_sethash): Use it. + (parse_keygrip): New. + (cmd_havekey, cmd_sigkey): Use it. + (cmd_passwd): New. + * genkey.c (agent_protect_and_store): New. + (store_key): Add arg FORCE. + (agent_genkey): Pass false to this force of store_key. + +2002-11-13 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (main): Switch all messages to utf-8. + + * simple-pwquery.c (agent_send_all_options): Use $GPG_TTY and + stdin with ttyname. + + * cache.c (new_data): Uiih - /sizeof d/sizeof *d/. + +2002-11-10 Werner Koch <wk@gnupg.org> + + * command.c (option_handler): Fix keep_tty check. + +2002-11-06 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (main): Make sure we have a default ttyname. + * command.c (option_handler): Check opt.keep_tty here + * query.c (start_pinentry): but not anymore here. + +2002-11-05 Werner Koch <wk@gnupg.org> + + * agent.h (opt,server_control_s): Move display and lc_ variables + to the control struct so that they are per connection. + * gpg-agent.c (agent_init_default_ctrl): New. + (main): Assign those command line options to new default_* variables. + Reset DISPLAY in server mode so that there is no implicit default. + * command.c (start_command_handler): Initialize and deinitialize + the control values. + (option_handler): Work on the ctrl values and not on the opt. + * query.c (start_pinentry): New argument CTRL to set the display + connection specific. Changed all callers to pass this value. + (agent_askpin,agent_get_passphrase,agent_get_confirmation): Add + CTRL arg and pass it ot start_pinentry. + * command.c (cmd_get_passphrase): Pass CTRL argument. + * trustlist.c (agent_marktrusted): Add CTRL argument + * command.c (cmd_marktrusted): Pass CTRL argument + * divert-scd.c (ask_for_card): Add CTRL arg. + (divert_pksign,divert_pkdecrypt): Ditto. Changed caller. + (getpin_cb): Use OPAQUE to pass the CTRL variable. Changed both + users. + * findkey.c (unprotect): Add CTRL arg. + (agent_key_from_file): Ditto. + + * query.c (unlock_pinentry): Disconnect the pinentry so that we + start a new one for each request. This is required to support + clients with different environments (e.g. X magic cookies). + +2002-09-05 Neal H. Walfield <neal@cs.uml.edu> + + * gpg-agent.c (main) [USE_GNU_PTH]: No need to call + assuan_set_io_func as assuan is smart. + +2002-09-25 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (handle_signal): Flush cache on SIGHUP. + * cache.c (agent_flush_cache): New. + + * gpg-agent.c, agent.h: Add --keep-display and --keep-tty. + * query.c (start_pinentry): Implement them. The option passing + needs more thoughts. + +2002-09-09 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (create_private_keys_directory) + (create_directories): New. + (main): Try to create a home directory. + +2002-09-04 Neal H. Walfield <neal@g10code.de> + + * gpg-agent.c (main): Use sigaction, not signal. + +2002-09-03 Neal H. Walfield <neal@g10code.de> + + * findkey.c: Include <fcntl.h>. + (agent_write_private_key): Prefer POSIX compatibity, open and + fdopen, over the simplicity of GNU extensions, fopen(file, "x"). + +2002-08-22 Werner Koch <wk@gnupg.org> + + * query.c (agent_askpin): Provide the default desc text depending + on the pininfo. Do the basic PIN verification only when + min_digits is set. + +2002-08-21 Werner Koch <wk@gnupg.org> + + * query.c (agent_askpin): Hack to show the right default prompt. + (agent_get_passphrase): Ditto. + + * trans.c: Removed and replaced all usages with standard _() + + * divert-scd.c (getpin_cb): Pass a more descritive text to the + pinentry. + + * Makefile.am: Renamed the binary protect-tool to gpg-protect-tool. + * protect-tool.c: Removed the note about internal use only. + + * gpg-agent.c (main): New option --daemon so that the program is + not accidently started in the background. + +2002-08-16 Werner Koch <wk@gnupg.org> + + * call-scd.c (learn_status_cb): Handle CERTINFO status. + (agent_card_learn): Add args for certinfo cb. + * learncard.c (release_certinfo,certinfo_cb): New. + (send_cert_back): New. With factored out code from .. + (agent_handle_learn): here. Return certinfo stuff. + +2002-07-26 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (main): New option --ignore-cache-for-signing. + * command.c (option_handler): New server option + use-cache-for-signing defaulting to true. + (cmd_pksign): handle global and per session option. + * findkey.c (agent_key_from_file, unprotect): New arg + ignore_cache. Changed all callers. + * pksign.c (agent_pksign): Likewise. + +2002-06-29 Werner Koch <wk@gnupg.org> + + * query.c (start_pinentry): Use GNUPG_DERAULT_PINENTRY. + * call-scd.c (start_scd): Use GNUPG_DEFAULT_SCDAEMON. + +2002-06-28 Werner Koch <wk@gnupg.org> + + * protect-tool.c (export_p12_file): New. + (main): New command --p12-export. + * minip12.c (create_final,p12_build,compute_tag_length): New. + (store_tag_length): New. + +2002-06-27 Werner Koch <wk@gnupg.org> + + * minip12.c (crypt_block): Renamed from decrypt_block, add arg to + allow encryption. + + * Makefile.am (pkglib_PROGRAMS): Put protect-tool there. + + * findkey.c (agent_write_private_key,agent_key_from_file) + (agent_key_available): Use GNUPG_PRIVATE_KEYS_DIR constant. + * gpg-agent.c (main): Use GNUPG_DEFAULT_HOMEDIR constant. + + * protect-tool.c (store_private_key): New. + (import_p12_file): Store the new file if requested. + (main): New options --force and --store. + + * gpg-agent.c (main): Set a global flag when running detached. + * query.c (start_pinentry): Pass the list of FD to keep in the + child when not running detached. + * call-scd.c (start_scd): Ditto. + +2002-06-26 Werner Koch <wk@gnupg.org> + + * command.c (cmd_istrusted, cmd_listtrusted, cmd_marktrusted) + (cmd_pksign, cmd_pkdecrypt, cmd_genkey, cmd_get_passphrase) + (cmd_learn): Print an error message for a failed operation. + + * simple-pwquery.c, simple-pwquery.h: New. + * protect-tool. (get_passphrase): New, used to get a passphrase + from the agent if none was given on the command line. + +2002-06-25 Werner Koch <wk@gnupg.org> + + * protect-tool.c (rsa_key_check): New. + (import_p12_file): New. + (main): New command --p12-import. + * minip12.c, minip12.h: New. + +2002-06-24 Werner Koch <wk@gnupg.org> + + * protect-tool.c (read_file): New. + (read_key): Factored most code out to read_file. + +2002-06-17 Werner Koch <wk@gnupg.org> + + * agent.h: Add a callback function to the pin_entry_info structure. + * query.c (agent_askpin): Use the callback to check for a correct + PIN. Removed the start_err_text argument because it is not + anymore needed; changed callers. + * findkey.c (unprotect): Replace our own check loop by a callback. + (try_unprotect_cb): New. + * genkey.c (reenter_compare_cb): New. + (agent_genkey): Use this callback here. Fixed setting of the pi2 + variable and a segv in case of an empty PIN. + + * divert-scd.c (getpin_cb): Removed some unused stuff and + explained what we still have to change. + +2002-06-12 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (main): New option --disable-pth. + +2002-06-11 Werner Koch <wk@gnupg.org> + + * protect-tool.c: Add command --show-keygrip + (show_keygrip): New. + +2002-05-23 Werner Koch <wk@gnupg.org> + + * call-scd.c: Seirialized all scdaeom access when using Pth. + + * cache.c: Made the cache Pth-thread-safe. + (agent_unlock_cache_entry): New. + * findkey.c (unprotect): Unlock the returned cache value. + * command.c (cmd_get_passphrase): Ditto. + + * gpg-agent.c (main): Register pth_read/write with Assuan. + +2002-05-22 Werner Koch <wk@gnupg.org> + + * query.c: Serialized all pinentry access when using Pth. + + * gpg-agent.c (handle_signal,start_connection_thread) + (handle_connections): New + (main): Use the new Pth stuff to allow concurrent connections. + * command.c (start_command_handler): Add new arg FD so that the + fucntion can also be used for an already connected socket. + * Makefile.am: Link with Pth. + +2002-05-14 Werner Koch <wk@gnupg.org> + + * cache.c (housekeeping, agent_put_cache): Use our time() wrapper. + +2002-04-26 Werner Koch <wk@gnupg.org> + + * cache.c (agent_put_cache): Reinitialize the creation time and + the ttl when reusing a slot. + + * call-scd.c (start_scd): Print debug messages only with debug + flags set. + * query.c (start_pinentry): Ditto. + +2002-04-25 Marcus Brinkmann <marcus@g10code.de> + + * agent.h (agent_get_confirmation): Replace paramter prompt with + two parameters ok and cancel. + * query.c (agent_get_confirmation): Likewise. Implement this. + * trustlist.c (agent_marktrusted): Fix invocation of + agent_get_confirmation. + * divert-scd.c (ask_for_card): Likewise. + +2002-04-24 Marcus Brinkmann <marcus@g10code.de> + + * agent.h (struct opt): Add members display, ttyname, ttytype, + lc_ctype, and lc_messages. + * gpg-agent.c (enum cmd_and_opt_values): Add oDisplay, oTTYname, + oTTYtype, oLCctype, and LCmessages. + (main): Handle these options. + * command.c (option_handler): New function. + (register_commands): Register option handler. + * query.c (start_pinentry): Pass the various display and tty + options to the pinentry. + +2002-04-05 Werner Koch <wk@gnupg.org> + + * protect-tool.c (show_file): New. Used as default action. + +2002-03-28 Werner Koch <wk@gnupg.org> + + * divert-scd.c (encode_md_for_card): Don't do the pkcs-1 padding, + the scdaemon should take care of it. + (ask_for_card): Hack to not display the trailing zero. + +2002-03-11 Werner Koch <wk@gnupg.org> + + * learncard.c (kpinfo_cb): Remove the content restrictions from + the keyID. + +2002-03-06 Werner Koch <wk@gnupg.org> + + * learncard.c: New. + * divert-scd.c (ask_for_card): The serial number is binary so + convert it to hex here. + * findkey.c (agent_write_private_key): New. + * genkey.c (store_key): And use it here. + + * pkdecrypt.c (agent_pkdecrypt): Changed the way the diversion is done. + * divert-scd.c (divert_pkdecrypt): Changed interface and + implemented it. + +2002-03-05 Werner Koch <wk@gnupg.org> + + * call-scd.c (inq_needpin): New. + (agent_card_pksign): Add getpin_cb args. + (agent_card_pkdecrypt): New. + +2002-03-04 Werner Koch <wk@gnupg.org> + + * pksign.c (agent_pksign): Changed how the diversion is done. + * divert-scd.c (divert_pksign): Changed interface and implemented it. + (encode_md_for_card): New. + * call-scd.c (agent_card_pksign): New. + +2002-02-28 Werner Koch <wk@gnupg.org> + + * pksign.c (agent_pksign): Detect whether a Smartcard is to be + used and divert the operation in this case. + * pkdecrypt.c (agent_pkdecrypt): Likewise + * findkey.c (agent_key_from_file): Add optional arg shadow_info + and have it return information about a shadowed key. + * protect.c (agent_get_shadow_info): New. + + * protect.c (snext,sskip,smatch): Moved to + * sexp-parse.h: New file. + * divert-scd.c: New. + +2002-02-27 Werner Koch <wk@gnupg.org> + + * protect.c (agent_shadow_key): New. + + * command.c (cmd_learn): New command LEARN. + * gpg-agent.c: New option --scdaemon-program. + * call-scd.c (start_scd): New. Based on query.c + * query.c: Add 2 more arguments to all uses of assuan_transact. + +2002-02-18 Werner Koch <wk@gnupg.org> + + * findkey.c (unprotect): Show an error message for a bad passphrase. + + * command.c (cmd_marktrusted): Implemented. + * trustlist.c (agent_marktrusted): New. + (open_list): Add APPEND arg. + + * query.c (agent_get_confirmation): New. + +2002-02-06 Werner Koch <wk@gnupg.org> + + * cache.c (housekeeping): Fixed linking in the remove case. + +2002-02-01 Werner Koch <wk@gnupg.org> + + * gpg-agent.c: New option --default-cache-ttl. + * cache.c (agent_put_cache): Use it. + + * cache.c: Add a few debug outputs. + + * protect.c (agent_private_key_type): New. + * agent.h: Add PRIVATE_KEY_ enums. + * findkey.c (agent_key_from_file): Use it to decide whether we + have to unprotect a key. + (unprotect): Cache the passphrase. + + * findkey.c (agent_key_from_file,agent_key_available): The key + files do now require a ".key" suffix to make a script's life + easier. + * genkey.c (store_key): Ditto. + +2002-01-31 Werner Koch <wk@gnupg.org> + + * genkey.c (store_key): Protect the key. + (agent_genkey): Ask for the passphrase. + * findkey.c (unprotect): Actually unprotect the key. + * query.c (agent_askpin): Add an optional start_err_text. + +2002-01-30 Werner Koch <wk@gnupg.org> + + * protect.c: New. + (hash_passphrase): Based on the GnuPG 1.0.6 version. + * protect-tool.c: New + +2002-01-29 Werner Koch <wk@gnupg.org> + + * findkey.c (agent_key_available): New. + * command.c (cmd_havekey): New. + (register_commands): And register new command. + +2002-01-20 Werner Koch <wk@gnupg.org> + + * command.c (cmd_get_passphrase): Remove the plus signs. + + * query.c (start_pinentry): Send no-grab option to pinentry + * gpg-agent.c (main): Move variable grab as no_grab to agent.h. + +2002-01-19 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (main): Disable core dumps. + + * cache.c: New. + * command.c (cmd_get_passphrase): Use the cache. + (cmd_clear_passphrase): Ditto. + + * gpg-agent.c: Removed unused cruft and implement the socket + based server. + (my_strusage): Take bug report address from configure.ac. + * command.c (start_command_handler): Add an argument to start as + regular server. + (start_command_handler): Enable Assuan logging. + +2002-01-15 Werner Koch <wk@gnupg.org> + + * trustlist.c: New. + * command.c (cmd_istrusted, cmd_listtrusted, cmd_marktrusted): New. + +2002-01-07 Werner Koch <wk@gnupg.org> + + * genkey.c: Store the secret part and return the public part. + +2002-01-03 Werner Koch <wk@gnupg.org> + + * command.c (cmd_get_passphrase): New. + (cmd_clear_passphrase): New. + * query.c (agent_get_passphrase): New. + +2002-01-02 Werner Koch <wk@gnupg.org> + + * genkey.c: New. + * command.c (cmd_genkey): New. + + * command.c (rc_to_assuan_status): Removed and changed all callers + to use map_to_assuan_status. + +2001-12-19 Werner Koch <wk@gnupg.org> + + * keyformat.txt: New. + +2001-12-19 Marcus Brinkmann <marcus@g10code.de> + + * query.c (start_pinentry): Add new argument to assuan_pipe_connect. + +2001-12-18 Werner Koch <wk@gnupg.org> + + * Makefile.am: Use LIBGCRYPT macros + +2001-12-14 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (main): New option --batch. New option --debug-wait + n, so that it is possible to attach gdb when used in server mode. + * query.c (agent_askpin): Don't ask in batch mode. + + * command.c: Removed the conversion macros as they are now in + ../common/util.h. + +2001-12-14 Marcus Brinkmann <marcus@g10code.de> + + * query.c (LINELENGTH): Removed. + (agent_askpin): Use ASSUAN_LINELENGTH, not LINELENGTH. + +2001-11-19 Werner Koch <wk@gnupg.org> + + * gpg-agent.c: Removed all GUI code, removed code for old + protocol. New code to use the Assuan protocol as a server and + also to communicate with a new ask-passphrase utility. + +2000-11-22 Werner Koch <wk@gnupg.org> + + * gpg-agent.c (main): csh support by Dan Winship, new options --sh + and --csh and set default by consulting $SHELL. + +Mon Aug 21 17:59:17 CEST 2000 Werner Koch <wk@openit.de> + + * gpg-agent.c (passphrase_dialog): Cleanup the window and added the + user supplied text to the window. + (main): Fixed segv in gtk_init when used without a command to start. + + * gpg-agent.c: --flush option. + (req_flush): New. + (req_clear_passphrase): Implemented. + +Fri Aug 18 14:27:14 CEST 2000 Werner Koch <wk@openit.de> + + * gpg-agent.c: New. + * Makefile.am: New. + + + Copyright 2001, 2002, 2003, 2004, 2005, + 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc. + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +Local Variables: +buffer-read-only: t +End: diff --git a/agent/Makefile.am b/agent/Makefile.am new file mode 100644 index 0000000..f0ba964 --- /dev/null +++ b/agent/Makefile.am @@ -0,0 +1,118 @@ +# Copyright (C) 2001, 2003, 2004, 2005 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 <https://www.gnu.org/licenses/>. + +## Process this file with automake to produce Makefile.in + +bin_PROGRAMS = gpg-agent +libexec_PROGRAMS = gpg-protect-tool +if !HAVE_W32CE_SYSTEM +# fixme: Do no use simple-pwquery for preset-passphrase. +libexec_PROGRAMS += gpg-preset-passphrase +endif +noinst_PROGRAMS = $(TESTS) + +EXTRA_DIST = ChangeLog-2011 \ + gpg-agent-w32info.rc gpg-agent.w32-manifest.in \ + all-tests.scm + + +AM_CPPFLAGS = + +include $(top_srcdir)/am/cmacros.am + +if HAVE_W32_SYSTEM +gpg_agent_robjs = $(resource_objs) gpg-agent-w32info.o +gpg-agent-w32info.o : gpg-agent.w32-manifest +else +gpg_agent_robjs = +endif + +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) + +gpg_agent_SOURCES = \ + gpg-agent.c agent.h \ + command.c command-ssh.c \ + call-pinentry.c \ + cache.c \ + trans.c \ + findkey.c \ + pksign.c \ + pkdecrypt.c \ + genkey.c \ + protect.c \ + trustlist.c \ + divert-scd.c \ + cvt-openpgp.c cvt-openpgp.h \ + call-scd.c \ + learncard.c + +common_libs = $(libcommon) +commonpth_libs = $(libcommonpth) +if HAVE_W32CE_SYSTEM +pwquery_libs = +else +pwquery_libs = ../common/libsimple-pwquery.a +endif + + +gpg_agent_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) \ + $(INCICONV) +gpg_agent_LDADD = $(commonpth_libs) \ + $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \ + $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ + $(gpg_agent_robjs) +gpg_agent_LDFLAGS = $(extra_bin_ldflags) +gpg_agent_DEPENDENCIES = $(gpg_agent_robjs) + +gpg_protect_tool_SOURCES = \ + protect-tool.c \ + protect.c cvt-openpgp.c + +gpg_protect_tool_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) \ + $(INCICONV) +gpg_protect_tool_LDADD = $(common_libs) $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) \ + $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) + +gpg_preset_passphrase_SOURCES = \ + preset-passphrase.c + +# Needs $(NETLIBS) for libsimple-pwquery.la. +gpg_preset_passphrase_LDADD = \ + $(pwquery_libs) $(common_libs) $(LIBASSUAN_LIBS) \ + $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) + + +# Make sure that all libs are build before we use them. This is +# important for things like make -j2. +$(PROGRAMS): $(common_libs) $(commonpth_libs) $(pwquery_libs) + + + +# +# Module tests +# +if DISABLE_TESTS +TESTS = +else +TESTS = t-protect +endif + +t_common_ldadd = $(common_libs) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(LIBICONV) $(NETLIBS) + +t_protect_SOURCES = t-protect.c protect.c +t_protect_LDADD = $(t_common_ldadd) diff --git a/agent/Makefile.in b/agent/Makefile.in new file mode 100644 index 0000000..ab8735f --- /dev/null +++ b/agent/Makefile.in @@ -0,0 +1,1408 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# Copyright (C) 2001, 2003, 2004, 2005 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 <https://www.gnu.org/licenses/>. + +# cmacros.am - C macro definitions +# Copyright (C) 2004 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 <https://www.gnu.org/licenses/>. + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +bin_PROGRAMS = gpg-agent$(EXEEXT) +libexec_PROGRAMS = gpg-protect-tool$(EXEEXT) $(am__EXEEXT_1) +# fixme: Do no use simple-pwquery for preset-passphrase. +@HAVE_W32CE_SYSTEM_FALSE@am__append_1 = gpg-preset-passphrase +noinst_PROGRAMS = $(am__EXEEXT_2) +@HAVE_DOSISH_SYSTEM_FALSE@am__append_2 = -DGNUPG_BINDIR="\"$(bindir)\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LIBEXECDIR="\"$(libexecdir)\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LIBDIR="\"$(libdir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_DATADIR="\"$(datadir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_SYSCONFDIR="\"$(sysconfdir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LOCALSTATEDIR="\"$(localstatedir)\"" + + +# If a specific protect tool program has been defined, pass its name +# to cc. Note that these macros should not be used directly but via +# the gnupg_module_name function. +@GNUPG_AGENT_PGM_TRUE@am__append_3 = -DGNUPG_DEFAULT_AGENT="\"@GNUPG_AGENT_PGM@\"" +@GNUPG_PINENTRY_PGM_TRUE@am__append_4 = -DGNUPG_DEFAULT_PINENTRY="\"@GNUPG_PINENTRY_PGM@\"" +@GNUPG_SCDAEMON_PGM_TRUE@am__append_5 = -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\"" +@GNUPG_DIRMNGR_PGM_TRUE@am__append_6 = -DGNUPG_DEFAULT_DIRMNGR="\"@GNUPG_DIRMNGR_PGM@\"" +@GNUPG_PROTECT_TOOL_PGM_TRUE@am__append_7 = -DGNUPG_DEFAULT_PROTECT_TOOL="\"@GNUPG_PROTECT_TOOL_PGM@\"" +@GNUPG_DIRMNGR_LDAP_PGM_TRUE@am__append_8 = -DGNUPG_DEFAULT_DIRMNGR_LDAP="\"@GNUPG_DIRMNGR_LDAP_PGM@\"" +@DISABLE_TESTS_FALSE@TESTS = t-protect$(EXEEXT) +subdir = agent +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/autobuild.m4 \ + $(top_srcdir)/m4/codeset.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/isc-posix.m4 $(top_srcdir)/m4/ksba.m4 \ + $(top_srcdir)/m4/lcmessage.m4 $(top_srcdir)/m4/ldap.m4 \ + $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \ + $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libassuan.m4 \ + $(top_srcdir)/m4/libgcrypt.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/npth.m4 $(top_srcdir)/m4/ntbtls.m4 \ + $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/m4/po.m4 \ + $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/readline.m4 \ + $(top_srcdir)/m4/socklen.m4 $(top_srcdir)/m4/sys_socket_h.m4 \ + $(top_srcdir)/m4/tar-ustar.m4 $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = gpg-agent.w32-manifest +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libexecdir)" +@HAVE_W32CE_SYSTEM_FALSE@am__EXEEXT_1 = \ +@HAVE_W32CE_SYSTEM_FALSE@ gpg-preset-passphrase$(EXEEXT) +@DISABLE_TESTS_FALSE@am__EXEEXT_2 = t-protect$(EXEEXT) +PROGRAMS = $(bin_PROGRAMS) $(libexec_PROGRAMS) $(noinst_PROGRAMS) +am_gpg_agent_OBJECTS = gpg_agent-gpg-agent.$(OBJEXT) \ + gpg_agent-command.$(OBJEXT) gpg_agent-command-ssh.$(OBJEXT) \ + gpg_agent-call-pinentry.$(OBJEXT) gpg_agent-cache.$(OBJEXT) \ + gpg_agent-trans.$(OBJEXT) gpg_agent-findkey.$(OBJEXT) \ + gpg_agent-pksign.$(OBJEXT) gpg_agent-pkdecrypt.$(OBJEXT) \ + gpg_agent-genkey.$(OBJEXT) gpg_agent-protect.$(OBJEXT) \ + gpg_agent-trustlist.$(OBJEXT) gpg_agent-divert-scd.$(OBJEXT) \ + gpg_agent-cvt-openpgp.$(OBJEXT) gpg_agent-call-scd.$(OBJEXT) \ + gpg_agent-learncard.$(OBJEXT) +gpg_agent_OBJECTS = $(am_gpg_agent_OBJECTS) +am__DEPENDENCIES_1 = +@HAVE_W32_SYSTEM_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) \ +@HAVE_W32_SYSTEM_TRUE@ gpg-agent-w32info.o +gpg_agent_LINK = $(CCLD) $(gpg_agent_CFLAGS) $(CFLAGS) \ + $(gpg_agent_LDFLAGS) $(LDFLAGS) -o $@ +am_gpg_preset_passphrase_OBJECTS = preset-passphrase.$(OBJEXT) +gpg_preset_passphrase_OBJECTS = $(am_gpg_preset_passphrase_OBJECTS) +@HAVE_W32CE_SYSTEM_FALSE@am__DEPENDENCIES_3 = \ +@HAVE_W32CE_SYSTEM_FALSE@ ../common/libsimple-pwquery.a +gpg_preset_passphrase_DEPENDENCIES = $(am__DEPENDENCIES_3) \ + $(common_libs) $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am_gpg_protect_tool_OBJECTS = gpg_protect_tool-protect-tool.$(OBJEXT) \ + gpg_protect_tool-protect.$(OBJEXT) \ + gpg_protect_tool-cvt-openpgp.$(OBJEXT) +gpg_protect_tool_OBJECTS = $(am_gpg_protect_tool_OBJECTS) +gpg_protect_tool_DEPENDENCIES = $(common_libs) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +gpg_protect_tool_LINK = $(CCLD) $(gpg_protect_tool_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_t_protect_OBJECTS = t-protect.$(OBJEXT) protect.$(OBJEXT) +t_protect_OBJECTS = $(am_t_protect_OBJECTS) +am__DEPENDENCIES_4 = $(common_libs) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +t_protect_DEPENDENCIES = $(am__DEPENDENCIES_4) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/gpg_agent-cache.Po \ + ./$(DEPDIR)/gpg_agent-call-pinentry.Po \ + ./$(DEPDIR)/gpg_agent-call-scd.Po \ + ./$(DEPDIR)/gpg_agent-command-ssh.Po \ + ./$(DEPDIR)/gpg_agent-command.Po \ + ./$(DEPDIR)/gpg_agent-cvt-openpgp.Po \ + ./$(DEPDIR)/gpg_agent-divert-scd.Po \ + ./$(DEPDIR)/gpg_agent-findkey.Po \ + ./$(DEPDIR)/gpg_agent-genkey.Po \ + ./$(DEPDIR)/gpg_agent-gpg-agent.Po \ + ./$(DEPDIR)/gpg_agent-learncard.Po \ + ./$(DEPDIR)/gpg_agent-pkdecrypt.Po \ + ./$(DEPDIR)/gpg_agent-pksign.Po \ + ./$(DEPDIR)/gpg_agent-protect.Po \ + ./$(DEPDIR)/gpg_agent-trans.Po \ + ./$(DEPDIR)/gpg_agent-trustlist.Po \ + ./$(DEPDIR)/gpg_protect_tool-cvt-openpgp.Po \ + ./$(DEPDIR)/gpg_protect_tool-protect-tool.Po \ + ./$(DEPDIR)/gpg_protect_tool-protect.Po \ + ./$(DEPDIR)/preset-passphrase.Po ./$(DEPDIR)/protect.Po \ + ./$(DEPDIR)/t-protect.Po +am__mv = mv -f +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(gpg_agent_SOURCES) $(gpg_preset_passphrase_SOURCES) \ + $(gpg_protect_tool_SOURCES) $(t_protect_SOURCES) +DIST_SOURCES = $(gpg_agent_SOURCES) $(gpg_preset_passphrase_SOURCES) \ + $(gpg_protect_tool_SOURCES) $(t_protect_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__tty_colors_dummy = \ + mgn= red= grn= lgn= blu= brg= std=; \ + am__color_tests=no +am__tty_colors = { \ + $(am__tty_colors_dummy); \ + if test "X$(AM_COLOR_TESTS)" = Xno; then \ + am__color_tests=no; \ + elif test "X$(AM_COLOR_TESTS)" = Xalways; then \ + am__color_tests=yes; \ + elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \ + am__color_tests=yes; \ + fi; \ + if test $$am__color_tests = yes; then \ + red='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + fi; \ +} +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(srcdir)/gpg-agent.w32-manifest.in \ + $(top_srcdir)/am/cmacros.am $(top_srcdir)/build-aux/depcomp \ + $(top_srcdir)/build-aux/mkinstalldirs +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +AWK_HEX_NUMBER_OPTION = @AWK_HEX_NUMBER_OPTION@ +BUILD_FILEVERSION = @BUILD_FILEVERSION@ +BUILD_HOSTNAME = @BUILD_HOSTNAME@ +BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@ +BUILD_REVISION = @BUILD_REVISION@ +BUILD_TIMESTAMP = @BUILD_TIMESTAMP@ +BUILD_VERSION = @BUILD_VERSION@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DL_LIBS = @DL_LIBS@ +DNSLIBS = @DNSLIBS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENCFS = @ENCFS@ +EXEEXT = @EXEEXT@ +FUSERMOUNT = @FUSERMOUNT@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUPG_AGENT_PGM = @GNUPG_AGENT_PGM@ +GNUPG_DIRMNGR_LDAP_PGM = @GNUPG_DIRMNGR_LDAP_PGM@ +GNUPG_DIRMNGR_PGM = @GNUPG_DIRMNGR_PGM@ +GNUPG_PINENTRY_PGM = @GNUPG_PINENTRY_PGM@ +GNUPG_PROTECT_TOOL_PGM = @GNUPG_PROTECT_TOOL_PGM@ +GNUPG_SCDAEMON_PGM = @GNUPG_SCDAEMON_PGM@ +GPGKEYS_LDAP = @GPGKEYS_LDAP@ +GPGRT_CONFIG = @GPGRT_CONFIG@ +GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@ +GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@ +GPG_ERROR_LIBS = @GPG_ERROR_LIBS@ +GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@ +GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +KSBA_CFLAGS = @KSBA_CFLAGS@ +KSBA_CONFIG = @KSBA_CONFIG@ +KSBA_LIBS = @KSBA_LIBS@ +LBER_LIBS = @LBER_LIBS@ +LDAPLIBS = @LDAPLIBS@ +LDAP_CPPFLAGS = @LDAP_CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@ +LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@ +LIBASSUAN_LIBS = @LIBASSUAN_LIBS@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBGNUTLS_CFLAGS = @LIBGNUTLS_CFLAGS@ +LIBGNUTLS_LIBS = @LIBGNUTLS_LIBS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBOBJS = @LIBOBJS@ +LIBREADLINE = @LIBREADLINE@ +LIBS = @LIBS@ +LIBUSB_CPPFLAGS = @LIBUSB_CPPFLAGS@ +LIBUSB_LIBS = @LIBUSB_LIBS@ +LIBUTIL_LIBS = @LIBUTIL_LIBS@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NETLIBS = @NETLIBS@ +NPTH_CFLAGS = @NPTH_CFLAGS@ +NPTH_CONFIG = @NPTH_CONFIG@ +NPTH_LIBS = @NPTH_LIBS@ +NTBTLS_CFLAGS = @NTBTLS_CFLAGS@ +NTBTLS_CONFIG = @NTBTLS_CONFIG@ +NTBTLS_LIBS = @NTBTLS_LIBS@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_GT = @PACKAGE_GT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POSUB = @POSUB@ +RANLIB = @RANLIB@ +SENDMAIL = @SENDMAIL@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SHRED = @SHRED@ +SQLITE3_CFLAGS = @SQLITE3_CFLAGS@ +SQLITE3_LIBS = @SQLITE3_LIBS@ +STRIP = @STRIP@ +SYSROOT = @SYSROOT@ +SYS_SOCKET_H = @SYS_SOCKET_H@ +TAR = @TAR@ +USE_C99_CFLAGS = @USE_C99_CFLAGS@ +USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +W32SOCKLIBS = @W32SOCKLIBS@ +WINDRES = @WINDRES@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +YAT2M = @YAT2M@ +ZLIBS = @ZLIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = $(datadir)/locale +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +EXTRA_DIST = ChangeLog-2011 \ + gpg-agent-w32info.rc gpg-agent.w32-manifest.in \ + all-tests.scm + + +# NB: AM_CFLAGS may also be used by tools running on the build +# platform to create source files. +AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" $(am__append_2) \ + $(am__append_3) $(am__append_4) $(am__append_5) \ + $(am__append_6) $(am__append_7) $(am__append_8) +@HAVE_W32CE_SYSTEM_FALSE@extra_sys_libs = + +# Under Windows we use LockFileEx. WindowsCE provides this only on +# the WindowsMobile 6 platform and thus we need to use the coredll6 +# import library. We also want to use a stacksize of 256k instead of +# the 2MB which is the default with cegcc. 256k is the largest stack +# we use with pth. +@HAVE_W32CE_SYSTEM_TRUE@extra_sys_libs = -lcoredll6 +@HAVE_W32CE_SYSTEM_FALSE@extra_bin_ldflags = +@HAVE_W32CE_SYSTEM_TRUE@extra_bin_ldflags = -Wl,--stack=0x40000 +resource_objs = + +# Convenience macros +libcommon = ../common/libcommon.a +libcommonpth = ../common/libcommonpth.a +libcommontls = ../common/libcommontls.a +libcommontlsnpth = ../common/libcommontlsnpth.a +@HAVE_W32_SYSTEM_FALSE@gpg_agent_robjs = +@HAVE_W32_SYSTEM_TRUE@gpg_agent_robjs = $(resource_objs) gpg-agent-w32info.o +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) +gpg_agent_SOURCES = \ + gpg-agent.c agent.h \ + command.c command-ssh.c \ + call-pinentry.c \ + cache.c \ + trans.c \ + findkey.c \ + pksign.c \ + pkdecrypt.c \ + genkey.c \ + protect.c \ + trustlist.c \ + divert-scd.c \ + cvt-openpgp.c cvt-openpgp.h \ + call-scd.c \ + learncard.c + +common_libs = $(libcommon) +commonpth_libs = $(libcommonpth) +@HAVE_W32CE_SYSTEM_FALSE@pwquery_libs = ../common/libsimple-pwquery.a +@HAVE_W32CE_SYSTEM_TRUE@pwquery_libs = +gpg_agent_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) \ + $(INCICONV) + +gpg_agent_LDADD = $(commonpth_libs) \ + $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \ + $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ + $(gpg_agent_robjs) + +gpg_agent_LDFLAGS = $(extra_bin_ldflags) +gpg_agent_DEPENDENCIES = $(gpg_agent_robjs) +gpg_protect_tool_SOURCES = \ + protect-tool.c \ + protect.c cvt-openpgp.c + +gpg_protect_tool_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) \ + $(INCICONV) + +gpg_protect_tool_LDADD = $(common_libs) $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) \ + $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) + +gpg_preset_passphrase_SOURCES = \ + preset-passphrase.c + + +# Needs $(NETLIBS) for libsimple-pwquery.la. +gpg_preset_passphrase_LDADD = \ + $(pwquery_libs) $(common_libs) $(LIBASSUAN_LIBS) \ + $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) + +t_common_ldadd = $(common_libs) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(LIBICONV) $(NETLIBS) + +t_protect_SOURCES = t-protect.c protect.c +t_protect_LDADD = $(t_common_ldadd) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj .rc +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/am/cmacros.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu agent/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu agent/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; +$(top_srcdir)/am/cmacros.am $(am__empty): + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +gpg-agent.w32-manifest: $(top_builddir)/config.status $(srcdir)/gpg-agent.w32-manifest.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +install-libexecPROGRAMS: $(libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(libexecdir)" && rm -f $$files + +clean-libexecPROGRAMS: + -test -z "$(libexec_PROGRAMS)" || rm -f $(libexec_PROGRAMS) + +clean-noinstPROGRAMS: + -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS) + +gpg-agent$(EXEEXT): $(gpg_agent_OBJECTS) $(gpg_agent_DEPENDENCIES) $(EXTRA_gpg_agent_DEPENDENCIES) + @rm -f gpg-agent$(EXEEXT) + $(AM_V_CCLD)$(gpg_agent_LINK) $(gpg_agent_OBJECTS) $(gpg_agent_LDADD) $(LIBS) + +gpg-preset-passphrase$(EXEEXT): $(gpg_preset_passphrase_OBJECTS) $(gpg_preset_passphrase_DEPENDENCIES) $(EXTRA_gpg_preset_passphrase_DEPENDENCIES) + @rm -f gpg-preset-passphrase$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(gpg_preset_passphrase_OBJECTS) $(gpg_preset_passphrase_LDADD) $(LIBS) + +gpg-protect-tool$(EXEEXT): $(gpg_protect_tool_OBJECTS) $(gpg_protect_tool_DEPENDENCIES) $(EXTRA_gpg_protect_tool_DEPENDENCIES) + @rm -f gpg-protect-tool$(EXEEXT) + $(AM_V_CCLD)$(gpg_protect_tool_LINK) $(gpg_protect_tool_OBJECTS) $(gpg_protect_tool_LDADD) $(LIBS) + +t-protect$(EXEEXT): $(t_protect_OBJECTS) $(t_protect_DEPENDENCIES) $(EXTRA_t_protect_DEPENDENCIES) + @rm -f t-protect$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(t_protect_OBJECTS) $(t_protect_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-cache.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-call-pinentry.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-call-scd.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-command-ssh.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-command.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-cvt-openpgp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-divert-scd.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-findkey.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-genkey.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-gpg-agent.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-learncard.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-pkdecrypt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-pksign.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-protect.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-trans.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_agent-trustlist.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_protect_tool-cvt-openpgp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_protect_tool-protect-tool.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_protect_tool-protect.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/preset-passphrase.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/protect.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t-protect.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +gpg_agent-gpg-agent.o: gpg-agent.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-gpg-agent.o -MD -MP -MF $(DEPDIR)/gpg_agent-gpg-agent.Tpo -c -o gpg_agent-gpg-agent.o `test -f 'gpg-agent.c' || echo '$(srcdir)/'`gpg-agent.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-gpg-agent.Tpo $(DEPDIR)/gpg_agent-gpg-agent.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpg-agent.c' object='gpg_agent-gpg-agent.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-gpg-agent.o `test -f 'gpg-agent.c' || echo '$(srcdir)/'`gpg-agent.c + +gpg_agent-gpg-agent.obj: gpg-agent.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-gpg-agent.obj -MD -MP -MF $(DEPDIR)/gpg_agent-gpg-agent.Tpo -c -o gpg_agent-gpg-agent.obj `if test -f 'gpg-agent.c'; then $(CYGPATH_W) 'gpg-agent.c'; else $(CYGPATH_W) '$(srcdir)/gpg-agent.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-gpg-agent.Tpo $(DEPDIR)/gpg_agent-gpg-agent.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpg-agent.c' object='gpg_agent-gpg-agent.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-gpg-agent.obj `if test -f 'gpg-agent.c'; then $(CYGPATH_W) 'gpg-agent.c'; else $(CYGPATH_W) '$(srcdir)/gpg-agent.c'; fi` + +gpg_agent-command.o: command.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-command.o -MD -MP -MF $(DEPDIR)/gpg_agent-command.Tpo -c -o gpg_agent-command.o `test -f 'command.c' || echo '$(srcdir)/'`command.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-command.Tpo $(DEPDIR)/gpg_agent-command.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='command.c' object='gpg_agent-command.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-command.o `test -f 'command.c' || echo '$(srcdir)/'`command.c + +gpg_agent-command.obj: command.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-command.obj -MD -MP -MF $(DEPDIR)/gpg_agent-command.Tpo -c -o gpg_agent-command.obj `if test -f 'command.c'; then $(CYGPATH_W) 'command.c'; else $(CYGPATH_W) '$(srcdir)/command.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-command.Tpo $(DEPDIR)/gpg_agent-command.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='command.c' object='gpg_agent-command.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-command.obj `if test -f 'command.c'; then $(CYGPATH_W) 'command.c'; else $(CYGPATH_W) '$(srcdir)/command.c'; fi` + +gpg_agent-command-ssh.o: command-ssh.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-command-ssh.o -MD -MP -MF $(DEPDIR)/gpg_agent-command-ssh.Tpo -c -o gpg_agent-command-ssh.o `test -f 'command-ssh.c' || echo '$(srcdir)/'`command-ssh.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-command-ssh.Tpo $(DEPDIR)/gpg_agent-command-ssh.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='command-ssh.c' object='gpg_agent-command-ssh.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-command-ssh.o `test -f 'command-ssh.c' || echo '$(srcdir)/'`command-ssh.c + +gpg_agent-command-ssh.obj: command-ssh.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-command-ssh.obj -MD -MP -MF $(DEPDIR)/gpg_agent-command-ssh.Tpo -c -o gpg_agent-command-ssh.obj `if test -f 'command-ssh.c'; then $(CYGPATH_W) 'command-ssh.c'; else $(CYGPATH_W) '$(srcdir)/command-ssh.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-command-ssh.Tpo $(DEPDIR)/gpg_agent-command-ssh.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='command-ssh.c' object='gpg_agent-command-ssh.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-command-ssh.obj `if test -f 'command-ssh.c'; then $(CYGPATH_W) 'command-ssh.c'; else $(CYGPATH_W) '$(srcdir)/command-ssh.c'; fi` + +gpg_agent-call-pinentry.o: call-pinentry.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-call-pinentry.o -MD -MP -MF $(DEPDIR)/gpg_agent-call-pinentry.Tpo -c -o gpg_agent-call-pinentry.o `test -f 'call-pinentry.c' || echo '$(srcdir)/'`call-pinentry.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-call-pinentry.Tpo $(DEPDIR)/gpg_agent-call-pinentry.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='call-pinentry.c' object='gpg_agent-call-pinentry.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-call-pinentry.o `test -f 'call-pinentry.c' || echo '$(srcdir)/'`call-pinentry.c + +gpg_agent-call-pinentry.obj: call-pinentry.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-call-pinentry.obj -MD -MP -MF $(DEPDIR)/gpg_agent-call-pinentry.Tpo -c -o gpg_agent-call-pinentry.obj `if test -f 'call-pinentry.c'; then $(CYGPATH_W) 'call-pinentry.c'; else $(CYGPATH_W) '$(srcdir)/call-pinentry.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-call-pinentry.Tpo $(DEPDIR)/gpg_agent-call-pinentry.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='call-pinentry.c' object='gpg_agent-call-pinentry.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-call-pinentry.obj `if test -f 'call-pinentry.c'; then $(CYGPATH_W) 'call-pinentry.c'; else $(CYGPATH_W) '$(srcdir)/call-pinentry.c'; fi` + +gpg_agent-cache.o: cache.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-cache.o -MD -MP -MF $(DEPDIR)/gpg_agent-cache.Tpo -c -o gpg_agent-cache.o `test -f 'cache.c' || echo '$(srcdir)/'`cache.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-cache.Tpo $(DEPDIR)/gpg_agent-cache.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cache.c' object='gpg_agent-cache.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-cache.o `test -f 'cache.c' || echo '$(srcdir)/'`cache.c + +gpg_agent-cache.obj: cache.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-cache.obj -MD -MP -MF $(DEPDIR)/gpg_agent-cache.Tpo -c -o gpg_agent-cache.obj `if test -f 'cache.c'; then $(CYGPATH_W) 'cache.c'; else $(CYGPATH_W) '$(srcdir)/cache.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-cache.Tpo $(DEPDIR)/gpg_agent-cache.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cache.c' object='gpg_agent-cache.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-cache.obj `if test -f 'cache.c'; then $(CYGPATH_W) 'cache.c'; else $(CYGPATH_W) '$(srcdir)/cache.c'; fi` + +gpg_agent-trans.o: trans.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-trans.o -MD -MP -MF $(DEPDIR)/gpg_agent-trans.Tpo -c -o gpg_agent-trans.o `test -f 'trans.c' || echo '$(srcdir)/'`trans.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-trans.Tpo $(DEPDIR)/gpg_agent-trans.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='trans.c' object='gpg_agent-trans.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-trans.o `test -f 'trans.c' || echo '$(srcdir)/'`trans.c + +gpg_agent-trans.obj: trans.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-trans.obj -MD -MP -MF $(DEPDIR)/gpg_agent-trans.Tpo -c -o gpg_agent-trans.obj `if test -f 'trans.c'; then $(CYGPATH_W) 'trans.c'; else $(CYGPATH_W) '$(srcdir)/trans.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-trans.Tpo $(DEPDIR)/gpg_agent-trans.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='trans.c' object='gpg_agent-trans.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-trans.obj `if test -f 'trans.c'; then $(CYGPATH_W) 'trans.c'; else $(CYGPATH_W) '$(srcdir)/trans.c'; fi` + +gpg_agent-findkey.o: findkey.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-findkey.o -MD -MP -MF $(DEPDIR)/gpg_agent-findkey.Tpo -c -o gpg_agent-findkey.o `test -f 'findkey.c' || echo '$(srcdir)/'`findkey.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-findkey.Tpo $(DEPDIR)/gpg_agent-findkey.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='findkey.c' object='gpg_agent-findkey.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-findkey.o `test -f 'findkey.c' || echo '$(srcdir)/'`findkey.c + +gpg_agent-findkey.obj: findkey.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-findkey.obj -MD -MP -MF $(DEPDIR)/gpg_agent-findkey.Tpo -c -o gpg_agent-findkey.obj `if test -f 'findkey.c'; then $(CYGPATH_W) 'findkey.c'; else $(CYGPATH_W) '$(srcdir)/findkey.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-findkey.Tpo $(DEPDIR)/gpg_agent-findkey.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='findkey.c' object='gpg_agent-findkey.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-findkey.obj `if test -f 'findkey.c'; then $(CYGPATH_W) 'findkey.c'; else $(CYGPATH_W) '$(srcdir)/findkey.c'; fi` + +gpg_agent-pksign.o: pksign.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-pksign.o -MD -MP -MF $(DEPDIR)/gpg_agent-pksign.Tpo -c -o gpg_agent-pksign.o `test -f 'pksign.c' || echo '$(srcdir)/'`pksign.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-pksign.Tpo $(DEPDIR)/gpg_agent-pksign.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='pksign.c' object='gpg_agent-pksign.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-pksign.o `test -f 'pksign.c' || echo '$(srcdir)/'`pksign.c + +gpg_agent-pksign.obj: pksign.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-pksign.obj -MD -MP -MF $(DEPDIR)/gpg_agent-pksign.Tpo -c -o gpg_agent-pksign.obj `if test -f 'pksign.c'; then $(CYGPATH_W) 'pksign.c'; else $(CYGPATH_W) '$(srcdir)/pksign.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-pksign.Tpo $(DEPDIR)/gpg_agent-pksign.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='pksign.c' object='gpg_agent-pksign.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-pksign.obj `if test -f 'pksign.c'; then $(CYGPATH_W) 'pksign.c'; else $(CYGPATH_W) '$(srcdir)/pksign.c'; fi` + +gpg_agent-pkdecrypt.o: pkdecrypt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-pkdecrypt.o -MD -MP -MF $(DEPDIR)/gpg_agent-pkdecrypt.Tpo -c -o gpg_agent-pkdecrypt.o `test -f 'pkdecrypt.c' || echo '$(srcdir)/'`pkdecrypt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-pkdecrypt.Tpo $(DEPDIR)/gpg_agent-pkdecrypt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='pkdecrypt.c' object='gpg_agent-pkdecrypt.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-pkdecrypt.o `test -f 'pkdecrypt.c' || echo '$(srcdir)/'`pkdecrypt.c + +gpg_agent-pkdecrypt.obj: pkdecrypt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-pkdecrypt.obj -MD -MP -MF $(DEPDIR)/gpg_agent-pkdecrypt.Tpo -c -o gpg_agent-pkdecrypt.obj `if test -f 'pkdecrypt.c'; then $(CYGPATH_W) 'pkdecrypt.c'; else $(CYGPATH_W) '$(srcdir)/pkdecrypt.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-pkdecrypt.Tpo $(DEPDIR)/gpg_agent-pkdecrypt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='pkdecrypt.c' object='gpg_agent-pkdecrypt.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-pkdecrypt.obj `if test -f 'pkdecrypt.c'; then $(CYGPATH_W) 'pkdecrypt.c'; else $(CYGPATH_W) '$(srcdir)/pkdecrypt.c'; fi` + +gpg_agent-genkey.o: genkey.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-genkey.o -MD -MP -MF $(DEPDIR)/gpg_agent-genkey.Tpo -c -o gpg_agent-genkey.o `test -f 'genkey.c' || echo '$(srcdir)/'`genkey.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-genkey.Tpo $(DEPDIR)/gpg_agent-genkey.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='genkey.c' object='gpg_agent-genkey.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-genkey.o `test -f 'genkey.c' || echo '$(srcdir)/'`genkey.c + +gpg_agent-genkey.obj: genkey.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-genkey.obj -MD -MP -MF $(DEPDIR)/gpg_agent-genkey.Tpo -c -o gpg_agent-genkey.obj `if test -f 'genkey.c'; then $(CYGPATH_W) 'genkey.c'; else $(CYGPATH_W) '$(srcdir)/genkey.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-genkey.Tpo $(DEPDIR)/gpg_agent-genkey.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='genkey.c' object='gpg_agent-genkey.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-genkey.obj `if test -f 'genkey.c'; then $(CYGPATH_W) 'genkey.c'; else $(CYGPATH_W) '$(srcdir)/genkey.c'; fi` + +gpg_agent-protect.o: protect.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-protect.o -MD -MP -MF $(DEPDIR)/gpg_agent-protect.Tpo -c -o gpg_agent-protect.o `test -f 'protect.c' || echo '$(srcdir)/'`protect.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-protect.Tpo $(DEPDIR)/gpg_agent-protect.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protect.c' object='gpg_agent-protect.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-protect.o `test -f 'protect.c' || echo '$(srcdir)/'`protect.c + +gpg_agent-protect.obj: protect.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-protect.obj -MD -MP -MF $(DEPDIR)/gpg_agent-protect.Tpo -c -o gpg_agent-protect.obj `if test -f 'protect.c'; then $(CYGPATH_W) 'protect.c'; else $(CYGPATH_W) '$(srcdir)/protect.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-protect.Tpo $(DEPDIR)/gpg_agent-protect.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protect.c' object='gpg_agent-protect.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-protect.obj `if test -f 'protect.c'; then $(CYGPATH_W) 'protect.c'; else $(CYGPATH_W) '$(srcdir)/protect.c'; fi` + +gpg_agent-trustlist.o: trustlist.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-trustlist.o -MD -MP -MF $(DEPDIR)/gpg_agent-trustlist.Tpo -c -o gpg_agent-trustlist.o `test -f 'trustlist.c' || echo '$(srcdir)/'`trustlist.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-trustlist.Tpo $(DEPDIR)/gpg_agent-trustlist.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='trustlist.c' object='gpg_agent-trustlist.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-trustlist.o `test -f 'trustlist.c' || echo '$(srcdir)/'`trustlist.c + +gpg_agent-trustlist.obj: trustlist.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-trustlist.obj -MD -MP -MF $(DEPDIR)/gpg_agent-trustlist.Tpo -c -o gpg_agent-trustlist.obj `if test -f 'trustlist.c'; then $(CYGPATH_W) 'trustlist.c'; else $(CYGPATH_W) '$(srcdir)/trustlist.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-trustlist.Tpo $(DEPDIR)/gpg_agent-trustlist.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='trustlist.c' object='gpg_agent-trustlist.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-trustlist.obj `if test -f 'trustlist.c'; then $(CYGPATH_W) 'trustlist.c'; else $(CYGPATH_W) '$(srcdir)/trustlist.c'; fi` + +gpg_agent-divert-scd.o: divert-scd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-divert-scd.o -MD -MP -MF $(DEPDIR)/gpg_agent-divert-scd.Tpo -c -o gpg_agent-divert-scd.o `test -f 'divert-scd.c' || echo '$(srcdir)/'`divert-scd.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-divert-scd.Tpo $(DEPDIR)/gpg_agent-divert-scd.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='divert-scd.c' object='gpg_agent-divert-scd.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-divert-scd.o `test -f 'divert-scd.c' || echo '$(srcdir)/'`divert-scd.c + +gpg_agent-divert-scd.obj: divert-scd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-divert-scd.obj -MD -MP -MF $(DEPDIR)/gpg_agent-divert-scd.Tpo -c -o gpg_agent-divert-scd.obj `if test -f 'divert-scd.c'; then $(CYGPATH_W) 'divert-scd.c'; else $(CYGPATH_W) '$(srcdir)/divert-scd.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-divert-scd.Tpo $(DEPDIR)/gpg_agent-divert-scd.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='divert-scd.c' object='gpg_agent-divert-scd.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-divert-scd.obj `if test -f 'divert-scd.c'; then $(CYGPATH_W) 'divert-scd.c'; else $(CYGPATH_W) '$(srcdir)/divert-scd.c'; fi` + +gpg_agent-cvt-openpgp.o: cvt-openpgp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-cvt-openpgp.o -MD -MP -MF $(DEPDIR)/gpg_agent-cvt-openpgp.Tpo -c -o gpg_agent-cvt-openpgp.o `test -f 'cvt-openpgp.c' || echo '$(srcdir)/'`cvt-openpgp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-cvt-openpgp.Tpo $(DEPDIR)/gpg_agent-cvt-openpgp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cvt-openpgp.c' object='gpg_agent-cvt-openpgp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-cvt-openpgp.o `test -f 'cvt-openpgp.c' || echo '$(srcdir)/'`cvt-openpgp.c + +gpg_agent-cvt-openpgp.obj: cvt-openpgp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-cvt-openpgp.obj -MD -MP -MF $(DEPDIR)/gpg_agent-cvt-openpgp.Tpo -c -o gpg_agent-cvt-openpgp.obj `if test -f 'cvt-openpgp.c'; then $(CYGPATH_W) 'cvt-openpgp.c'; else $(CYGPATH_W) '$(srcdir)/cvt-openpgp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-cvt-openpgp.Tpo $(DEPDIR)/gpg_agent-cvt-openpgp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cvt-openpgp.c' object='gpg_agent-cvt-openpgp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-cvt-openpgp.obj `if test -f 'cvt-openpgp.c'; then $(CYGPATH_W) 'cvt-openpgp.c'; else $(CYGPATH_W) '$(srcdir)/cvt-openpgp.c'; fi` + +gpg_agent-call-scd.o: call-scd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-call-scd.o -MD -MP -MF $(DEPDIR)/gpg_agent-call-scd.Tpo -c -o gpg_agent-call-scd.o `test -f 'call-scd.c' || echo '$(srcdir)/'`call-scd.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-call-scd.Tpo $(DEPDIR)/gpg_agent-call-scd.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='call-scd.c' object='gpg_agent-call-scd.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-call-scd.o `test -f 'call-scd.c' || echo '$(srcdir)/'`call-scd.c + +gpg_agent-call-scd.obj: call-scd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-call-scd.obj -MD -MP -MF $(DEPDIR)/gpg_agent-call-scd.Tpo -c -o gpg_agent-call-scd.obj `if test -f 'call-scd.c'; then $(CYGPATH_W) 'call-scd.c'; else $(CYGPATH_W) '$(srcdir)/call-scd.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-call-scd.Tpo $(DEPDIR)/gpg_agent-call-scd.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='call-scd.c' object='gpg_agent-call-scd.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-call-scd.obj `if test -f 'call-scd.c'; then $(CYGPATH_W) 'call-scd.c'; else $(CYGPATH_W) '$(srcdir)/call-scd.c'; fi` + +gpg_agent-learncard.o: learncard.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-learncard.o -MD -MP -MF $(DEPDIR)/gpg_agent-learncard.Tpo -c -o gpg_agent-learncard.o `test -f 'learncard.c' || echo '$(srcdir)/'`learncard.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-learncard.Tpo $(DEPDIR)/gpg_agent-learncard.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='learncard.c' object='gpg_agent-learncard.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-learncard.o `test -f 'learncard.c' || echo '$(srcdir)/'`learncard.c + +gpg_agent-learncard.obj: learncard.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -MT gpg_agent-learncard.obj -MD -MP -MF $(DEPDIR)/gpg_agent-learncard.Tpo -c -o gpg_agent-learncard.obj `if test -f 'learncard.c'; then $(CYGPATH_W) 'learncard.c'; else $(CYGPATH_W) '$(srcdir)/learncard.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_agent-learncard.Tpo $(DEPDIR)/gpg_agent-learncard.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='learncard.c' object='gpg_agent-learncard.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_agent_CFLAGS) $(CFLAGS) -c -o gpg_agent-learncard.obj `if test -f 'learncard.c'; then $(CYGPATH_W) 'learncard.c'; else $(CYGPATH_W) '$(srcdir)/learncard.c'; fi` + +gpg_protect_tool-protect-tool.o: protect-tool.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-protect-tool.o -MD -MP -MF $(DEPDIR)/gpg_protect_tool-protect-tool.Tpo -c -o gpg_protect_tool-protect-tool.o `test -f 'protect-tool.c' || echo '$(srcdir)/'`protect-tool.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_protect_tool-protect-tool.Tpo $(DEPDIR)/gpg_protect_tool-protect-tool.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protect-tool.c' object='gpg_protect_tool-protect-tool.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-protect-tool.o `test -f 'protect-tool.c' || echo '$(srcdir)/'`protect-tool.c + +gpg_protect_tool-protect-tool.obj: protect-tool.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-protect-tool.obj -MD -MP -MF $(DEPDIR)/gpg_protect_tool-protect-tool.Tpo -c -o gpg_protect_tool-protect-tool.obj `if test -f 'protect-tool.c'; then $(CYGPATH_W) 'protect-tool.c'; else $(CYGPATH_W) '$(srcdir)/protect-tool.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_protect_tool-protect-tool.Tpo $(DEPDIR)/gpg_protect_tool-protect-tool.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protect-tool.c' object='gpg_protect_tool-protect-tool.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-protect-tool.obj `if test -f 'protect-tool.c'; then $(CYGPATH_W) 'protect-tool.c'; else $(CYGPATH_W) '$(srcdir)/protect-tool.c'; fi` + +gpg_protect_tool-protect.o: protect.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-protect.o -MD -MP -MF $(DEPDIR)/gpg_protect_tool-protect.Tpo -c -o gpg_protect_tool-protect.o `test -f 'protect.c' || echo '$(srcdir)/'`protect.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_protect_tool-protect.Tpo $(DEPDIR)/gpg_protect_tool-protect.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protect.c' object='gpg_protect_tool-protect.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-protect.o `test -f 'protect.c' || echo '$(srcdir)/'`protect.c + +gpg_protect_tool-protect.obj: protect.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-protect.obj -MD -MP -MF $(DEPDIR)/gpg_protect_tool-protect.Tpo -c -o gpg_protect_tool-protect.obj `if test -f 'protect.c'; then $(CYGPATH_W) 'protect.c'; else $(CYGPATH_W) '$(srcdir)/protect.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_protect_tool-protect.Tpo $(DEPDIR)/gpg_protect_tool-protect.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protect.c' object='gpg_protect_tool-protect.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-protect.obj `if test -f 'protect.c'; then $(CYGPATH_W) 'protect.c'; else $(CYGPATH_W) '$(srcdir)/protect.c'; fi` + +gpg_protect_tool-cvt-openpgp.o: cvt-openpgp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-cvt-openpgp.o -MD -MP -MF $(DEPDIR)/gpg_protect_tool-cvt-openpgp.Tpo -c -o gpg_protect_tool-cvt-openpgp.o `test -f 'cvt-openpgp.c' || echo '$(srcdir)/'`cvt-openpgp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_protect_tool-cvt-openpgp.Tpo $(DEPDIR)/gpg_protect_tool-cvt-openpgp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cvt-openpgp.c' object='gpg_protect_tool-cvt-openpgp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-cvt-openpgp.o `test -f 'cvt-openpgp.c' || echo '$(srcdir)/'`cvt-openpgp.c + +gpg_protect_tool-cvt-openpgp.obj: cvt-openpgp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -MT gpg_protect_tool-cvt-openpgp.obj -MD -MP -MF $(DEPDIR)/gpg_protect_tool-cvt-openpgp.Tpo -c -o gpg_protect_tool-cvt-openpgp.obj `if test -f 'cvt-openpgp.c'; then $(CYGPATH_W) 'cvt-openpgp.c'; else $(CYGPATH_W) '$(srcdir)/cvt-openpgp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_protect_tool-cvt-openpgp.Tpo $(DEPDIR)/gpg_protect_tool-cvt-openpgp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cvt-openpgp.c' object='gpg_protect_tool-cvt-openpgp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_protect_tool_CFLAGS) $(CFLAGS) -c -o gpg_protect_tool-cvt-openpgp.obj `if test -f 'cvt-openpgp.c'; then $(CYGPATH_W) 'cvt-openpgp.c'; else $(CYGPATH_W) '$(srcdir)/cvt-openpgp.c'; fi` + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +check-TESTS: $(TESTS) + @failed=0; all=0; xfail=0; xpass=0; skip=0; \ + srcdir=$(srcdir); export srcdir; \ + list=' $(TESTS) '; \ + $(am__tty_colors); \ + if test -n "$$list"; then \ + for tst in $$list; do \ + if test -f ./$$tst; then dir=./; \ + elif test -f $$tst; then dir=; \ + else dir="$(srcdir)/"; fi; \ + if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xpass=`expr $$xpass + 1`; \ + failed=`expr $$failed + 1`; \ + col=$$red; res=XPASS; \ + ;; \ + *) \ + col=$$grn; res=PASS; \ + ;; \ + esac; \ + elif test $$? -ne 77; then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xfail=`expr $$xfail + 1`; \ + col=$$lgn; res=XFAIL; \ + ;; \ + *) \ + failed=`expr $$failed + 1`; \ + col=$$red; res=FAIL; \ + ;; \ + esac; \ + else \ + skip=`expr $$skip + 1`; \ + col=$$blu; res=SKIP; \ + fi; \ + echo "$${col}$$res$${std}: $$tst"; \ + done; \ + if test "$$all" -eq 1; then \ + tests="test"; \ + All=""; \ + else \ + tests="tests"; \ + All="All "; \ + fi; \ + if test "$$failed" -eq 0; then \ + if test "$$xfail" -eq 0; then \ + banner="$$All$$all $$tests passed"; \ + else \ + if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \ + banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \ + fi; \ + else \ + if test "$$xpass" -eq 0; then \ + banner="$$failed of $$all $$tests failed"; \ + else \ + if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \ + banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \ + fi; \ + fi; \ + dashes="$$banner"; \ + skipped=""; \ + if test "$$skip" -ne 0; then \ + if test "$$skip" -eq 1; then \ + skipped="($$skip test was not run)"; \ + else \ + skipped="($$skip tests were not run)"; \ + fi; \ + test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$skipped"; \ + fi; \ + report=""; \ + if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \ + report="Please report to $(PACKAGE_BUGREPORT)"; \ + test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$report"; \ + fi; \ + dashes=`echo "$$dashes" | sed s/./=/g`; \ + if test "$$failed" -eq 0; then \ + col="$$grn"; \ + else \ + col="$$red"; \ + fi; \ + echo "$${col}$$dashes$${std}"; \ + echo "$${col}$$banner$${std}"; \ + test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \ + test -z "$$report" || echo "$${col}$$report$${std}"; \ + echo "$${col}$$dashes$${std}"; \ + test "$$failed" -eq 0; \ + else :; fi + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libexecdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libexecPROGRAMS \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/gpg_agent-cache.Po + -rm -f ./$(DEPDIR)/gpg_agent-call-pinentry.Po + -rm -f ./$(DEPDIR)/gpg_agent-call-scd.Po + -rm -f ./$(DEPDIR)/gpg_agent-command-ssh.Po + -rm -f ./$(DEPDIR)/gpg_agent-command.Po + -rm -f ./$(DEPDIR)/gpg_agent-cvt-openpgp.Po + -rm -f ./$(DEPDIR)/gpg_agent-divert-scd.Po + -rm -f ./$(DEPDIR)/gpg_agent-findkey.Po + -rm -f ./$(DEPDIR)/gpg_agent-genkey.Po + -rm -f ./$(DEPDIR)/gpg_agent-gpg-agent.Po + -rm -f ./$(DEPDIR)/gpg_agent-learncard.Po + -rm -f ./$(DEPDIR)/gpg_agent-pkdecrypt.Po + -rm -f ./$(DEPDIR)/gpg_agent-pksign.Po + -rm -f ./$(DEPDIR)/gpg_agent-protect.Po + -rm -f ./$(DEPDIR)/gpg_agent-trans.Po + -rm -f ./$(DEPDIR)/gpg_agent-trustlist.Po + -rm -f ./$(DEPDIR)/gpg_protect_tool-cvt-openpgp.Po + -rm -f ./$(DEPDIR)/gpg_protect_tool-protect-tool.Po + -rm -f ./$(DEPDIR)/gpg_protect_tool-protect.Po + -rm -f ./$(DEPDIR)/preset-passphrase.Po + -rm -f ./$(DEPDIR)/protect.Po + -rm -f ./$(DEPDIR)/t-protect.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS install-libexecPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/gpg_agent-cache.Po + -rm -f ./$(DEPDIR)/gpg_agent-call-pinentry.Po + -rm -f ./$(DEPDIR)/gpg_agent-call-scd.Po + -rm -f ./$(DEPDIR)/gpg_agent-command-ssh.Po + -rm -f ./$(DEPDIR)/gpg_agent-command.Po + -rm -f ./$(DEPDIR)/gpg_agent-cvt-openpgp.Po + -rm -f ./$(DEPDIR)/gpg_agent-divert-scd.Po + -rm -f ./$(DEPDIR)/gpg_agent-findkey.Po + -rm -f ./$(DEPDIR)/gpg_agent-genkey.Po + -rm -f ./$(DEPDIR)/gpg_agent-gpg-agent.Po + -rm -f ./$(DEPDIR)/gpg_agent-learncard.Po + -rm -f ./$(DEPDIR)/gpg_agent-pkdecrypt.Po + -rm -f ./$(DEPDIR)/gpg_agent-pksign.Po + -rm -f ./$(DEPDIR)/gpg_agent-protect.Po + -rm -f ./$(DEPDIR)/gpg_agent-trans.Po + -rm -f ./$(DEPDIR)/gpg_agent-trustlist.Po + -rm -f ./$(DEPDIR)/gpg_protect_tool-cvt-openpgp.Po + -rm -f ./$(DEPDIR)/gpg_protect_tool-protect-tool.Po + -rm -f ./$(DEPDIR)/gpg_protect_tool-protect.Po + -rm -f ./$(DEPDIR)/preset-passphrase.Po + -rm -f ./$(DEPDIR)/protect.Po + -rm -f ./$(DEPDIR)/t-protect.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS uninstall-libexecPROGRAMS + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-TESTS \ + check-am clean clean-binPROGRAMS clean-generic \ + clean-libexecPROGRAMS clean-noinstPROGRAMS cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-binPROGRAMS install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-libexecPROGRAMS install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-binPROGRAMS \ + uninstall-libexecPROGRAMS + +.PRECIOUS: Makefile + + +@HAVE_W32_SYSTEM_TRUE@.rc.o: +@HAVE_W32_SYSTEM_TRUE@ $(WINDRES) $(DEFAULT_INCLUDES) $(INCLUDES) "$<" "$@" +@HAVE_W32_SYSTEM_TRUE@gpg-agent-w32info.o : gpg-agent.w32-manifest + +# Make sure that all libs are build before we use them. This is +# important for things like make -j2. +$(PROGRAMS): $(common_libs) $(commonpth_libs) $(pwquery_libs) + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/agent/agent.h b/agent/agent.h new file mode 100644 index 0000000..56e13ec --- /dev/null +++ b/agent/agent.h @@ -0,0 +1,650 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#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 <gpg-error.h> +#define map_assuan_err(a) \ + map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) +#include <errno.h> + +#include <gcrypt.h> +#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; + + /* If set the extended key format is used for new keys. Note that + * this may have the value 2 in which case + * --disable-extended-key-format won't have any effect and thus + * effectivley locking it. This is required to support existing + * profiles which lock the use of --enable-extended-key-format. */ + int enable_extended_key_format; + + int running_detached; /* We are running detached from the tty. */ + + /* If this global option is true, the passphrase cache is ignored + for signing operations. */ + int ignore_cache_for_signing; + + /* If this global option is true, the user is allowed to + interactively mark certificate in trustlist.txt as trusted. */ + int allow_mark_trusted; + + /* 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, + time_t timestamp, + const char *serialno, const char *keyref, + const char *dispserialno); +gpg_error_t agent_key_from_file (ctrl_t ctrl, + const char *cache_nonce, + const char *desc_text, + const unsigned char *grip, + unsigned char **shadow_info, + cache_mode_t cache_mode, + lookup_ttl_t lookup_ttl, + gcry_sexp_t *result, + char **r_passphrase); +gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t *result); +gpg_error_t agent_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, int use_ocb); +gpg_error_t agent_unprotect (ctrl_t ctrl, + const unsigned char *protectedkey, const char *passphrase, + gnupg_isotime_t protected_at, + unsigned char **result, size_t *resultlen); +int agent_private_key_type (const unsigned char *privatekey); +unsigned char *make_shadow_info (const char *serialno, const char *idstring); +int agent_shadow_key (const unsigned char *pubkey, + const unsigned char *shadow_info, + unsigned char **result); +gpg_error_t agent_get_shadow_info (const unsigned char *shadowkey, + unsigned char const **shadow_info); +gpg_error_t parse_shadow_info (const unsigned char *shadow_info, + char **r_hexsn, char **r_idstr, int *r_pinlen); +gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo, + int s2kmode, + const unsigned char *s2ksalt, + unsigned int s2kcount, + unsigned char *key, size_t keylen); +gpg_error_t agent_write_shadow_key (int maybe_update, + const unsigned char *grip, + const char *serialno, const char *keyid, + const unsigned char *pkbuf, int force, + 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); + + +/*-- 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/all-tests.scm b/agent/all-tests.scm new file mode 100644 index 0000000..6449ebb --- /dev/null +++ b/agent/all-tests.scm @@ -0,0 +1,35 @@ +;; Copyright (C) 2017 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 <http://www.gnu.org/licenses/>. + +(export all-tests + ;; Parse the Makefile.am to find all tests. + + (load (with-path "makefile.scm")) + + (define (expander filename port key) + (parse-makefile port key)) + + (define (parse filename key) + (parse-makefile-expand filename expander key)) + + (map (lambda (name) + (test::binary #f + (path-join "agent" name) + (path-join (getenv "objdir") "agent" name))) + (parse-makefile-expand (in-srcdir "agent" "Makefile.am") + (lambda (filename port key) (parse-makefile port key)) + "TESTS"))) diff --git a/agent/cache.c b/agent/cache.c new file mode 100644 index 0000000..238b6e2 --- /dev/null +++ b/agent/cache.c @@ -0,0 +1,530 @@ +/* cache.c - keep a cache of passphrases + * Copyright (C) 2002, 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <assert.h> +#include <npth.h> + +#include "agent.h" + +/* The size of the encryption key in bytes. */ +#define ENCRYPTION_KEYSIZE (128/8) + +/* A mutex used to serialize access to the cache. */ +static npth_mutex_t cache_lock; +/* The encryption context. This is the only place where the + encryption key for all cached entries is available. It would be nice + to keep this (or just the key) in some hardware device, for example + a TPM. Libgcrypt could be extended to provide such a service. + With the current scheme it is easy to retrieve the cached entries + if access to Libgcrypt's memory is available. The encryption + merely avoids grepping for clear texts in the memory. Nevertheless + the encryption provides the necessary infrastructure to make it + more secure. */ +static gcry_cipher_hd_t encryption_handle; + + +struct secret_data_s { + int totallen; /* This includes the padding and space for AESWRAP. */ + char data[1]; /* A string. */ +}; + +typedef struct cache_item_s *ITEM; +struct cache_item_s { + ITEM next; + time_t created; + time_t accessed; + int ttl; /* max. lifetime given in seconds, -1 one means infinite */ + struct secret_data_s *pw; + cache_mode_t cache_mode; + int restricted; /* The value of ctrl->restricted is part of the key. */ + char key[1]; +}; + +/* The cache himself. */ +static ITEM thecache; + +/* NULL or the last cache key stored by agent_store_cache_hit. */ +static char *last_stored_cache_key; + + +/* This function must be called once to initialize this module. It + has to be done before a second thread is spawned. */ +void +initialize_module_cache (void) +{ + int err; + + err = npth_mutex_init (&cache_lock, NULL); + + if (err) + log_fatal ("error initializing cache module: %s\n", strerror (err)); +} + + +void +deinitialize_module_cache (void) +{ + gcry_cipher_close (encryption_handle); + encryption_handle = NULL; +} + + +/* We do the encryption init on the fly. We can't do it in the module + init code because that is run before we listen for connections and + in case we are started on demand by gpg etc. it will only wait for + a few seconds to decide whether the agent may now accept + connections. Thus we should get into listen state as soon as + possible. */ +static gpg_error_t +init_encryption (void) +{ + gpg_error_t err; + void *key; + + if (encryption_handle) + return 0; /* Shortcut - Already initialized. */ + + err = gcry_cipher_open (&encryption_handle, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_AESWRAP, GCRY_CIPHER_SECURE); + if (!err) + { + key = gcry_random_bytes (ENCRYPTION_KEYSIZE, GCRY_STRONG_RANDOM); + if (!key) + err = gpg_error_from_syserror (); + else + { + err = gcry_cipher_setkey (encryption_handle, key, ENCRYPTION_KEYSIZE); + xfree (key); + } + if (err) + { + gcry_cipher_close (encryption_handle); + encryption_handle = NULL; + } + } + if (err) + log_error ("error initializing cache encryption context: %s\n", + gpg_strerror (err)); + + return err? gpg_error (GPG_ERR_NOT_INITIALIZED) : 0; +} + + + +static void +release_data (struct secret_data_s *data) +{ + xfree (data); +} + +static gpg_error_t +new_data (const char *string, struct secret_data_s **r_data) +{ + gpg_error_t err; + struct secret_data_s *d, *d_enc; + size_t length; + int total; + + *r_data = NULL; + + err = init_encryption (); + if (err) + return err; + + length = strlen (string) + 1; + + /* We pad the data to 32 bytes so that it get more complicated + finding something out by watching allocation patterns. This is + usually not possible but we better assume nothing about our secure + storage provider. To support the AESWRAP mode we need to add 8 + extra bytes as well. */ + total = (length + 8) + 32 - ((length+8) % 32); + + d = xtrymalloc_secure (sizeof *d + total - 1); + if (!d) + return gpg_error_from_syserror (); + memcpy (d->data, string, length); + + d_enc = xtrymalloc (sizeof *d_enc + total - 1); + if (!d_enc) + { + err = gpg_error_from_syserror (); + xfree (d); + return err; + } + + d_enc->totallen = total; + err = gcry_cipher_encrypt (encryption_handle, d_enc->data, total, + d->data, total - 8); + xfree (d); + if (err) + { + xfree (d_enc); + return err; + } + *r_data = d_enc; + return 0; +} + + + +/* Check whether there are items to expire. */ +static void +housekeeping (void) +{ + ITEM r, rprev; + time_t current = gnupg_get_time (); + + /* First expire the actual data */ + for (r=thecache; r; r = r->next) + { + if (r->pw && r->ttl >= 0 && r->accessed + r->ttl < current) + { + if (DBG_CACHE) + log_debug (" expired '%s'.%d (%ds after last access)\n", + r->key, r->restricted, r->ttl); + release_data (r->pw); + r->pw = NULL; + r->accessed = current; + } + } + + /* Second, make sure that we also remove them based on the created stamp so + that the user has to enter it from time to time. */ + for (r=thecache; r; r = r->next) + { + unsigned long maxttl; + + switch (r->cache_mode) + { + case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break; + default: maxttl = opt.max_cache_ttl; break; + } + if (r->pw && r->created + maxttl < current) + { + if (DBG_CACHE) + log_debug (" expired '%s'.%d (%lus after creation)\n", + r->key, r->restricted, opt.max_cache_ttl); + release_data (r->pw); + r->pw = NULL; + r->accessed = current; + } + } + + /* Third, make sure that we don't have too many items in the list. + * Expire old and unused entries after 30 minutes. */ + for (rprev=NULL, r=thecache; r; ) + { + if (!r->pw && r->ttl >= 0 && r->accessed + 60*30 < current) + { + ITEM r2 = r->next; + if (DBG_CACHE) + log_debug (" removed '%s'.%d (mode %d) (slot not used for 30m)\n", + r->key, r->restricted, r->cache_mode); + xfree (r); + if (!rprev) + thecache = r2; + else + rprev->next = r2; + r = r2; + } + else + { + rprev = r; + r = r->next; + } + } +} + + +void +agent_cache_housekeeping (void) +{ + int res; + + if (DBG_CACHE) + log_debug ("agent_cache_housekeeping\n"); + + res = npth_mutex_lock (&cache_lock); + if (res) + log_fatal ("failed to acquire cache mutex: %s\n", strerror (res)); + + housekeeping (); + + res = npth_mutex_unlock (&cache_lock); + if (res) + log_fatal ("failed to release cache mutex: %s\n", strerror (res)); +} + + +void +agent_flush_cache (void) +{ + ITEM r; + int res; + + if (DBG_CACHE) + log_debug ("agent_flush_cache\n"); + + res = npth_mutex_lock (&cache_lock); + if (res) + log_fatal ("failed to acquire cache mutex: %s\n", strerror (res)); + + for (r=thecache; r; r = r->next) + { + if (r->pw) + { + if (DBG_CACHE) + log_debug (" flushing '%s'.%d\n", r->key, r->restricted); + release_data (r->pw); + r->pw = NULL; + r->accessed = 0; + } + } + + res = npth_mutex_unlock (&cache_lock); + if (res) + log_fatal ("failed to release cache mutex: %s\n", strerror (res)); +} + + +/* Compare two cache modes. */ +static int +cache_mode_equal (cache_mode_t a, cache_mode_t b) +{ + /* CACHE_MODE_ANY matches any mode other than CACHE_MODE_IGNORE. */ + return ((a == CACHE_MODE_ANY && b != CACHE_MODE_IGNORE) + || (b == CACHE_MODE_ANY && a != CACHE_MODE_IGNORE) || a == b); +} + + +/* Store the string DATA in the cache under KEY and mark it with a + maximum lifetime of TTL seconds. If there is already data under + this key, it will be replaced. Using a DATA of NULL deletes the + entry. A TTL of 0 is replaced by the default TTL and a TTL of -1 + set infinite timeout. CACHE_MODE is stored with the cache entry + and used to select different timeouts. */ +int +agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, + const char *data, int ttl) +{ + gpg_error_t err = 0; + ITEM r; + int res; + int restricted = ctrl? ctrl->restricted : -1; + + res = npth_mutex_lock (&cache_lock); + if (res) + log_fatal ("failed to acquire cache mutex: %s\n", strerror (res)); + + if (DBG_CACHE) + log_debug ("agent_put_cache '%s'.%d (mode %d) requested ttl=%d\n", + key, restricted, cache_mode, ttl); + housekeeping (); + + if (!ttl) + { + switch(cache_mode) + { + case CACHE_MODE_SSH: ttl = opt.def_cache_ttl_ssh; break; + default: ttl = opt.def_cache_ttl; break; + } + } + if ((!ttl && data) || cache_mode == CACHE_MODE_IGNORE) + goto out; + + for (r=thecache; r; r = r->next) + { + if (((cache_mode != CACHE_MODE_USER + && cache_mode != CACHE_MODE_NONCE) + || cache_mode_equal (r->cache_mode, cache_mode)) + && r->restricted == restricted + && !strcmp (r->key, key)) + break; + } + if (r) /* Replace. */ + { + if (r->pw) + { + release_data (r->pw); + r->pw = NULL; + } + if (data) + { + r->created = r->accessed = gnupg_get_time (); + r->ttl = ttl; + r->cache_mode = cache_mode; + err = new_data (data, &r->pw); + if (err) + log_error ("error replacing cache item: %s\n", gpg_strerror (err)); + } + } + else if (data) /* Insert. */ + { + r = xtrycalloc (1, sizeof *r + strlen (key)); + if (!r) + err = gpg_error_from_syserror (); + else + { + strcpy (r->key, key); + r->restricted = restricted; + r->created = r->accessed = gnupg_get_time (); + r->ttl = ttl; + r->cache_mode = cache_mode; + err = new_data (data, &r->pw); + if (err) + xfree (r); + else + { + r->next = thecache; + thecache = r; + } + } + if (err) + log_error ("error inserting cache item: %s\n", gpg_strerror (err)); + } + + out: + res = npth_mutex_unlock (&cache_lock); + if (res) + log_fatal ("failed to release cache mutex: %s\n", strerror (res)); + + return err; +} + + +/* Try to find an item in the cache. Note that we currently don't + make use of CACHE_MODE except for CACHE_MODE_NONCE and + CACHE_MODE_USER. */ +char * +agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) +{ + gpg_error_t err; + ITEM r; + char *value = NULL; + int res; + int last_stored = 0; + int restricted = ctrl? ctrl->restricted : -1; + + if (cache_mode == CACHE_MODE_IGNORE) + return NULL; + + res = npth_mutex_lock (&cache_lock); + if (res) + log_fatal ("failed to acquire cache mutex: %s\n", strerror (res)); + + if (!key) + { + key = last_stored_cache_key; + if (!key) + goto out; + last_stored = 1; + } + + if (DBG_CACHE) + log_debug ("agent_get_cache '%s'.%d (mode %d)%s ...\n", + key, ctrl->restricted, cache_mode, + last_stored? " (stored cache key)":""); + housekeeping (); + + for (r=thecache; r; r = r->next) + { + if (r->pw + && ((cache_mode != CACHE_MODE_USER + && cache_mode != CACHE_MODE_NONCE) + || cache_mode_equal (r->cache_mode, cache_mode)) + && r->restricted == restricted + && !strcmp (r->key, key)) + { + /* Note: To avoid races KEY may not be accessed anymore below. */ + r->accessed = gnupg_get_time (); + if (DBG_CACHE) + log_debug ("... hit\n"); + if (r->pw->totallen < 32) + err = gpg_error (GPG_ERR_INV_LENGTH); + else if ((err = init_encryption ())) + ; + else if (!(value = xtrymalloc_secure (r->pw->totallen - 8))) + err = gpg_error_from_syserror (); + else + { + err = gcry_cipher_decrypt (encryption_handle, + value, r->pw->totallen - 8, + r->pw->data, r->pw->totallen); + } + if (err) + { + xfree (value); + value = NULL; + log_error ("retrieving cache entry '%s'.%d failed: %s\n", + key, restricted, gpg_strerror (err)); + } + break; + } + } + if (DBG_CACHE && value == NULL) + log_debug ("... miss\n"); + + out: + res = npth_mutex_unlock (&cache_lock); + if (res) + log_fatal ("failed to release cache mutex: %s\n", strerror (res)); + + return value; +} + + +/* Store the key for the last successful cache hit. That value is + used by agent_get_cache if the requested KEY is given as NULL. + NULL may be used to remove that key. */ +void +agent_store_cache_hit (const char *key) +{ + char *new; + char *old; + + /* To make sure the update is atomic under the non-preemptive thread + * model, we must make sure not to surrender control to a different + * thread. Therefore, we avoid calling the allocator during the + * update. + * + * Background: xtrystrdup uses gcry_strdup which may use the secure + * memory allocator of Libgcrypt. That allocator takes locks and + * since version 1.14 libgpg-error is nPth aware and thus taking a + * lock may now lead to thread switch. Note that this only happens + * when secure memory is _allocated_ (the standard allocator uses + * malloc which is not nPth aware) but not when calling _xfree_ + * because gcry_free needs to check whether the pointer is in secure + * memory and thus needs to take a lock. + */ + new = key ? xtrystrdup (key) : NULL; + + /* Atomic update. */ + old = last_stored_cache_key; + last_stored_cache_key = new; + /* Done. */ + + xfree (old); +} diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c new file mode 100644 index 0000000..5fcf98b --- /dev/null +++ b/agent/call-pinentry.c @@ -0,0 +1,2091 @@ +/* call-pinentry.c - Spawn the pinentry to query stuff from the user + * Copyright (C) 2001, 2002, 2004, 2007, 2008, + * 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <sys/stat.h> +#ifndef HAVE_W32_SYSTEM +# include <sys/wait.h> +# include <sys/types.h> +# include <signal.h> +# include <sys/utsname.h> +#endif +#include <npth.h> + +#include "agent.h" +#include <assuan.h> +#include "../common/sysutils.h" +#include "../common/i18n.h" +#include "../common/zb32.h" + +#ifdef _POSIX_OPEN_MAX +#define MAX_OPEN_FDS _POSIX_OPEN_MAX +#else +#define MAX_OPEN_FDS 20 +#endif + + +/* Because access to the pinentry must be serialized (it is and shall + be a global mutually exclusive dialog) we better timeout pending + requests after some time. 1 minute seem to be a reasonable + time. */ +#define LOCK_TIMEOUT (1*60) + +/* Define the number of bits to use for a generated pin. The + * passphrase will be rendered as zbase32 which results for 150 bits + * in a string of 30 characters. That fits nicely into the 5 + * character blocking which pinentry can do. 128 bits would actually + * be sufficient but can't be formatted nicely. */ +#define DEFAULT_GENPIN_BITS 150 + +/* The assuan context of the current pinentry. */ +static assuan_context_t entry_ctx; + +/* A list of features of the current pinentry. */ +static struct +{ + /* The Pinentry support RS+US tabbing. This means that a RS (0x1e) + * starts a new tabbing block in which a US (0x1f) followed by a + * colon marks a colon. A pinentry can use this to pretty print + * name value pairs. */ + unsigned int tabbing:1; +} entry_features; + + +/* A mutex used to serialize access to the pinentry. */ +static npth_mutex_t entry_lock; + +/* The thread ID of the popup working thread. */ +static npth_t popup_tid; + +/* A flag used in communication between the popup working thread and + its stop function. */ +static int popup_finished; + + + +/* Data to be passed to our callbacks, */ +struct entry_parm_s +{ + int lines; + size_t size; + unsigned char *buffer; + int status; + unsigned int constraints_flags; +}; + + + + +/* This function must be called once to initialize this module. This + has to be done before a second thread is spawned. We can't do the + static initialization because Pth emulation code might not be able + to do a static init; in particular, it is not possible for W32. */ +void +initialize_module_call_pinentry (void) +{ + static int initialized; + int err; + + if (!initialized) + { + err = npth_mutex_init (&entry_lock, NULL); + if (err) + log_fatal ("error initializing mutex: %s\n", strerror (err)); + + initialized = 1; + } +} + + + +/* This function may be called to print information pertaining to the + current state of this module to the log. */ +void +agent_query_dump_state (void) +{ + log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%p\n", + entry_ctx, (long)assuan_get_pid (entry_ctx), (void*)popup_tid); +} + +/* Called to make sure that a popup window owned by the current + connection gets closed. */ +void +agent_reset_query (ctrl_t ctrl) +{ + if (entry_ctx && popup_tid && ctrl->pinentry_active) + { + agent_popup_message_stop (ctrl); + } +} + + +/* Unlock the pinentry so that another thread can start one and + disconnect that pinentry - we do this after the unlock so that a + stalled pinentry does not block other threads. Fixme: We should + have a timeout in Assuan for the disconnect operation. */ +static gpg_error_t +unlock_pinentry (ctrl_t ctrl, gpg_error_t rc) +{ + assuan_context_t ctx = entry_ctx; + int err; + + if (rc) + { + if (DBG_IPC) + log_debug ("error calling pinentry: %s <%s>\n", + gpg_strerror (rc), gpg_strsource (rc)); + + /* Change the source of the error to pinentry so that the final + consumer of the error code knows that the problem is with + pinentry. For backward compatibility we do not do that for + some common error codes. */ + switch (gpg_err_code (rc)) + { + case GPG_ERR_NO_PIN_ENTRY: + case GPG_ERR_CANCELED: + case GPG_ERR_FULLY_CANCELED: + case GPG_ERR_ASS_UNKNOWN_INQUIRE: + case GPG_ERR_ASS_TOO_MUCH_DATA: + case GPG_ERR_NO_PASSPHRASE: + case GPG_ERR_BAD_PASSPHRASE: + case GPG_ERR_BAD_PIN: + break; + + case GPG_ERR_CORRUPTED_PROTECTION: + /* This comes from gpg-agent. */ + break; + + default: + rc = gpg_err_make (GPG_ERR_SOURCE_PINENTRY, gpg_err_code (rc)); + break; + } + } + + if (--ctrl->pinentry_active == 0) + { + entry_ctx = NULL; + err = npth_mutex_unlock (&entry_lock); + if (err) + { + log_error ("failed to release the entry lock: %s\n", strerror (err)); + if (!rc) + rc = gpg_error_from_errno (err); + } + assuan_release (ctx); + } + return rc; +} + + +/* Helper for at_fork_cb which can also be called by the parent to + * show which envvars will be set. */ +static void +atfork_core (ctrl_t ctrl, int debug_mode) +{ + int iterator = 0; + const char *name, *assname, *value; + + while ((name = session_env_list_stdenvnames (&iterator, &assname))) + { + /* For all new envvars (!ASSNAME) and the two medium old ones + * which do have an assuan name but are conveyed using + * environment variables, update the environment of the forked + * process. We also pass DISPLAY despite that --display is also + * used when exec-ing the pinentry. The reason is that for + * example the qt5ct tool does not have any arguments and thus + * relies on the DISPLAY envvar. The use case here is a global + * envvar like "QT_QPA_PLATFORMTHEME=qt5ct" which for example is + * useful when using the Qt pinentry under GNOME or XFCE. + */ + if (!assname + || (!opt.keep_display && !strcmp (name, "DISPLAY")) + || !strcmp (name, "XAUTHORITY") + || !strcmp (name, "PINENTRY_USER_DATA")) + { + value = session_env_getenv (ctrl->session_env, name); + if (value) + { + if (debug_mode) + log_debug ("pinentry: atfork used setenv(%s,%s)\n",name,value); + else + gnupg_setenv (name, value, 1); + } + } + } +} + + +/* To make sure we leave no secrets in our image after forking of the + pinentry, we use this callback. */ +static void +atfork_cb (void *opaque, int where) +{ + ctrl_t ctrl = opaque; + + if (!where) + { + gcry_control (GCRYCTL_TERM_SECMEM); + atfork_core (ctrl, 0); + } +} + + +/* Status line callback for the FEATURES status. */ +static gpg_error_t +getinfo_features_cb (void *opaque, const char *line) +{ + const char *args; + char **tokens; + int i; + + (void)opaque; + + if ((args = has_leading_keyword (line, "FEATURES"))) + { + tokens = strtokenize (args, " "); + if (!tokens) + return gpg_error_from_syserror (); + for (i=0; tokens[i]; i++) + if (!strcmp (tokens[i], "tabbing")) + entry_features.tabbing = 1; + xfree (tokens); + } + + return 0; +} + + +static gpg_error_t +getinfo_pid_cb (void *opaque, const void *buffer, size_t length) +{ + unsigned long *pid = opaque; + char pidbuf[50]; + + /* There is only the pid in the server's response. */ + if (length >= sizeof pidbuf) + length = sizeof pidbuf -1; + if (length) + { + strncpy (pidbuf, buffer, length); + pidbuf[length] = 0; + *pid = strtoul (pidbuf, NULL, 10); + } + return 0; +} + + +/* Fork off the pin entry if this has not already been done. Note, + that this function must always be used to acquire the lock for the + pinentry - we will serialize _all_ pinentry calls. + */ +static gpg_error_t +start_pinentry (ctrl_t ctrl) +{ + int rc = 0; + const char *full_pgmname; + const char *pgmname; + assuan_context_t ctx; + const char *argv[5]; + assuan_fd_t no_close_list[3]; + int i; + const char *tmpstr; + unsigned long pinentry_pid; + const char *value; + struct timespec abstime; + char *flavor_version; + int err; + + if (ctrl->pinentry_active) + { + /* It's trying to use pinentry recursively. In this situation, + the thread holds ENTRY_LOCK already. */ + ctrl->pinentry_active++; + return 0; + } + + npth_clock_gettime (&abstime); + abstime.tv_sec += LOCK_TIMEOUT; + err = npth_mutex_timedlock (&entry_lock, &abstime); + if (err) + { + if (err == ETIMEDOUT) + rc = gpg_error (GPG_ERR_TIMEOUT); + else + rc = gpg_error_from_errno (rc); + log_error (_("failed to acquire the pinentry lock: %s\n"), + gpg_strerror (rc)); + return rc; + } + + if (entry_ctx) + return 0; + + if (opt.verbose) + log_info ("starting a new PIN Entry\n"); + +#ifdef HAVE_W32_SYSTEM + fflush (stdout); + fflush (stderr); +#endif + if (fflush (NULL)) + { +#ifndef HAVE_W32_SYSTEM + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); +#endif + log_error ("error flushing pending output: %s\n", strerror (errno)); + /* At least Windows XP fails here with EBADF. According to docs + and Wine an fflush(NULL) is the same as _flushall. However + the Wine implementation does not flush stdin,stdout and stderr + - see above. Let's try to ignore the error. */ +#ifndef HAVE_W32_SYSTEM + return unlock_pinentry (ctrl, tmperr); +#endif + } + + full_pgmname = opt.pinentry_program; + if (!full_pgmname || !*full_pgmname) + full_pgmname = gnupg_module_name (GNUPG_MODULE_NAME_PINENTRY); + if ( !(pgmname = strrchr (full_pgmname, '/'))) + pgmname = full_pgmname; + else + pgmname++; + + /* OS X needs the entire file name in argv[0], so that it can locate + the resource bundle. For other systems we stick to the usual + convention of supplying only the name of the program. */ +#ifdef __APPLE__ + argv[0] = full_pgmname; +#else /*!__APPLE__*/ + argv[0] = pgmname; +#endif /*__APPLE__*/ + + if (!opt.keep_display + && (value = session_env_getenv (ctrl->session_env, "DISPLAY"))) + { + argv[1] = "--display"; + argv[2] = value; + argv[3] = NULL; + } + else + argv[1] = NULL; + + i=0; + if (!opt.running_detached) + { + if (log_get_fd () != -1) + no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); + no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); + } + no_close_list[i] = ASSUAN_INVALID_FD; + + rc = assuan_new (&ctx); + if (rc) + { + log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); + return rc; + } + + ctrl->pinentry_active = 1; + entry_ctx = ctx; + + /* We don't want to log the pinentry communication to make the logs + easier to read. We might want to add a new debug option to enable + pinentry logging. */ +#ifdef ASSUAN_NO_LOGGING + assuan_set_flag (ctx, ASSUAN_NO_LOGGING, !opt.debug_pinentry); +#endif + + /* Connect to the pinentry and perform initial handshaking. Note + that atfork is used to change the environment for pinentry. We + start the server in detached mode to suppress the console window + under Windows. */ + rc = assuan_pipe_connect (entry_ctx, full_pgmname, argv, + no_close_list, atfork_cb, ctrl, + ASSUAN_PIPE_CONNECT_DETACHED); + if (rc) + { + log_error ("can't connect to the PIN entry module '%s': %s\n", + full_pgmname, gpg_strerror (rc)); + return unlock_pinentry (ctrl, gpg_error (GPG_ERR_NO_PIN_ENTRY)); + } + + if (DBG_IPC) + log_debug ("connection to PIN entry established\n"); + + if (opt.debug_pinentry) + atfork_core (ctrl, 1); /* Just show the envvars set after the fork. */ + + value = session_env_getenv (ctrl->session_env, "PINENTRY_USER_DATA"); + if (value != NULL) + { + char *optstr; + if (asprintf (&optstr, "OPTION pinentry-user-data=%s", value) < 0 ) + return unlock_pinentry (ctrl, out_of_core ()); + rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + xfree (optstr); + if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) + return unlock_pinentry (ctrl, rc); + } + + rc = assuan_transact (entry_ctx, + opt.no_grab? "OPTION no-grab":"OPTION grab", + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + + value = session_env_getenv (ctrl->session_env, "GPG_TTY"); + if (value) + { + char *optstr; + if (asprintf (&optstr, "OPTION ttyname=%s", value) < 0 ) + return unlock_pinentry (ctrl, out_of_core ()); + rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + xfree (optstr); + if (rc) + return unlock_pinentry (ctrl, rc); + } + value = session_env_getenv (ctrl->session_env, "TERM"); + if (value && *value) + { + char *optstr; + if (asprintf (&optstr, "OPTION ttytype=%s", value) < 0 ) + return unlock_pinentry (ctrl, out_of_core ()); + rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + xfree (optstr); + if (rc) + return unlock_pinentry (ctrl, rc); + } + if (ctrl->lc_ctype) + { + char *optstr; + if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 ) + return unlock_pinentry (ctrl, out_of_core ()); + rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + xfree (optstr); + if (rc) + return unlock_pinentry (ctrl, rc); + } + if (ctrl->lc_messages) + { + char *optstr; + if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 ) + return unlock_pinentry (ctrl, out_of_core ()); + rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + xfree (optstr); + if (rc) + return unlock_pinentry (ctrl, rc); + } + + + if (opt.allow_external_cache) + { + /* Indicate to the pinentry that it may read from an external cache. + + It is essential that the pinentry respect this. If the + cached password is not up to date and retry == 1, then, using + a version of GPG Agent that doesn't support this, won't issue + another pin request and the user won't get a chance to + correct the password. */ + rc = assuan_transact (entry_ctx, "OPTION allow-external-password-cache", + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) + return unlock_pinentry (ctrl, rc); + } + + if (opt.allow_emacs_pinentry) + { + /* Indicate to the pinentry that it may read passphrase through + Emacs minibuffer, if possible. */ + rc = assuan_transact (entry_ctx, "OPTION allow-emacs-prompt", + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) + return unlock_pinentry (ctrl, rc); + } + + + { + /* Provide a few default strings for use by the pinentries. This + may help a pinentry to avoid implementing localization code. */ + static const struct { const char *key, *value; int what; } tbl[] = { + /* TRANSLATORS: These are labels for buttons etc used in + Pinentries. An underscore indicates that the next letter + should be used as an accelerator. Double the underscore for + a literal one. The actual to be translated text starts after + the second vertical bar. Note that gpg-agent has been set to + utf-8 so that the strings are in the expected encoding. */ + { "ok", N_("|pinentry-label|_OK") }, + { "cancel", N_("|pinentry-label|_Cancel") }, + { "yes", N_("|pinentry-label|_Yes") }, + { "no", N_("|pinentry-label|_No") }, + { "prompt", N_("|pinentry-label|PIN:") }, + { "pwmngr", N_("|pinentry-label|_Save in password manager"), 1 }, + { "cf-visi",N_("Do you really want to make your " + "passphrase visible on the screen?") }, + { "tt-visi",N_("|pinentry-tt|Make passphrase visible") }, + { "tt-hide",N_("|pinentry-tt|Hide passphrase") }, + { NULL, NULL} + }; + char *optstr; + int idx; + const char *s, *s2; + + for (idx=0; tbl[idx].key; idx++) + { + if (!opt.allow_external_cache && tbl[idx].what == 1) + continue; /* No need for it. */ + s = L_(tbl[idx].value); + if (*s == '|' && (s2=strchr (s+1,'|'))) + s = s2+1; + if (asprintf (&optstr, "OPTION default-%s=%s", tbl[idx].key, s) < 0 ) + return unlock_pinentry (ctrl, out_of_core ()); + assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + xfree (optstr); + } + } + + /* Tell the pinentry that we would prefer that the given character + is used as the invisible character by the entry widget. */ + if (opt.pinentry_invisible_char) + { + char *optstr; + if ((optstr = xtryasprintf ("OPTION invisible-char=%s", + opt.pinentry_invisible_char))) + { + assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + /* We ignore errors because this is just a fancy thing and + older pinentries do not support this feature. */ + xfree (optstr); + } + } + + if (opt.pinentry_timeout) + { + char *optstr; + if ((optstr = xtryasprintf ("SETTIMEOUT %lu", opt.pinentry_timeout))) + { + assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + /* We ignore errors because this is just a fancy thing. */ + xfree (optstr); + } + } + + /* Tell the pinentry the name of a file it shall touch after having + messed with the tty. This is optional and only supported by + newer pinentries and thus we do no error checking. */ + tmpstr = opt.pinentry_touch_file; + if (tmpstr && !strcmp (tmpstr, "/dev/null")) + tmpstr = NULL; + else if (!tmpstr) + tmpstr = get_agent_socket_name (); + if (tmpstr) + { + char *optstr; + + if (asprintf (&optstr, "OPTION touch-file=%s", tmpstr ) < 0 ) + ; + else + { + assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + xfree (optstr); + } + } + + /* Tell Pinentry about our client. */ + if (ctrl->client_pid) + { + char *optstr; + const char *nodename = ""; + +#ifndef HAVE_W32_SYSTEM + struct utsname utsbuf; + if (!uname (&utsbuf)) + nodename = utsbuf.nodename; +#endif /*!HAVE_W32_SYSTEM*/ + + if ((optstr = xtryasprintf ("OPTION owner=%lu %s", + ctrl->client_pid, nodename))) + { + assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + /* We ignore errors because this is just a fancy thing and + older pinentries do not support this feature. */ + xfree (optstr); + } + } + + + /* Ask the pinentry for its version and flavor and store that as a + * string in MB. This information is useful for helping users to + * figure out Pinentry problems. Note that "flavor" may also return + * a status line with the features; we use a dedicated handler for + * that. */ + { + membuf_t mb; + + init_membuf (&mb, 256); + if (assuan_transact (entry_ctx, "GETINFO flavor", + put_membuf_cb, &mb, + NULL, NULL, + getinfo_features_cb, NULL)) + put_membuf_str (&mb, "unknown"); + put_membuf_str (&mb, " "); + if (assuan_transact (entry_ctx, "GETINFO version", + put_membuf_cb, &mb, NULL, NULL, NULL, NULL)) + put_membuf_str (&mb, "unknown"); + put_membuf_str (&mb, " "); + if (assuan_transact (entry_ctx, "GETINFO ttyinfo", + put_membuf_cb, &mb, NULL, NULL, NULL, NULL)) + put_membuf_str (&mb, "? ? ?"); + put_membuf (&mb, "", 1); + flavor_version = get_membuf (&mb, NULL); + } + + + /* Now ask the Pinentry for its PID. If the Pinentry is new enough + it will send the pid back and we will use an inquire to notify + our client. The client may answer the inquiry either with END or + with CAN to cancel the pinentry. */ + rc = assuan_transact (entry_ctx, "GETINFO pid", + getinfo_pid_cb, &pinentry_pid, + NULL, NULL, NULL, NULL); + if (rc) + { + log_info ("You may want to update to a newer pinentry\n"); + rc = 0; + } + else if (!rc && (pid_t)pinentry_pid == (pid_t)(-1)) + log_error ("pinentry did not return a PID\n"); + else + { + rc = agent_inq_pinentry_launched (ctrl, pinentry_pid, flavor_version); + if (gpg_err_code (rc) == GPG_ERR_CANCELED + || gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED) + return unlock_pinentry (ctrl, gpg_err_make (GPG_ERR_SOURCE_DEFAULT, + gpg_err_code (rc))); + rc = 0; + } + + xfree (flavor_version); + + return rc; +} + + +/* Returns True if the pinentry is currently active. If WAITSECONDS is + greater than zero the function will wait for this many seconds + before returning. */ +int +pinentry_active_p (ctrl_t ctrl, int waitseconds) +{ + int err; + (void)ctrl; + + if (waitseconds > 0) + { + struct timespec abstime; + int rc; + + npth_clock_gettime (&abstime); + abstime.tv_sec += waitseconds; + err = npth_mutex_timedlock (&entry_lock, &abstime); + if (err) + { + if (err == ETIMEDOUT) + rc = gpg_error (GPG_ERR_TIMEOUT); + else + rc = gpg_error (GPG_ERR_INTERNAL); + return rc; + } + } + else + { + err = npth_mutex_trylock (&entry_lock); + if (err) + return gpg_error (GPG_ERR_LOCKED); + } + + err = npth_mutex_unlock (&entry_lock); + if (err) + log_error ("failed to release the entry lock at %d: %s\n", __LINE__, + strerror (errno)); + return 0; +} + + +static gpg_error_t +getpin_cb (void *opaque, const void *buffer, size_t length) +{ + struct entry_parm_s *parm = opaque; + + if (!buffer) + return 0; + + /* we expect the pin to fit on one line */ + if (parm->lines || length >= parm->size) + return gpg_error (GPG_ERR_ASS_TOO_MUCH_DATA); + + /* fixme: we should make sure that the assuan buffer is allocated in + secure memory or read the response byte by byte */ + memcpy (parm->buffer, buffer, length); + parm->buffer[length] = 0; + parm->lines++; + return 0; +} + + +static int +all_digitsp( const char *s) +{ + for (; *s && *s >= '0' && *s <= '9'; s++) + ; + return !*s; +} + + +/* Return a new malloced string by unescaping the string S. Escaping + is percent escaping and '+'/space mapping. A binary Nul will + silently be replaced by a 0xFF. Function returns NULL to indicate + an out of memory status. Parsing stops at the end of the string or + a white space character. */ +static char * +unescape_passphrase_string (const unsigned char *s) +{ + char *buffer, *d; + + buffer = d = xtrymalloc_secure (strlen ((const char*)s)+1); + if (!buffer) + return NULL; + while (*s && !spacep (s)) + { + if (*s == '%' && s[1] && s[2]) + { + s++; + *d = xtoi_2 (s); + if (!*d) + *d = '\xff'; + d++; + s += 2; + } + else if (*s == '+') + { + *d++ = ' '; + s++; + } + else + *d++ = *s++; + } + *d = 0; + return buffer; +} + + +/* Estimate the quality of the passphrase PW and return a value in the + range 0..100. */ +static int +estimate_passphrase_quality (const char *pw) +{ + int goodlength = opt.min_passphrase_len + opt.min_passphrase_len/3; + int length; + const char *s; + + if (goodlength < 1) + return 0; + + for (length = 0, s = pw; *s; s++) + if (!spacep (s)) + length ++; + + if (length > goodlength) + return 100; + return ((length*10) / goodlength)*10; +} + + +/* Generate a random passphrase in zBase32 encoding (RFC-6189) to be + * used by Pinentry to suggest a passphrase. */ +static char * +generate_pin (void) +{ + unsigned int nbits = opt.min_passphrase_len * 8; + size_t nbytes; + void *rand; + char *generated; + + if (nbits < 128) + nbits = DEFAULT_GENPIN_BITS; + + nbytes = (nbits + 7) / 8; + + rand = gcry_random_bytes_secure (nbytes, GCRY_STRONG_RANDOM); + if (!rand) + { + log_error ("failed to generate random pin\n"); + return NULL; + } + + generated = zb32_encode (rand, nbits); + gcry_free (rand); + return generated; +} + + +/* Handle inquiries. */ +struct inq_cb_parm_s +{ + assuan_context_t ctx; + unsigned int flags; /* CHECK_CONSTRAINTS_... */ + int genpinhash_valid; + char genpinhash[32]; /* Hash of the last generated pin. */ +}; + + +/* Return true if PIN is indentical to the last generated pin. */ +static int +is_generated_pin (struct inq_cb_parm_s *parm, const char *pin) +{ + char hashbuf[32]; + + if (!parm->genpinhash_valid) + return 0; + if (!*pin) + return 0; + /* Note that we compare the hash so that we do not need to save the + * generated PIN longer than needed. */ + gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, pin, strlen (pin)); + + if (!memcmp (hashbuf, parm->genpinhash, 32)) + return 1; /* yes, it is the same. */ + + return 0; +} + + +static gpg_error_t +inq_cb (void *opaque, const char *line) +{ + struct inq_cb_parm_s *parm = opaque; + gpg_error_t err; + const char *s; + char *pin; + int percent; + char numbuf[20]; + + if ((s = has_leading_keyword (line, "QUALITY"))) + { + pin = unescape_passphrase_string (s); + if (!pin) + err = gpg_error_from_syserror (); + else + { + percent = estimate_passphrase_quality (pin); + if (check_passphrase_constraints (NULL, pin, parm->flags, NULL)) + percent = -percent; + snprintf (numbuf, sizeof numbuf, "%d", percent); + err = assuan_send_data (parm->ctx, numbuf, strlen (numbuf)); + xfree (pin); + } + } + else if ((s = has_leading_keyword (line, "CHECKPIN"))) + { + char *errtext = NULL; + size_t errtextlen; + + if (!opt.enforce_passphrase_constraints) + { + log_error ("unexpected inquiry 'CHECKPIN' without enforced " + "passphrase constraints\n"); + err = gpg_error (GPG_ERR_ASS_UNEXPECTED_CMD); + goto leave; + } + + pin = unescape_passphrase_string (s); + if (!pin) + err = gpg_error_from_syserror (); + else + { + if (!is_generated_pin (parm, pin) + && check_passphrase_constraints (NULL, pin,parm->flags, &errtext)) + { + if (errtext) + { + /* Unescape the percent-escaped errtext because + assuan_send_data escapes it again. */ + errtextlen = percent_unescape_inplace (errtext, 0); + err = assuan_send_data (parm->ctx, errtext, errtextlen); + } + else + { + log_error ("passphrase check failed without error text\n"); + err = gpg_error (GPG_ERR_GENERAL); + } + } + else + { + err = assuan_send_data (parm->ctx, NULL, 0); + } + xfree (errtext); + xfree (pin); + } + } + else if ((s = has_leading_keyword (line, "GENPIN"))) + { + int wasconf; + + parm->genpinhash_valid = 0; + pin = generate_pin (); + if (!pin) + { + log_error ("failed to generate a passphrase\n"); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + wasconf = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); + assuan_begin_confidential (parm->ctx); + err = assuan_send_data (parm->ctx, pin, strlen (pin)); + if (!wasconf) + assuan_end_confidential (parm->ctx); + gcry_md_hash_buffer (GCRY_MD_SHA256, parm->genpinhash, pin, strlen (pin)); + parm->genpinhash_valid = 1; + xfree (pin); + } + else + { + log_error ("unsupported inquiry '%s' from pinentry\n", line); + err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); + } + + leave: + return err; +} + + +/* Helper to setup pinentry for genpin action. */ +static gpg_error_t +setup_genpin (ctrl_t ctrl) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + char *tmpstr, *tmpstr2; + const char *tooltip; + + (void)ctrl; + + /* TRANSLATORS: This string is displayed by Pinentry as the label + for generating a passphrase. */ + tmpstr = try_percent_escape (L_("Suggest"), "\t\r\n\f\v"); + snprintf (line, DIM(line), "SETGENPIN %s", tmpstr? tmpstr:""); + xfree (tmpstr); + err = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (gpg_err_code (err) == 103 /*(Old assuan error code)*/ + || gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD) + ; /* Ignore Unknown Command from old Pinentry versions. */ + else if (err) + return err; + + tmpstr2 = gnupg_get_help_string ("pinentry.genpin.tooltip", 0); + if (tmpstr2) + tooltip = tmpstr2; + else + { + /* TRANSLATORS: This string is a tooltip, shown by pinentry when + hovering over the generate button. Please use an appropriate + string to describe what this is about. The length of the + tooltip is limited to about 900 characters. If you do not + translate this entry, a default English text (see source) + will be used. The strcmp thingy is there to detect a + non-translated string. */ + tooltip = L_("pinentry.genpin.tooltip"); + if (!strcmp ("pinentry.genpin.tooltip", tooltip)) + tooltip = "Suggest a random passphrase."; + } + tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v"); + xfree (tmpstr2); + snprintf (line, DIM(line), "SETGENPIN_TT %s", tmpstr? tmpstr:""); + xfree (tmpstr); + err = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (gpg_err_code (err) == 103 /*(Old assuan error code)*/ + || gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD) + ; /* Ignore Unknown Command from old pinentry versions. */ + else if (err) + return err; + + return 0; +} + + +/* Helper to setup pinentry for formatted passphrase. */ +static gpg_error_t +setup_formatted_passphrase (ctrl_t ctrl) +{ + static const struct { const char *key, *help_id, *value; } tbl[] = { + /* TRANSLATORS: This is a text shown by pinentry if the option + for formatted passphrase is enabled. The length is + limited to about 900 characters. */ + { "hint", "pinentry.formatted_passphrase.hint", + N_("Note: The blanks are not part of the passphrase.") }, + { NULL, NULL } + }; + + gpg_error_t rc; + char line[ASSUAN_LINELENGTH]; + int idx; + char *tmpstr; + const char *s; + char *escapedstr; + + (void)ctrl; + + if (opt.pinentry_formatted_passphrase) + { + snprintf (line, DIM(line), "OPTION formatted-passphrase"); + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, + NULL); + if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) + return rc; + + for (idx=0; tbl[idx].key; idx++) + { + tmpstr = gnupg_get_help_string (tbl[idx].help_id, 0); + if (tmpstr) + s = tmpstr; + else + s = L_(tbl[idx].value); + escapedstr = try_percent_escape (s, "\t\r\n\f\v"); + xfree (tmpstr); + if (escapedstr && *escapedstr) + { + snprintf (line, DIM(line), "OPTION formatted-passphrase-%s=%s", + tbl[idx].key, escapedstr); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + } + else + rc = 0; + xfree (escapedstr); + if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) + return rc; + } + } + + return 0; +} + + +/* Helper to setup pinentry for enforced passphrase constraints. */ +static gpg_error_t +setup_enforced_constraints (ctrl_t ctrl) +{ + static const struct { const char *key, *help_id, *value; } tbl[] = { + { "hint-short", "pinentry.constraints.hint.short", NULL }, + { "hint-long", "pinentry.constraints.hint.long", NULL }, + /* TRANSLATORS: This is a text shown by pinentry as title of a dialog + telling the user that the entered new passphrase does not satisfy + the passphrase constraints. Please keep it short. */ + { "error-title", NULL, N_("Passphrase Not Allowed") }, + { NULL, NULL } + }; + + gpg_error_t rc; + char line[ASSUAN_LINELENGTH]; + int idx; + char *tmpstr; + const char *s; + char *escapedstr; + + (void)ctrl; + + if (opt.enforce_passphrase_constraints) + { + snprintf (line, DIM(line), "OPTION constraints-enforce"); + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, + NULL); + if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) + return rc; + + for (idx=0; tbl[idx].key; idx++) + { + tmpstr = gnupg_get_help_string (tbl[idx].help_id, 0); + if (tmpstr) + s = tmpstr; + else if (tbl[idx].value) + s = L_(tbl[idx].value); + else + { + log_error ("no help string found for %s\n", tbl[idx].help_id); + continue; + } + escapedstr = try_percent_escape (s, "\t\r\n\f\v"); + xfree (tmpstr); + if (escapedstr && *escapedstr) + { + snprintf (line, DIM(line), "OPTION constraints-%s=%s", + tbl[idx].key, escapedstr); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + } + else + rc = 0; /* Ignore an empty string (would give an IPC error). */ + xfree (escapedstr); + if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) + return rc; + } + } + + return 0; +} + + +/* Helper for agent_askpin and agent_get_passphrase. */ +static gpg_error_t +setup_qualitybar (ctrl_t ctrl) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + char *tmpstr, *tmpstr2; + const char *tooltip; + + (void)ctrl; + + /* TRANSLATORS: This string is displayed by Pinentry as the label + for the quality bar. */ + tmpstr = try_percent_escape (L_("Quality:"), "\t\r\n\f\v"); + snprintf (line, DIM(line), "SETQUALITYBAR %s", tmpstr? tmpstr:""); + xfree (tmpstr); + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc == 103 /*(Old assuan error code)*/ + || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD) + ; /* Ignore Unknown Command from old Pinentry versions. */ + else if (rc) + return rc; + + tmpstr2 = gnupg_get_help_string ("pinentry.qualitybar.tooltip", 0); + if (tmpstr2) + tooltip = tmpstr2; + else + { + /* TRANSLATORS: This string is a tooltip, shown by pinentry when + hovering over the quality bar. Please use an appropriate + string to describe what this is about. The length of the + tooltip is limited to about 900 characters. If you do not + translate this entry, a default english text (see source) + will be used. */ + tooltip = L_("pinentry.qualitybar.tooltip"); + if (!strcmp ("pinentry.qualitybar.tooltip", tooltip)) + tooltip = ("The quality of the text entered above.\n" + "Please ask your administrator for " + "details about the criteria."); + } + tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v"); + xfree (tmpstr2); + snprintf (line, DIM(line), "SETQUALITYBAR_TT %s", tmpstr? tmpstr:""); + xfree (tmpstr); + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc == 103 /*(Old assuan error code)*/ + || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD) + ; /* Ignore Unknown Command from old pinentry versions. */ + else if (rc) + return rc; + + return 0; +} + +/* Check the button_info line for a close action. Also check for the + PIN_REPEATED flag. */ +static gpg_error_t +pinentry_status_cb (void *opaque, const char *line) +{ + unsigned int *flag = opaque; + const char *args; + + if ((args = has_leading_keyword (line, "BUTTON_INFO"))) + { + if (!strcmp (args, "close")) + *flag |= PINENTRY_STATUS_CLOSE_BUTTON; + } + else if (has_leading_keyword (line, "PIN_REPEATED")) + { + *flag |= PINENTRY_STATUS_PIN_REPEATED; + } + else if (has_leading_keyword (line, "PASSWORD_FROM_CACHE")) + { + *flag |= PINENTRY_STATUS_PASSWORD_FROM_CACHE; + } + + return 0; +} + + +/* Build a SETDESC command line. This is a dedicated function so that + * it can remove control characters which are not supported by the + * current Pinentry. */ +static void +build_cmd_setdesc (char *line, size_t linelen, const char *desc) +{ + char *src, *dst; + + snprintf (line, linelen, "SETDESC %s", desc); + if (!entry_features.tabbing) + { + /* Remove RS and US. */ + for (src=dst=line; *src; src++) + if (!strchr ("\x1e\x1f", *src)) + *dst++ = *src; + *dst = 0; + } +} + + +/* Ask pinentry to get a pin by "GETPIN" command, spawning a thread + * detecting the socket's EOF. */ +static gpg_error_t +do_getpin (ctrl_t ctrl, struct entry_parm_s *parm) +{ + gpg_error_t rc; + int wasconf; + struct inq_cb_parm_s inq_cb_parm; + + (void)ctrl; + + inq_cb_parm.ctx = entry_ctx; + inq_cb_parm.flags = parm->constraints_flags; + inq_cb_parm.genpinhash_valid = 0; + + wasconf = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); + assuan_begin_confidential (entry_ctx); + rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, parm, + inq_cb, &inq_cb_parm, + pinentry_status_cb, &parm->status); + if (!wasconf) + assuan_end_confidential (entry_ctx); + + if (!rc && parm->buffer && is_generated_pin (&inq_cb_parm, parm->buffer)) + parm->status |= PINENTRY_STATUS_PASSWORD_GENERATED; + else + parm->status &= ~PINENTRY_STATUS_PASSWORD_GENERATED; + + /* Most pinentries out in the wild return the old Assuan error code + for canceled which gets translated to an assuan Cancel error and + not to the code for a user cancel. Fix this here. */ + if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); + /* Change error code in case the window close button was clicked + to cancel the operation. */ + if ((parm->status & PINENTRY_STATUS_CLOSE_BUTTON) + && gpg_err_code (rc) == GPG_ERR_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); + + return rc; +} + + + +/* Call the Entry and ask for the PIN. We do check for a valid PIN + number here and repeat it as long as we have invalid formed + numbers. KEYINFO and CACHE_MODE are used to tell pinentry something + about the key. */ +gpg_error_t +agent_askpin (ctrl_t ctrl, + const char *desc_text, const char *prompt_text, + const char *initial_errtext, + struct pin_entry_info_s *pininfo, + const char *keyinfo, cache_mode_t cache_mode) +{ + gpg_error_t rc; + char line[ASSUAN_LINELENGTH]; + struct entry_parm_s parm; + const char *errtext = NULL; + int is_pin = 0; + int is_generated; + + if (opt.batch) + return 0; /* fixme: we should return BAD PIN */ + + if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) + { + if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) + return gpg_error (GPG_ERR_CANCELED); + if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) + { + unsigned char *passphrase; + size_t size; + + *pininfo->pin = 0; /* Reset the PIN. */ + rc = pinentry_loopback (ctrl, "PASSPHRASE", &passphrase, &size, + pininfo->max_length - 1); + if (rc) + return rc; + + memcpy(&pininfo->pin, passphrase, size); + xfree(passphrase); + pininfo->pin[size] = 0; + if (pininfo->check_cb) + { + /* More checks by utilizing the optional callback. */ + pininfo->cb_errtext = NULL; + rc = pininfo->check_cb (pininfo); + } + return rc; + } + return gpg_error(GPG_ERR_NO_PIN_ENTRY); + } + + if (!pininfo || pininfo->max_length < 1) + return gpg_error (GPG_ERR_INV_VALUE); + if (!desc_text && pininfo->min_digits) + desc_text = L_("Please enter your PIN, so that the secret key " + "can be unlocked for this session"); + else if (!desc_text) + desc_text = L_("Please enter your passphrase, so that the secret key " + "can be unlocked for this session"); + + if (prompt_text) + is_pin = !!strstr (prompt_text, "PIN"); + else + is_pin = desc_text && strstr (desc_text, "PIN"); + + rc = start_pinentry (ctrl); + if (rc) + return rc; + + /* If we have a KEYINFO string and are normal, user, or ssh cache + mode, we tell that the Pinentry so it may use it for own caching + purposes. Most pinentries won't have this implemented and thus + we do not error out in this case. */ + if (keyinfo && (cache_mode == CACHE_MODE_NORMAL + || cache_mode == CACHE_MODE_USER + || cache_mode == CACHE_MODE_SSH)) + snprintf (line, DIM(line), "SETKEYINFO %c/%s", + cache_mode == CACHE_MODE_USER? 'u' : + cache_mode == CACHE_MODE_SSH? 's' : 'n', + keyinfo); + else + snprintf (line, DIM(line), "SETKEYINFO --clear"); + + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD) + return unlock_pinentry (ctrl, rc); + + build_cmd_setdesc (line, DIM(line), desc_text); + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + + snprintf (line, DIM(line), "SETPROMPT %s", + prompt_text? prompt_text : is_pin? L_("PIN:") : L_("Passphrase:")); + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + + /* If a passphrase quality indicator has been requested and a + minimum passphrase length has not been disabled, send the command + to the pinentry. */ + if (pininfo->with_qualitybar && opt.min_passphrase_len ) + { + rc = setup_qualitybar (ctrl); + if (rc) + return unlock_pinentry (ctrl, rc); + } + + if (initial_errtext) + { + snprintf (line, DIM(line), "SETERROR %s", initial_errtext); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + } + + if (pininfo->with_repeat) + { + snprintf (line, DIM(line), "SETREPEATERROR %s", + L_("does not match - try again")); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + pininfo->with_repeat = 0; /* Pinentry does not support it. */ + } + pininfo->repeat_okay = 0; + pininfo->status = 0; + + for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++) + { + memset (&parm, 0, sizeof parm); + parm.size = pininfo->max_length; + *pininfo->pin = 0; /* Reset the PIN. */ + parm.buffer = (unsigned char*)pininfo->pin; + parm.constraints_flags = pininfo->constraints_flags; + + if (errtext) + { + /* TRANSLATORS: The string is appended to an error message in + the pinentry. The %s is the actual error message, the + two %d give the current and maximum number of tries. + Do not translate the "SETERROR" keyword. */ + snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"), + errtext, pininfo->failed_tries+1, pininfo->max_tries); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + errtext = NULL; + } + + if (pininfo->with_repeat) + { + snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:")); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + } + + rc = do_getpin (ctrl, &parm); + pininfo->status = parm.status; + is_generated = !!(parm.status & PINENTRY_STATUS_PASSWORD_GENERATED); + + if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA) + errtext = is_pin? L_("PIN too long") + : L_("Passphrase too long"); + else if (rc) + return unlock_pinentry (ctrl, rc); + + if (!errtext && pininfo->min_digits && !is_generated) + { + /* do some basic checks on the entered PIN. */ + if (!all_digitsp (pininfo->pin)) + errtext = L_("Invalid characters in PIN"); + else if (pininfo->max_digits + && strlen (pininfo->pin) > pininfo->max_digits) + errtext = L_("PIN too long"); + else if (strlen (pininfo->pin) < pininfo->min_digits) + errtext = L_("PIN too short"); + } + + if (!errtext && pininfo->check_cb && !is_generated) + { + /* More checks by utilizing the optional callback. */ + pininfo->cb_errtext = NULL; + rc = pininfo->check_cb (pininfo); + /* When pinentry cache causes an error, return now. */ + if (rc + && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) + return unlock_pinentry (ctrl, rc); + + if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) + { + if (pininfo->cb_errtext) + errtext = pininfo->cb_errtext; + else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE + || gpg_err_code (rc) == GPG_ERR_BAD_PIN) + errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); + } + else if (rc) + return unlock_pinentry (ctrl, rc); + } + + if (!errtext) + { + if (pininfo->with_repeat + && (pininfo->status & PINENTRY_STATUS_PIN_REPEATED)) + pininfo->repeat_okay = 1; + return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */ + } + + if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) + { + /* The password was read from the cache. Don't count this + against the retry count. */ + pininfo->failed_tries --; + } + } + + return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN + : GPG_ERR_BAD_PASSPHRASE)); +} + + + +/* Ask for the passphrase using the supplied arguments. The returned + passphrase needs to be freed by the caller. PININFO is optional + and can be used to have constraints checinkg while the pinentry + dialog is open (like what we do in agent_askpin). This is very + similar to agent_akpin and we should eventually merge the two + functions. */ +int +agent_get_passphrase (ctrl_t ctrl, + char **retpass, const char *desc, const char *prompt, + const char *errtext, int with_qualitybar, + const char *keyinfo, cache_mode_t cache_mode, + struct pin_entry_info_s *pininfo) +{ + int rc; + int is_pin; + int is_generated; + char line[ASSUAN_LINELENGTH]; + struct entry_parm_s parm; + + *retpass = NULL; + if (opt.batch) + return gpg_error (GPG_ERR_BAD_PASSPHRASE); + + if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) + { + unsigned char *passphrase; + size_t size; + + if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) + return gpg_error (GPG_ERR_CANCELED); + + if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK && pininfo) + { + *pininfo->pin = 0; /* Reset the PIN. */ + rc = pinentry_loopback (ctrl, "PASSPHRASE", + &passphrase, &size, + pininfo->max_length - 1); + if (rc) + return rc; + + memcpy (&pininfo->pin, passphrase, size); + wipememory (passphrase, size); + xfree (passphrase); + pininfo->pin[size] = 0; + if (pininfo->check_cb) + { + /* More checks by utilizing the optional callback. */ + pininfo->cb_errtext = NULL; + rc = pininfo->check_cb (pininfo); + } + return rc; + + } + else if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) + { + /* Legacy variant w/o PININFO. */ + return pinentry_loopback (ctrl, "PASSPHRASE", + (unsigned char **)retpass, &size, + MAX_PASSPHRASE_LEN); + } + + return gpg_error (GPG_ERR_NO_PIN_ENTRY); + } + + rc = start_pinentry (ctrl); + if (rc) + return rc; + + /* Set IS_PIN and if needed a default prompt. */ + if (prompt) + is_pin = !!strstr (prompt, "PIN"); + else + { + is_pin = desc && strstr (desc, "PIN"); + prompt = is_pin? L_("PIN:"): L_("Passphrase:"); + } + + /* If we have a KEYINFO string and are normal, user, or ssh cache + mode, we tell that the Pinentry so it may use it for own caching + purposes. Most pinentries won't have this implemented and thus + we do not error out in this case. */ + if (keyinfo && (cache_mode == CACHE_MODE_NORMAL + || cache_mode == CACHE_MODE_USER + || cache_mode == CACHE_MODE_SSH)) + snprintf (line, DIM(line), "SETKEYINFO %c/%s", + cache_mode == CACHE_MODE_USER? 'u' : + cache_mode == CACHE_MODE_SSH? 's' : 'n', + keyinfo); + else + snprintf (line, DIM(line), "SETKEYINFO --clear"); + + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD) + return unlock_pinentry (ctrl, rc); + + if (desc) + build_cmd_setdesc (line, DIM(line), desc); + else + snprintf (line, DIM(line), "RESET"); + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + + snprintf (line, DIM(line), "SETPROMPT %s", prompt); + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + + if ((with_qualitybar || (pininfo && pininfo->with_qualitybar)) + && opt.min_passphrase_len) + { + rc = setup_qualitybar (ctrl); + if (rc) + return unlock_pinentry (ctrl, rc); + } + + if (errtext) + { + snprintf (line, DIM(line), "SETERROR %s", errtext); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + } + + rc = setup_formatted_passphrase (ctrl); + if (rc) + return unlock_pinentry (ctrl, rc); + + if (!pininfo) + { + /* Legacy method without PININFO. */ + memset (&parm, 0, sizeof parm); + parm.size = ASSUAN_LINELENGTH/2 - 5; + parm.buffer = gcry_malloc_secure (parm.size+10); + if (!parm.buffer) + return unlock_pinentry (ctrl, out_of_core ()); + + rc = do_getpin (ctrl, &parm); + if (rc) + xfree (parm.buffer); + else + *retpass = parm.buffer; + return unlock_pinentry (ctrl, rc); + } + + /* We got PININFO. */ + + if (pininfo->with_repeat) + { + snprintf (line, DIM(line), "SETREPEATERROR %s", + L_("does not match - try again")); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + pininfo->with_repeat = 0; /* Pinentry does not support it. */ + + (void)setup_genpin (ctrl); + + rc = setup_enforced_constraints (ctrl); + if (rc) + return unlock_pinentry (ctrl, rc); + } + pininfo->repeat_okay = 0; + pininfo->status = 0; + + for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++) + { + memset (&parm, 0, sizeof parm); + parm.constraints_flags = pininfo->constraints_flags; + parm.size = pininfo->max_length; + parm.buffer = (unsigned char*)pininfo->pin; + *pininfo->pin = 0; /* Reset the PIN. */ + + if (errtext) + { + /* TRANSLATORS: The string is appended to an error message in + the pinentry. The %s is the actual error message, the + two %d give the current and maximum number of tries. */ + snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"), + errtext, pininfo->failed_tries+1, pininfo->max_tries); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + errtext = NULL; + } + + if (pininfo->with_repeat) + { + snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:")); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + } + + rc = do_getpin (ctrl, &parm); + pininfo->status = parm.status; + is_generated = !!(parm.status & PINENTRY_STATUS_PASSWORD_GENERATED); + + if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA) + errtext = is_pin? L_("PIN too long") + : L_("Passphrase too long"); + else if (rc) + return unlock_pinentry (ctrl, rc); + + if (!errtext && pininfo->min_digits && !is_generated) + { + /* do some basic checks on the entered PIN. */ + if (!all_digitsp (pininfo->pin)) + errtext = L_("Invalid characters in PIN"); + else if (pininfo->max_digits + && strlen (pininfo->pin) > pininfo->max_digits) + errtext = L_("PIN too long"); + else if (strlen (pininfo->pin) < pininfo->min_digits) + errtext = L_("PIN too short"); + } + + if (!errtext && pininfo->check_cb && !is_generated) + { + /* More checks by utilizing the optional callback. */ + pininfo->cb_errtext = NULL; + rc = pininfo->check_cb (pininfo); + /* When pinentry cache causes an error, return now. */ + if (rc && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) + return unlock_pinentry (ctrl, rc); + + if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) + { + if (pininfo->cb_errtext) + errtext = pininfo->cb_errtext; + else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE + || gpg_err_code (rc) == GPG_ERR_BAD_PIN) + errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); + } + else if (rc) + return unlock_pinentry (ctrl, rc); + } + + if (!errtext) + { + if (pininfo->with_repeat + && (pininfo->status & PINENTRY_STATUS_PIN_REPEATED)) + pininfo->repeat_okay = 1; + return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */ + } + + if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) + { + /* The password was read from the Pinentry's own cache. + Don't count this against the retry count. */ + pininfo->failed_tries--; + } + } + + return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN + : GPG_ERR_BAD_PASSPHRASE)); +} + + + +/* Pop up the PIN-entry, display the text and the prompt and ask the + user to confirm this. We return 0 for success, ie. the user + confirmed it, GPG_ERR_NOT_CONFIRMED for what the text says or an + other error. If WITH_CANCEL it true an extra cancel button is + displayed to allow the user to easily return a GPG_ERR_CANCELED. + if the Pinentry does not support this, the user can still cancel by + closing the Pinentry window. */ +int +agent_get_confirmation (ctrl_t ctrl, + const char *desc, const char *ok, + const char *notok, int with_cancel) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + + if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) + { + if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) + return gpg_error (GPG_ERR_CANCELED); + + return gpg_error (GPG_ERR_NO_PIN_ENTRY); + } + + rc = start_pinentry (ctrl); + if (rc) + return rc; + + if (desc) + build_cmd_setdesc (line, DIM(line), desc); + else + snprintf (line, DIM(line), "RESET"); + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + /* Most pinentries out in the wild return the old Assuan error code + for canceled which gets translated to an assuan Cancel error and + not to the code for a user cancel. Fix this here. */ + if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); + + if (rc) + return unlock_pinentry (ctrl, rc); + + if (ok) + { + snprintf (line, DIM(line), "SETOK %s", ok); + rc = assuan_transact (entry_ctx, + line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + } + if (notok) + { + /* Try to use the newer NOTOK feature if a cancel button is + requested. If no cancel button is requested we keep on using + the standard cancel. */ + if (with_cancel) + { + snprintf (line, DIM(line), "SETNOTOK %s", notok); + rc = assuan_transact (entry_ctx, + line, NULL, NULL, NULL, NULL, NULL, NULL); + } + else + rc = GPG_ERR_ASS_UNKNOWN_CMD; + + if (gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD) + { + snprintf (line, DIM(line), "SETCANCEL %s", notok); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + } + if (rc) + return unlock_pinentry (ctrl, rc); + } + + rc = assuan_transact (entry_ctx, "CONFIRM", + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); + + return unlock_pinentry (ctrl, rc); +} + + + +/* Pop up the PINentry, display the text DESC and a button with the + text OK_BTN (which may be NULL to use the default of "OK") and wait + for the user to hit this button. The return value is not + relevant. */ +int +agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + + if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) + return gpg_error (GPG_ERR_CANCELED); + + rc = start_pinentry (ctrl); + if (rc) + return rc; + + if (desc) + build_cmd_setdesc (line, DIM(line), desc); + else + snprintf (line, DIM(line), "RESET"); + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + /* Most pinentries out in the wild return the old Assuan error code + for canceled which gets translated to an assuan Cancel error and + not to the code for a user cancel. Fix this here. */ + if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); + + if (rc) + return unlock_pinentry (ctrl, rc); + + if (ok_btn) + { + snprintf (line, DIM(line), "SETOK %s", ok_btn); + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, + NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + } + + rc = assuan_transact (entry_ctx, "CONFIRM --one-button", NULL, NULL, NULL, + NULL, NULL, NULL); + if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); + + return unlock_pinentry (ctrl, rc); +} + + +/* The thread running the popup message. */ +static void * +popup_message_thread (void *arg) +{ + (void)arg; + + /* We use the --one-button hack instead of the MESSAGE command to + allow the use of old Pinentries. Those old Pinentries will then + show an additional Cancel button but that is mostly a visual + annoyance. */ + assuan_transact (entry_ctx, "CONFIRM --one-button", + NULL, NULL, NULL, NULL, NULL, NULL); + popup_finished = 1; + return NULL; +} + + +/* Pop up a message window similar to the confirm one but keep it open + until agent_popup_message_stop has been called. It is crucial for + the caller to make sure that the stop function gets called as soon + as the message is not anymore required because the message is + system modal and all other attempts to use the pinentry will fail + (after a timeout). */ +int +agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + npth_attr_t tattr; + int err; + + if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) + return gpg_error (GPG_ERR_CANCELED); + + rc = start_pinentry (ctrl); + if (rc) + return rc; + + if (desc) + build_cmd_setdesc (line, DIM(line), desc); + else + snprintf (line, DIM(line), "RESET"); + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + + if (ok_btn) + { + snprintf (line, DIM(line), "SETOK %s", ok_btn); + rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + } + + err = npth_attr_init (&tattr); + if (err) + return unlock_pinentry (ctrl, gpg_error_from_errno (err)); + npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); + + popup_finished = 0; + err = npth_create (&popup_tid, &tattr, popup_message_thread, NULL); + npth_attr_destroy (&tattr); + if (err) + { + rc = gpg_error_from_errno (err); + log_error ("error spawning popup message handler: %s\n", + strerror (err) ); + return unlock_pinentry (ctrl, rc); + } + npth_setname_np (popup_tid, "popup-message"); + + return 0; +} + +/* Close a popup window. */ +void +agent_popup_message_stop (ctrl_t ctrl) +{ + int rc; + pid_t pid; + + (void)ctrl; + + if (!popup_tid || !entry_ctx) + { + log_debug ("agent_popup_message_stop called with no active popup\n"); + return; + } + + pid = assuan_get_pid (entry_ctx); + if (pid == (pid_t)(-1)) + ; /* No pid available can't send a kill. */ + else if (popup_finished) + ; /* Already finished and ready for joining. */ +#ifdef HAVE_W32_SYSTEM + /* Older versions of assuan set PID to 0 on Windows to indicate an + invalid value. */ + else if (pid != (pid_t) INVALID_HANDLE_VALUE + && pid != 0) + { + HANDLE process = (HANDLE) pid; + + /* Arbitrary error code. */ + TerminateProcess (process, 1); + } +#else + else if (pid && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) + { /* The daemon already died. No need to send a kill. However + because we already waited for the process, we need to tell + assuan that it should not wait again (done by + unlock_pinentry). */ + if (rc == pid) + assuan_set_flag (entry_ctx, ASSUAN_NO_WAITPID, 1); + } + else if (pid > 0) + kill (pid, SIGINT); +#endif + + /* Now wait for the thread to terminate. */ + rc = npth_join (popup_tid, NULL); + if (rc) + log_debug ("agent_popup_message_stop: pth_join failed: %s\n", + strerror (rc)); + /* Thread IDs are opaque, but we try our best here by resetting it + to the same content that a static global variable has. */ + memset (&popup_tid, '\0', sizeof (popup_tid)); + + /* Now we can close the connection. */ + unlock_pinentry (ctrl, 0); +} + +int +agent_clear_passphrase (ctrl_t ctrl, + const char *keyinfo, cache_mode_t cache_mode) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + + if (! (keyinfo && (cache_mode == CACHE_MODE_NORMAL + || cache_mode == CACHE_MODE_USER + || cache_mode == CACHE_MODE_SSH))) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + rc = start_pinentry (ctrl); + if (rc) + return rc; + + snprintf (line, DIM(line), "CLEARPASSPHRASE %c/%s", + cache_mode == CACHE_MODE_USER? 'u' : + cache_mode == CACHE_MODE_SSH? 's' : 'n', + keyinfo); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + + return unlock_pinentry (ctrl, rc); +} diff --git a/agent/call-scd.c b/agent/call-scd.c new file mode 100644 index 0000000..c5b95f4 --- /dev/null +++ b/agent/call-scd.c @@ -0,0 +1,1340 @@ +/* call-scd.c - fork of the scdaemon to do SC operations + * Copyright (C) 2001, 2002, 2005, 2007, 2010, + * 2011 Free Software Foundation, Inc. + * Copyright (C) 2013 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <unistd.h> +#ifdef HAVE_SIGNAL_H +# include <signal.h> +#endif +#include <sys/stat.h> +#include <sys/types.h> +#ifndef HAVE_W32_SYSTEM +#include <sys/wait.h> +#endif +#include <npth.h> + +#include "agent.h" +#include <assuan.h> +#include "../common/strlist.h" + +#ifdef _POSIX_OPEN_MAX +#define MAX_OPEN_FDS _POSIX_OPEN_MAX +#else +#define MAX_OPEN_FDS 20 +#endif + +/* Definition of module local data of the CTRL structure. */ +struct scd_local_s +{ + /* We keep a list of all allocated context with an anchor at + SCD_LOCAL_LIST (see below). */ + struct scd_local_s *next_local; + + /* We need to get back to the ctrl object actually referencing this + structure. This is really an awkward way of enumerating the local + contexts. A much cleaner way would be to keep a global list of + ctrl objects to enumerate them. */ + ctrl_t ctrl_backlink; + + assuan_context_t ctx; /* NULL or session context for the SCdaemon + used with this connection. */ + int locked; /* This flag is used to assert proper use of + start_scd and unlock_scd. */ + +}; + + +/* Callback parameter for learn card */ +struct learn_parm_s +{ + 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; +}; + + +/* Callback parameter used by inq_getpin and inq_writekey_parms. */ +struct inq_needpin_parm_s +{ + assuan_context_t ctx; + int (*getpin_cb)(void *, const char *, const char *, char*, size_t); + void *getpin_cb_arg; + const char *getpin_cb_desc; + assuan_context_t passthru; /* If not NULL, pass unknown inquiries + up to the caller. */ + + /* The next fields are used by inq_writekey_parm. */ + const unsigned char *keydata; + size_t keydatalen; +}; + + +/* To keep track of all active SCD contexts, we keep a linked list + anchored at this variable. */ +static struct scd_local_s *scd_local_list; + +/* A Mutex used inside the start_scd function. */ +static npth_mutex_t start_scd_lock; + +/* A malloced string with the name of the socket to be used for + additional connections. May be NULL if not provided by + SCdaemon. */ +static char *socket_name; + +/* The context of the primary connection. This is also used as a flag + to indicate whether the scdaemon has been started. */ +static assuan_context_t primary_scd_ctx; + +/* To allow reuse of the primary connection, the following flag is set + to true if the primary context has been reset and is not in use by + any connection. */ +static int primary_scd_ctx_reusable; + + + +/* Local prototypes. */ + + + + +/* This function must be called once to initialize this module. This + has to be done before a second thread is spawned. We can't do the + static initialization because NPth emulation code might not be able + to do a static init; in particular, it is not possible for W32. */ +void +initialize_module_call_scd (void) +{ + static int initialized; + int err; + + if (!initialized) + { + err = npth_mutex_init (&start_scd_lock, NULL); + if (err) + log_fatal ("error initializing mutex: %s\n", strerror (err)); + initialized = 1; + } +} + + +/* This function may be called to print information pertaining to the + current state of this module to the log. */ +void +agent_scd_dump_state (void) +{ + log_info ("agent_scd_dump_state: primary_scd_ctx=%p pid=%ld reusable=%d\n", + primary_scd_ctx, + (long)assuan_get_pid (primary_scd_ctx), + primary_scd_ctx_reusable); + if (socket_name) + log_info ("agent_scd_dump_state: socket='%s'\n", socket_name); +} + + +/* The unlock_scd function shall be called after having accessed the + SCD. It is currently not very useful but gives an opportunity to + keep track of connections currently calling SCD. Note that the + "lock" operation is done by the start_scd() function which must be + called and error checked before any SCD operation. CTRL is the + usual connection context and RC the error code to be passed trhough + the function. */ +static int +unlock_scd (ctrl_t ctrl, int rc) +{ + if (ctrl->scd_local->locked != 1) + { + log_error ("unlock_scd: invalid lock count (%d)\n", + ctrl->scd_local->locked); + if (!rc) + rc = gpg_error (GPG_ERR_INTERNAL); + } + ctrl->scd_local->locked = 0; + return rc; +} + +/* To make sure we leave no secrets in our image after forking of the + scdaemon, we use this callback. */ +static void +atfork_cb (void *opaque, int where) +{ + (void)opaque; + + if (!where) + gcry_control (GCRYCTL_TERM_SECMEM); +} + + +/* Fork off the SCdaemon if this has not already been done. Lock the + daemon and make sure that a proper context has been setup in CTRL. + This function might also lock the daemon, which means that the + caller must call unlock_scd after this function has returned + success and the actual Assuan transaction been done. */ +static int +start_scd (ctrl_t ctrl) +{ + gpg_error_t err = 0; + const char *pgmname; + assuan_context_t ctx = NULL; + const char *argv[5]; + assuan_fd_t no_close_list[3]; + int i; + int rc; + char *abs_homedir = NULL; + + if (opt.disable_scdaemon) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + /* If this is the first call for this session, setup the local data + structure. */ + if (!ctrl->scd_local) + { + ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local); + if (!ctrl->scd_local) + return gpg_error_from_syserror (); + ctrl->scd_local->ctrl_backlink = ctrl; + ctrl->scd_local->next_local = scd_local_list; + scd_local_list = ctrl->scd_local; + } + + + /* Assert that the lock count is as expected. */ + if (ctrl->scd_local->locked) + { + log_error ("start_scd: invalid lock count (%d)\n", + ctrl->scd_local->locked); + return gpg_error (GPG_ERR_INTERNAL); + } + ctrl->scd_local->locked++; + + if (ctrl->scd_local->ctx) + return 0; /* Okay, the context is fine. We used to test for an + alive context here and do an disconnect. Now that we + have a ticker function to check for it, it is easier + not to check here but to let the connection run on an + error instead. */ + + + /* We need to protect the following code. */ + rc = npth_mutex_lock (&start_scd_lock); + if (rc) + { + log_error ("failed to acquire the start_scd lock: %s\n", + strerror (rc)); + return gpg_error (GPG_ERR_INTERNAL); + } + + /* Check whether the pipe server has already been started and in + this case either reuse a lingering pipe connection or establish a + new socket based one. */ + if (primary_scd_ctx && primary_scd_ctx_reusable) + { + ctx = primary_scd_ctx; + primary_scd_ctx_reusable = 0; + if (opt.verbose) + log_info ("new connection to SCdaemon established (reusing)\n"); + goto leave; + } + + rc = assuan_new (&ctx); + if (rc) + { + log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); + err = rc; + goto leave; + } + + if (socket_name) + { + rc = assuan_socket_connect (ctx, socket_name, 0, 0); + if (rc) + { + log_error ("can't connect to socket '%s': %s\n", + socket_name, gpg_strerror (rc)); + err = gpg_error (GPG_ERR_NO_SCDAEMON); + goto leave; + } + + if (opt.verbose) + log_info ("new connection to SCdaemon established\n"); + goto leave; + } + + if (primary_scd_ctx) + { + log_info ("SCdaemon is running but won't accept further connections\n"); + err = gpg_error (GPG_ERR_NO_SCDAEMON); + goto leave; + } + + /* Nope, it has not been started. Fire it up now. */ + if (opt.verbose) + log_info ("no running SCdaemon - starting it\n"); + + if (fflush (NULL)) + { +#ifndef HAVE_W32_SYSTEM + err = gpg_error_from_syserror (); +#endif + log_error ("error flushing pending output: %s\n", strerror (errno)); + /* At least Windows XP fails here with EBADF. According to docs + and Wine an fflush(NULL) is the same as _flushall. However + the Wime implementation does not flush stdin,stdout and stderr + - see above. Lets try to ignore the error. */ +#ifndef HAVE_W32_SYSTEM + goto leave; +#endif + } + + if (!opt.scdaemon_program || !*opt.scdaemon_program) + opt.scdaemon_program = gnupg_module_name (GNUPG_MODULE_NAME_SCDAEMON); + if ( !(pgmname = strrchr (opt.scdaemon_program, '/'))) + pgmname = opt.scdaemon_program; + else + pgmname++; + + argv[0] = pgmname; + argv[1] = "--multi-server"; + if (gnupg_default_homedir_p ()) + argv[2] = NULL; + else + { + abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); + if (!abs_homedir) + { + log_error ("error building filename: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + goto leave; + } + + argv[2] = "--homedir"; + argv[3] = abs_homedir; + argv[4] = NULL; + } + + i=0; + if (!opt.running_detached) + { + if (log_get_fd () != -1) + no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); + no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); + } + no_close_list[i] = ASSUAN_INVALID_FD; + + /* Connect to the scdaemon and perform initial handshaking. Use + detached flag so that under Windows SCDAEMON does not show up a + new window. */ + rc = assuan_pipe_connect (ctx, opt.scdaemon_program, argv, + no_close_list, atfork_cb, NULL, + ASSUAN_PIPE_CONNECT_DETACHED); + if (rc) + { + log_error ("can't connect to the SCdaemon: %s\n", + gpg_strerror (rc)); + err = gpg_error (GPG_ERR_NO_SCDAEMON); + goto leave; + } + + if (opt.verbose) + log_debug ("first connection to SCdaemon established\n"); + + + /* Get the name of the additional socket opened by scdaemon. */ + { + membuf_t data; + unsigned char *databuf; + size_t datalen; + + xfree (socket_name); + socket_name = NULL; + init_membuf (&data, 256); + assuan_transact (ctx, "GETINFO socket_name", + put_membuf_cb, &data, NULL, NULL, NULL, NULL); + + databuf = get_membuf (&data, &datalen); + if (databuf && datalen) + { + socket_name = xtrymalloc (datalen + 1); + if (!socket_name) + log_error ("warning: can't store socket name: %s\n", + strerror (errno)); + else + { + memcpy (socket_name, databuf, datalen); + socket_name[datalen] = 0; + if (DBG_IPC) + log_debug ("additional connections at '%s'\n", socket_name); + } + } + xfree (databuf); + } + + /* Tell the scdaemon we want him to send us an event signal. We + don't support this for W32CE. */ +#ifndef HAVE_W32CE_SYSTEM + if (opt.sigusr2_enabled) + { + char buf[100]; + +#ifdef HAVE_W32_SYSTEM + snprintf (buf, sizeof buf, "OPTION event-signal=%p", + get_agent_scd_notify_event ()); +#else + snprintf (buf, sizeof buf, "OPTION event-signal=%d", SIGUSR2); +#endif + assuan_transact (ctx, buf, NULL, NULL, NULL, NULL, NULL, NULL); + } +#endif /*HAVE_W32CE_SYSTEM*/ + + primary_scd_ctx = ctx; + primary_scd_ctx_reusable = 0; + + leave: + xfree (abs_homedir); + if (err) + { + unlock_scd (ctrl, err); + if (ctx) + assuan_release (ctx); + } + else + { + ctrl->scd_local->ctx = ctx; + } + rc = npth_mutex_unlock (&start_scd_lock); + if (rc) + log_error ("failed to release the start_scd lock: %s\n", strerror (rc)); + return err; +} + + +/* Check whether the SCdaemon is active. This is a fast check without + any locking and might give a wrong result if another thread is about + to start the daemon or the daemon is about to be stopped.. */ +int +agent_scd_check_running (void) +{ + return !!primary_scd_ctx; +} + + +/* Check whether the Scdaemon is still alive and clean it up if not. */ +void +agent_scd_check_aliveness (void) +{ + pid_t pid; +#ifdef HAVE_W32_SYSTEM + DWORD rc; +#else + int rc; +#endif + struct timespec abstime; + int err; + + if (!primary_scd_ctx) + return; /* No scdaemon running. */ + + /* This is not a critical function so we use a short timeout while + acquiring the lock. */ + npth_clock_gettime (&abstime); + abstime.tv_sec += 1; + err = npth_mutex_timedlock (&start_scd_lock, &abstime); + if (err) + { + if (err == ETIMEDOUT) + { + if (opt.verbose > 1) + log_info ("failed to acquire the start_scd lock while" + " doing an aliveness check: %s\n", strerror (err)); + } + else + log_error ("failed to acquire the start_scd lock while" + " doing an aliveness check: %s\n", strerror (err)); + return; + } + + if (primary_scd_ctx) + { + pid = assuan_get_pid (primary_scd_ctx); +#ifdef HAVE_W32_SYSTEM + /* If we have a PID we disconnect if either GetExitProcessCode + fails or if ir returns the exit code of the scdaemon. 259 is + the error code for STILL_ALIVE. */ + if (pid != (pid_t)(void*)(-1) && pid + && (!GetExitCodeProcess ((HANDLE)pid, &rc) || rc != 259)) +#else + if (pid != (pid_t)(-1) && pid + && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) +#endif + { + /* Okay, scdaemon died. Disconnect the primary connection + now but take care that it won't do another wait. Also + cleanup all other connections and release their + resources. The next use will start a new daemon then. + Due to the use of the START_SCD_LOCAL we are sure that + none of these context are actually in use. */ + struct scd_local_s *sl; + + assuan_set_flag (primary_scd_ctx, ASSUAN_NO_WAITPID, 1); + assuan_release (primary_scd_ctx); + + for (sl=scd_local_list; sl; sl = sl->next_local) + { + if (sl->ctx) + { + if (sl->ctx != primary_scd_ctx) + assuan_release (sl->ctx); + sl->ctx = NULL; + } + } + + primary_scd_ctx = NULL; + primary_scd_ctx_reusable = 0; + + xfree (socket_name); + socket_name = NULL; + } + } + + err = npth_mutex_unlock (&start_scd_lock); + if (err) + log_error ("failed to release the start_scd lock while" + " doing the aliveness check: %s\n", strerror (err)); +} + + + +/* Reset the SCD if it has been used. Actually it is not a reset but + a cleanup of resources used by the current connection. */ +int +agent_reset_scd (ctrl_t ctrl) +{ + if (ctrl->scd_local) + { + if (ctrl->scd_local->ctx) + { + /* We can't disconnect the primary context because libassuan + does a waitpid on it and thus the system would hang. + Instead we send a reset and keep that connection for + reuse. */ + if (ctrl->scd_local->ctx == primary_scd_ctx) + { + /* Send a RESTART to the SCD. This is required for the + primary connection as a kind of virtual EOF; we don't + have another way to tell it that the next command + should be viewed as if a new connection has been + made. For the non-primary connections this is not + needed as we simply close the socket. We don't check + for an error here because the RESTART may fail for + example if the scdaemon has already been terminated. + Anyway, we need to set the reusable flag to make sure + that the aliveness check can clean it up. */ + assuan_transact (primary_scd_ctx, "RESTART", + NULL, NULL, NULL, NULL, NULL, NULL); + primary_scd_ctx_reusable = 1; + } + else + assuan_release (ctrl->scd_local->ctx); + ctrl->scd_local->ctx = NULL; + } + + /* Remove the local context from our list and release it. */ + if (!scd_local_list) + BUG (); + else if (scd_local_list == ctrl->scd_local) + scd_local_list = ctrl->scd_local->next_local; + else + { + struct scd_local_s *sl; + + for (sl=scd_local_list; sl->next_local; sl = sl->next_local) + if (sl->next_local == ctrl->scd_local) + break; + if (!sl->next_local) + BUG (); + sl->next_local = ctrl->scd_local->next_local; + } + xfree (ctrl->scd_local); + ctrl->scd_local = NULL; + } + + return 0; +} + + + +static gpg_error_t +learn_status_cb (void *opaque, const char *line) +{ + struct learn_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, "CERTINFO", keywordlen)) + { + parm->certinfo_cb (parm->certinfo_cb_arg, line); + } + else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) + { + parm->kpinfo_cb (parm->kpinfo_cb_arg, line); + } + else if (keywordlen && *line) + { + parm->sinfo_cb (parm->sinfo_cb_arg, keyword, keywordlen, line); + } + + return 0; +} + +/* Perform the LEARN command and return a list of all private keys + stored on the card. */ +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 rc; + struct learn_parm_s parm; + + rc = start_scd (ctrl); + if (rc) + return rc; + + memset (&parm, 0, sizeof parm); + parm.kpinfo_cb = kpinfo_cb; + parm.kpinfo_cb_arg = kpinfo_cb_arg; + parm.certinfo_cb = certinfo_cb; + parm.certinfo_cb_arg = certinfo_cb_arg; + parm.sinfo_cb = sinfo_cb; + parm.sinfo_cb_arg = sinfo_cb_arg; + rc = assuan_transact (ctrl->scd_local->ctx, "LEARN --force", + NULL, NULL, NULL, NULL, + learn_status_cb, &parm); + if (rc) + return unlock_scd (ctrl, rc); + + return unlock_scd (ctrl, 0); +} + + + +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; +} + +/* Return the serial number of the card or an appropriate error. The + serial number is returned as a hexstring. */ +int +agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand) +{ + int rc; + char *serialno = NULL; + char line[ASSUAN_LINELENGTH]; + + rc = start_scd (ctrl); + if (rc) + return rc; + + if (!demand) + strcpy (line, "SERIALNO"); + else + snprintf (line, DIM(line), "SERIALNO --demand=%s", demand); + + rc = assuan_transact (ctrl->scd_local->ctx, line, + NULL, NULL, NULL, NULL, + get_serialno_cb, &serialno); + if (rc) + { + xfree (serialno); + return unlock_scd (ctrl, rc); + } + *r_serialno = serialno; + return unlock_scd (ctrl, 0); +} + + + + +/* Handle the NEEDPIN inquiry. */ +static gpg_error_t +inq_needpin (void *opaque, const char *line) +{ + struct inq_needpin_parm_s *parm = opaque; + const char *s; + char *pin; + size_t pinlen; + int rc; + + if ((s = has_leading_keyword (line, "NEEDPIN"))) + { + line = s; + pinlen = 90; + pin = gcry_malloc_secure (pinlen); + if (!pin) + return out_of_core (); + + rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc, + line, pin, pinlen); + if (!rc) + { + assuan_begin_confidential (parm->ctx); + rc = assuan_send_data (parm->ctx, pin, pinlen); + assuan_end_confidential (parm->ctx); + } + wipememory (pin, pinlen); + xfree (pin); + } + else if ((s = has_leading_keyword (line, "POPUPPINPADPROMPT"))) + { + rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc, + s, NULL, 1); + } + else if ((s = has_leading_keyword (line, "DISMISSPINPADPROMPT"))) + { + rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc, + "", NULL, 0); + } + else if (parm->passthru) + { + unsigned char *value; + size_t valuelen; + int rest; + int needrest = !strncmp (line, "KEYDATA", 8); + + /* Pass the inquiry up to our caller. We limit the maximum + amount to an arbitrary value. As we know that the KEYDATA + enquiry is pretty sensitive we disable logging then */ + if ((rest = (needrest + && !assuan_get_flag (parm->passthru, ASSUAN_CONFIDENTIAL)))) + assuan_begin_confidential (parm->passthru); + rc = assuan_inquire (parm->passthru, line, &value, &valuelen, 8096); + if (rest) + assuan_end_confidential (parm->passthru); + if (!rc) + { + if ((rest = (needrest + && !assuan_get_flag (parm->ctx, ASSUAN_CONFIDENTIAL)))) + assuan_begin_confidential (parm->ctx); + rc = assuan_send_data (parm->ctx, value, valuelen); + if (rest) + assuan_end_confidential (parm->ctx); + xfree (value); + } + else + log_error ("error forwarding inquiry '%s': %s\n", + line, gpg_strerror (rc)); + } + else + { + log_error ("unsupported inquiry '%s'\n", line); + rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); + } + + return rc; +} + + +/* Helper returning a command option to describe the used hash + algorithm. See scd/command.c:cmd_pksign. */ +static const char * +hash_algo_option (int algo) +{ + switch (algo) + { + case GCRY_MD_MD5 : return "--hash=md5"; + case GCRY_MD_RMD160: return "--hash=rmd160"; + case GCRY_MD_SHA1 : return "--hash=sha1"; + case GCRY_MD_SHA224: return "--hash=sha224"; + case GCRY_MD_SHA256: return "--hash=sha256"; + case GCRY_MD_SHA384: return "--hash=sha384"; + case GCRY_MD_SHA512: return "--hash=sha512"; + default: return ""; + } +} + + +/* Create a signature using the current card. MDALGO is either 0 or + * gives the digest algorithm. DESC_TEXT is an additional parameter + * passed to GETPIN_CB. */ +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 rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + struct inq_needpin_parm_s inqparm; + + *r_buf = NULL; + rc = start_scd (ctrl); + if (rc) + return rc; + + if (indatalen*2 + 50 > DIM(line)) + return unlock_scd (ctrl, gpg_error (GPG_ERR_GENERAL)); + + bin2hex (indata, indatalen, stpcpy (line, "SETDATA ")); + + rc = assuan_transact (ctrl->scd_local->ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_scd (ctrl, rc); + + init_membuf (&data, 1024); + inqparm.ctx = ctrl->scd_local->ctx; + inqparm.getpin_cb = getpin_cb; + inqparm.getpin_cb_arg = getpin_cb_arg; + inqparm.getpin_cb_desc = desc_text; + inqparm.passthru = 0; + inqparm.keydata = NULL; + inqparm.keydatalen = 0; + + if (ctrl->use_auth_call) + snprintf (line, sizeof line, "PKAUTH %s", keyid); + else + snprintf (line, sizeof line, "PKSIGN %s %s", + hash_algo_option (mdalgo), keyid); + rc = assuan_transact (ctrl->scd_local->ctx, line, + put_membuf_cb, &data, + inq_needpin, &inqparm, + NULL, NULL); + + if (rc) + { + size_t len; + + xfree (get_membuf (&data, &len)); + return unlock_scd (ctrl, rc); + } + + *r_buf = get_membuf (&data, r_buflen); + return unlock_scd (ctrl, 0); +} + + + + +/* Check whether there is any padding info from scdaemon. */ +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; +} + + +/* Decipher INDATA using the current card. Note that the returned + * value is not an s-expression but the raw data as returned by + * scdaemon. The padding information is stored at R_PADDING with -1 + * for not known. DESC_TEXT is an additional parameter passed to + * GETPIN_CB. */ +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 rc, i; + char *p, line[ASSUAN_LINELENGTH]; + membuf_t data; + struct inq_needpin_parm_s inqparm; + size_t len; + + *r_buf = NULL; + *r_padding = -1; /* Unknown. */ + rc = start_scd (ctrl); + if (rc) + return rc; + + /* FIXME: use secure memory where appropriate */ + + for (len = 0; len < indatalen;) + { + p = stpcpy (line, "SETDATA "); + if (len) + p = stpcpy (p, "--append "); + for (i=0; len < indatalen && (i*2 < DIM(line)-50); i++, len++) + { + sprintf (p, "%02X", indata[len]); + p += 2; + } + rc = assuan_transact (ctrl->scd_local->ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_scd (ctrl, rc); + } + + init_membuf (&data, 1024); + inqparm.ctx = ctrl->scd_local->ctx; + inqparm.getpin_cb = getpin_cb; + inqparm.getpin_cb_arg = getpin_cb_arg; + inqparm.getpin_cb_desc = desc_text; + inqparm.passthru = 0; + inqparm.keydata = NULL; + inqparm.keydatalen = 0; + snprintf (line, DIM(line), "PKDECRYPT %s", keyid); + rc = assuan_transact (ctrl->scd_local->ctx, line, + put_membuf_cb, &data, + inq_needpin, &inqparm, + padding_info_cb, r_padding); + + if (rc) + { + xfree (get_membuf (&data, &len)); + return unlock_scd (ctrl, rc); + } + *r_buf = get_membuf (&data, r_buflen); + if (!*r_buf) + return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); + + return unlock_scd (ctrl, 0); +} + + + +/* Read a certificate with ID into R_BUF and R_BUFLEN. */ +int +agent_card_readcert (ctrl_t ctrl, + const char *id, char **r_buf, size_t *r_buflen) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t len; + + *r_buf = NULL; + rc = start_scd (ctrl); + if (rc) + return rc; + + init_membuf (&data, 1024); + snprintf (line, DIM(line), "READCERT %s", id); + rc = assuan_transact (ctrl->scd_local->ctx, line, + put_membuf_cb, &data, + NULL, NULL, + NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return unlock_scd (ctrl, rc); + } + *r_buf = get_membuf (&data, r_buflen); + if (!*r_buf) + return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); + + return unlock_scd (ctrl, 0); +} + + + +/* Read a key with ID and return it in an allocate buffer pointed to + by r_BUF as a valid S-expression. */ +int +agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t len, buflen; + + *r_buf = NULL; + rc = start_scd (ctrl); + if (rc) + return rc; + + init_membuf (&data, 1024); + snprintf (line, DIM(line), "READKEY %s", id); + rc = assuan_transact (ctrl->scd_local->ctx, line, + put_membuf_cb, &data, + NULL, NULL, + NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return unlock_scd (ctrl, rc); + } + *r_buf = get_membuf (&data, &buflen); + if (!*r_buf) + return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); + + if (!gcry_sexp_canon_len (*r_buf, buflen, NULL, NULL)) + { + xfree (*r_buf); *r_buf = NULL; + return unlock_scd (ctrl, gpg_error (GPG_ERR_INV_VALUE)); + } + + return unlock_scd (ctrl, 0); +} + + +/* Handle a KEYDATA inquiry. Note, we only send the data, + assuan_transact takes care of flushing and writing the end */ +static gpg_error_t +inq_writekey_parms (void *opaque, const char *line) +{ + struct inq_needpin_parm_s *parm = opaque; + + if (has_leading_keyword (line, "KEYDATA")) + return assuan_send_data (parm->ctx, parm->keydata, parm->keydatalen); + else + return inq_needpin (opaque, line); +} + + +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) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct inq_needpin_parm_s parms; + + (void)serialno; + rc = start_scd (ctrl); + if (rc) + return rc; + + snprintf (line, DIM(line), "WRITEKEY %s%s", force ? "--force " : "", id); + parms.ctx = ctrl->scd_local->ctx; + parms.getpin_cb = getpin_cb; + parms.getpin_cb_arg = getpin_cb_arg; + parms.getpin_cb_desc= NULL; + parms.passthru = 0; + parms.keydata = keydata; + parms.keydatalen = keydatalen; + + rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, + inq_writekey_parms, &parms, NULL, NULL); + return unlock_scd (ctrl, rc); +} + + + +/* Type used with the card_getattr_cb. */ +struct card_getattr_parm_s { + const char *keyword; /* Keyword to look for. */ + size_t keywordlen; /* strlen of KEYWORD. */ + char *data; /* Malloced and unescaped data. */ + int error; /* ERRNO value or 0 on success. */ +}; + +/* Callback function for agent_card_getattr. */ +static gpg_error_t +card_getattr_cb (void *opaque, const char *line) +{ + struct card_getattr_parm_s *parm = opaque; + const char *keyword = line; + int keywordlen; + + if (parm->data) + return 0; /* We want only the first occurrence. */ + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == parm->keywordlen + && !memcmp (keyword, parm->keyword, keywordlen)) + { + parm->data = percent_plus_unescape ((const unsigned char*)line, 0xff); + if (!parm->data) + parm->error = errno; + } + + return 0; +} + + +/* Call the agent to retrieve a single line data object. On success + the object is malloced and stored at RESULT; it is guaranteed that + NULL is never stored in this case. On error an error code is + returned and NULL stored at RESULT. */ +gpg_error_t +agent_card_getattr (ctrl_t ctrl, const char *name, char **result) +{ + int err; + struct card_getattr_parm_s parm; + char line[ASSUAN_LINELENGTH]; + + *result = NULL; + + if (!*name) + return gpg_error (GPG_ERR_INV_VALUE); + + memset (&parm, 0, sizeof parm); + parm.keyword = name; + parm.keywordlen = strlen (name); + + /* We assume that NAME does not need escaping. */ + if (8 + strlen (name) > DIM(line)-1) + return gpg_error (GPG_ERR_TOO_LARGE); + stpcpy (stpcpy (line, "GETATTR "), name); + + err = start_scd (ctrl); + if (err) + return err; + + err = assuan_transact (ctrl->scd_local->ctx, line, + NULL, NULL, NULL, NULL, + card_getattr_cb, &parm); + if (!err && parm.error) + err = gpg_error_from_errno (parm.error); + + if (!err && !parm.data) + err = gpg_error (GPG_ERR_NO_DATA); + + if (!err) + *result = parm.data; + else + xfree (parm.data); + + return unlock_scd (ctrl, 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; +} + +/* Call the scdaemon to retrieve list of available cards. On success + the allocated strlist is stored at RESULT. On error an error code is + returned and NULL stored at RESULT. */ +gpg_error_t +agent_card_cardlist (ctrl_t ctrl, strlist_t *result) +{ + int err; + struct card_cardlist_parm_s parm; + char line[ASSUAN_LINELENGTH]; + + *result = NULL; + + memset (&parm, 0, sizeof parm); + strcpy (line, "GETINFO card_list"); + + err = start_scd (ctrl); + if (err) + return err; + + err = assuan_transact (ctrl->scd_local->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 unlock_scd (ctrl, err); +} + + + +static gpg_error_t +pass_status_thru (void *opaque, const char *line) +{ + assuan_context_t ctx = opaque; + char keyword[200]; + int i; + + if (line[0] == '#' && (!line[1] || spacep (line+1))) + { + /* We are called in convey comments mode. Now, if we see a + comment marker as keyword we forward the line verbatim to the + the caller. This way the comment lines from scdaemon won't + appear as status lines with keyword '#'. */ + assuan_write_line (ctx, line); + } + else + { + for (i=0; *line && !spacep (line) && i < DIM(keyword)-1; line++, i++) + keyword[i] = *line; + keyword[i] = 0; + + /* Truncate any remaining keyword stuff. */ + for (; *line && !spacep (line); line++) + ; + while (spacep (line)) + line++; + + assuan_write_status (ctx, keyword, line); + } + return 0; +} + +static gpg_error_t +pass_data_thru (void *opaque, const void *buffer, size_t length) +{ + assuan_context_t ctx = opaque; + + assuan_send_data (ctx, buffer, length); + return 0; +} + + +/* Send the line CMDLINE with command for the SCDdaemon to it and send + all status messages back. This command is used as a general quoting + mechanism to pass everything verbatim to SCDAEMON. The PIN + inquiry is handled inside gpg-agent. */ +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) +{ + int rc; + struct inq_needpin_parm_s inqparm; + int saveflag; + + rc = start_scd (ctrl); + if (rc) + return rc; + + inqparm.ctx = ctrl->scd_local->ctx; + inqparm.getpin_cb = getpin_cb; + inqparm.getpin_cb_arg = getpin_cb_arg; + inqparm.getpin_cb_desc = NULL; + inqparm.passthru = assuan_context; + inqparm.keydata = NULL; + inqparm.keydatalen = 0; + + saveflag = assuan_get_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS); + assuan_set_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS, 1); + rc = assuan_transact (ctrl->scd_local->ctx, cmdline, + pass_data_thru, assuan_context, + inq_needpin, &inqparm, + pass_status_thru, assuan_context); + + assuan_set_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS, saveflag); + if (rc) + { + return unlock_scd (ctrl, rc); + } + + return unlock_scd (ctrl, 0); +} + +void +agent_card_killscd (void) +{ + if (primary_scd_ctx == NULL) + return; + assuan_transact (primary_scd_ctx, "KILLSCD", + NULL, NULL, NULL, NULL, NULL, NULL); +} diff --git a/agent/command-ssh.c b/agent/command-ssh.c new file mode 100644 index 0000000..21dd53c --- /dev/null +++ b/agent/command-ssh.c @@ -0,0 +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 <https://www.gnu.org/licenses/>. + */ + +/* 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <assert.h> +#ifndef HAVE_W32_SYSTEM +#include <sys/socket.h> +#include <sys/un.h> +#endif /*!HAVE_W32_SYSTEM*/ +#ifdef HAVE_SYS_UCRED_H +#include <sys/ucred.h> +#endif +#ifdef HAVE_UCRED_H +#include <ucred.h> +#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 (0, grip, serialno, authkeyid, pkbuf, 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, -1); + 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, 0, + NULL, NULL, NULL); + 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 new file mode 100644 index 0000000..b682c55 --- /dev/null +++ b/agent/command.c @@ -0,0 +1,3648 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +/* FIXME: we should not use the default assuan buffering but setup + some buffering in secure mempory to protect session keys etc. */ + +#include <config.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> + +#include "agent.h" +#include <assuan.h> +#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 <hexstring_with_fingerprint>\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 <hexstring_with_fingerprint> <flag> <display_name>\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 <hexstrings_with_keygrips>\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 <hexstring_with_keygrip>\n" + "SETKEY <hexstring_with_keygrip>\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=<name>)|(<algonumber>) <hexstring>\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 [<options>] [<cache_nonce>]\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 [<options>]\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=<isodate>]\n" + " [--inq-passwd] [--passwd-nonce=<s>] [<cache_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] <hexstring_with_keygrip>\n" + " --card <keyid>\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); + if (agent_key_available (grip)) + { + /* Shadow-key is not available in our key storage. */ + rc = agent_write_shadow_key (0, grip, serialno, keyid, pkbuf, 0, + dispserialno); + } + else + { + /* Shadow-key is available in our key storage but ne check + * whether we need to update it with a new display-s/n or + * whatever. */ + rc = agent_write_shadow_key (1, grip, serialno, keyid, pkbuf, 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] <keygrip>\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 <keygrip> <type> <serialno> <idstr> <cached> <protection> <fpr>\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] <cache_id>\n" + " [<error_message> <prompt> <description>]\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] <cache_id>\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 <description>\n" + "\n" + "This command may be used to ask for a simple confirmation.\n" + "DESCRIPTION is displayed along with a Okay and Cancel button. This\n" + "command uses a syntax which helps clients to use the agent with\n" + "minimum effort. The agent either returns with an error or with a\n" + "OK. Note, that the length of DESCRIPTION is implicitly limited by\n" + "the maximum length of a command. DESCRIPTION should not contain\n" + "any spaces, those must be encoded either percent escaped or simply\n" + "as '+'."; +static gpg_error_t +cmd_get_confirmation (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + char *desc = NULL; + char *p; + + if (ctrl->restricted) + return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); + + /* parse the stuff */ + for (p=line; *p == ' '; p++) + ; + desc = p; + p = strchr (desc, ' '); + if (p) + *p = 0; /* We ignore any garbage -may be later used for other args. */ + + if (!*desc) + return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); + + if (!strcmp (desc, "X")) + desc = NULL; + + /* Note, that we only need to replace the + characters and should + leave the other escaping in place because the escaped string is + send verbatim to the pinentry which does the unescaping (but not + the + replacing) */ + if (desc) + plus_to_blank (desc); + + rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0); + return leave_cmd (ctx, rc); +} + + + +static const char hlp_learn[] = + "LEARN [--send] [--sendinfo] [--force]\n" + "\n" + "Learn something about the currently inserted smartcard. With\n" + "--sendinfo information about the card is returned; with --send\n" + "the available certificates are returned as D lines; with --force\n" + "private key storage will be updated by the result."; +static gpg_error_t +cmd_learn (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + int send, sendinfo, force; + + send = has_option (line, "--send"); + sendinfo = send? 1 : has_option (line, "--sendinfo"); + force = has_option (line, "--force"); + + if (ctrl->restricted) + return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); + + err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force); + return leave_cmd (ctx, err); +} + + + +static const char hlp_passwd[] = + "PASSWD [--cache-nonce=<c>] [--passwd-nonce=<s>] [--preset]\n" + " [--verify] <hexkeygrip>\n" + "\n" + "Change the passphrase/PIN for the key identified by keygrip in LINE. If\n" + "--preset is used then the new passphrase will be added to the cache.\n" + "If --verify is used the command asks for the passphrase and verifies\n" + "that the passphrase valid.\n"; +static gpg_error_t +cmd_passwd (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + int c; + char *cache_nonce = NULL; + char *passwd_nonce = NULL; + unsigned char grip[20]; + gcry_sexp_t s_skey = NULL; + unsigned char *shadow_info = NULL; + char *passphrase = NULL; + char *pend; + int opt_preset, opt_verify; + + if (ctrl->restricted) + return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); + + opt_preset = has_option (line, "--preset"); + cache_nonce = option_value (line, "--cache-nonce"); + opt_verify = has_option (line, "--verify"); + if (cache_nonce) + { + for (pend = cache_nonce; *pend && !spacep (pend); pend++) + ; + c = *pend; + *pend = '\0'; + cache_nonce = xtrystrdup (cache_nonce); + *pend = c; + if (!cache_nonce) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + passwd_nonce = option_value (line, "--passwd-nonce"); + if (passwd_nonce) + { + for (pend = passwd_nonce; *pend && !spacep (pend); pend++) + ; + c = *pend; + *pend = '\0'; + passwd_nonce = xtrystrdup (passwd_nonce); + *pend = c; + if (!passwd_nonce) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + line = skip_options (line); + + err = parse_keygrip (ctx, line, grip); + if (err) + goto leave; + + ctrl->in_passwd++; + err = agent_key_from_file (ctrl, + opt_verify? NULL : cache_nonce, + ctrl->server_local->keydesc, + grip, &shadow_info, CACHE_MODE_IGNORE, NULL, + &s_skey, &passphrase); + if (err) + ; + else if (shadow_info) + { + log_error ("changing a smartcard PIN is not yet supported\n"); + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + else if (opt_verify) + { + /* All done. */ + if (passphrase) + { + if (!passwd_nonce) + { + char buf[12]; + gcry_create_nonce (buf, 12); + passwd_nonce = bin2hex (buf, 12, NULL); + } + if (passwd_nonce + && !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE, + passphrase, CACHE_TTL_NONCE)) + { + assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce); + xfree (ctrl->server_local->last_passwd_nonce); + ctrl->server_local->last_passwd_nonce = passwd_nonce; + passwd_nonce = NULL; + } + } + } + else + { + char *newpass = NULL; + + if (passwd_nonce) + newpass = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE); + err = agent_protect_and_store (ctrl, s_skey, &newpass); + if (!err && passphrase) + { + /* A passphrase existed on the old key and the change was + successful. Return a nonce for that old passphrase to + let the caller try to unprotect the other subkeys with + the same key. */ + if (!cache_nonce) + { + char buf[12]; + gcry_create_nonce (buf, 12); + cache_nonce = bin2hex (buf, 12, NULL); + } + if (cache_nonce + && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, + passphrase, CACHE_TTL_NONCE)) + { + assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); + xfree (ctrl->server_local->last_cache_nonce); + ctrl->server_local->last_cache_nonce = cache_nonce; + cache_nonce = NULL; + } + if (newpass) + { + /* If we have a new passphrase (which might be empty) we + store it under a passwd nonce so that the caller may + send that nonce again to use it for another key. */ + if (!passwd_nonce) + { + char buf[12]; + gcry_create_nonce (buf, 12); + passwd_nonce = bin2hex (buf, 12, NULL); + } + if (passwd_nonce + && !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE, + newpass, CACHE_TTL_NONCE)) + { + assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce); + xfree (ctrl->server_local->last_passwd_nonce); + ctrl->server_local->last_passwd_nonce = passwd_nonce; + passwd_nonce = NULL; + } + } + } + if (!err && opt_preset) + { + char hexgrip[40+1]; + bin2hex(grip, 20, hexgrip); + err = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, newpass, + ctrl->cache_ttl_opt_preset); + } + xfree (newpass); + } + ctrl->in_passwd--; + + xfree (ctrl->server_local->keydesc); + ctrl->server_local->keydesc = NULL; + + leave: + xfree (passphrase); + gcry_sexp_release (s_skey); + xfree (shadow_info); + xfree (cache_nonce); + xfree (passwd_nonce); + return leave_cmd (ctx, err); +} + + +static const char hlp_preset_passphrase[] = + "PRESET_PASSPHRASE [--inquire] <string_or_keygrip> <timeout> [<hexstring>]\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 <commands to pass to the scdaemon>\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] <mode>\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=<isodate>]\n" + " [<cache_nonce>]\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, -1); + if (!err) + err = agent_write_private_key (grip, finalkey, finalkeylen, force, + opt_timestamp, NULL, NULL, NULL); + } + else + err = agent_write_private_key (grip, key, realkeylen, force, + opt_timestamp, NULL, NULL, NULL); + + 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=<nonce>] [--openpgp] <hexstring_with_keygrip>\n" + "\n" + "Export a secret key from the key store. The key will be encrypted\n" + "using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n" + "using the AESWRAP-128 algorithm. The caller needs to retrieve that key\n" + "prior to using this command. The function takes the keygrip as argument.\n" + "\n" + "If --openpgp is used, the secret key material will be exported in RFC 4880\n" + "compatible passphrase-protected form. Without --openpgp, the secret key\n" + "material will be exported in the clear (after prompting the user to unlock\n" + "it, if needed).\n"; +static gpg_error_t +cmd_export_key (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + unsigned char grip[20]; + gcry_sexp_t s_skey = NULL; + unsigned char *key = NULL; + size_t keylen; + gcry_cipher_hd_t cipherhd = NULL; + unsigned char *wrappedkey = NULL; + size_t wrappedkeylen; + int openpgp; + char *cache_nonce; + char *passphrase = NULL; + unsigned char *shadow_info = NULL; + char *pend; + int c; + + if (ctrl->restricted) + return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); + + openpgp = has_option (line, "--openpgp"); + cache_nonce = option_value (line, "--cache-nonce"); + if (cache_nonce) + { + for (pend = cache_nonce; *pend && !spacep (pend); pend++) + ; + c = *pend; + *pend = '\0'; + cache_nonce = xtrystrdup (cache_nonce); + *pend = c; + if (!cache_nonce) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + line = skip_options (line); + + if (!ctrl->server_local->export_key) + { + err = set_error (GPG_ERR_MISSING_KEY, "did you run KEYWRAP_KEY ?"); + goto leave; + } + + err = parse_keygrip (ctx, line, grip); + if (err) + goto leave; + + if (agent_key_available (grip)) + { + err = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + + /* Get the key from the file. With the openpgp flag we also ask for + the passphrase so that we can use it to re-encrypt it. */ + err = agent_key_from_file (ctrl, cache_nonce, + ctrl->server_local->keydesc, grip, + &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, + openpgp ? &passphrase : NULL); + if (err) + goto leave; + if (shadow_info) + { + /* Key is on a smartcard. */ + err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); + goto leave; + } + + if (openpgp) + { + /* The openpgp option changes the key format into the OpenPGP + key transfer format. The result is already a padded + canonical S-expression. */ + if (!passphrase) + { + err = agent_ask_new_passphrase + (ctrl, _("This key (or subkey) is not protected with a passphrase." + " Please enter a new passphrase to export it."), + &passphrase); + if (err) + goto leave; + } + err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen); + if (!err && passphrase) + { + if (!cache_nonce) + { + char buf[12]; + gcry_create_nonce (buf, 12); + cache_nonce = bin2hex (buf, 12, NULL); + } + if (cache_nonce + && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, + passphrase, CACHE_TTL_NONCE)) + { + assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); + xfree (ctrl->server_local->last_cache_nonce); + ctrl->server_local->last_cache_nonce = cache_nonce; + cache_nonce = NULL; + } + } + } + else + { + /* Convert into a canonical S-expression and wrap that. */ + err = make_canon_sexp_pad (s_skey, 1, &key, &keylen); + } + if (err) + goto leave; + gcry_sexp_release (s_skey); + s_skey = NULL; + + err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_AESWRAP, 0); + if (err) + goto leave; + err = gcry_cipher_setkey (cipherhd, + ctrl->server_local->export_key, KEYWRAP_KEYSIZE); + if (err) + goto leave; + + wrappedkeylen = keylen + 8; + wrappedkey = xtrymalloc (wrappedkeylen); + if (!wrappedkey) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen); + if (err) + goto leave; + xfree (key); + key = NULL; + gcry_cipher_close (cipherhd); + cipherhd = NULL; + + assuan_begin_confidential (ctx); + err = assuan_send_data (ctx, wrappedkey, wrappedkeylen); + assuan_end_confidential (ctx); + + + leave: + xfree (cache_nonce); + xfree (passphrase); + xfree (wrappedkey); + gcry_cipher_close (cipherhd); + xfree (key); + gcry_sexp_release (s_skey); + xfree (ctrl->server_local->keydesc); + ctrl->server_local->keydesc = NULL; + xfree (shadow_info); + + return leave_cmd (ctx, err); +} + + + +static const char hlp_delete_key[] = + "DELETE_KEY [--force|--stub-only] <hexstring_with_keygrip>\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); +} + + + +#if SIZEOF_TIME_T > SIZEOF_UNSIGNED_LONG +#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010llu))" +#else +#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010lu))" +#endif + +static const char hlp_keytocard[] = + "KEYTOCARD [--force] <hexstring_with_keygrip> <serialno> <id> <timestamp>\n" + "\n"; +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; + unsigned char grip[20]; + gcry_sexp_t s_skey = NULL; + unsigned char *keydata; + size_t keydatalen; + const char *serialno, *timestamp_str, *id; + unsigned char *shadow_info = NULL; + time_t timestamp; + + if (ctrl->restricted) + return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); + + force = has_option (line, "--force"); + line = skip_options (line); + + err = parse_keygrip (ctx, line, grip); + if (err) + goto leave; + + if (agent_key_available (grip)) + { + err =gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + + /* Fixme: Replace the parsing code by split_fields(). */ + line += 40; + while (*line && (*line == ' ' || *line == '\t')) + line++; + serialno = line; + while (*line && (*line != ' ' && *line != '\t')) + line++; + if (!*line) + { + err = gpg_error (GPG_ERR_MISSING_VALUE); + goto leave; + } + *line = '\0'; + line++; + while (*line && (*line == ' ' || *line == '\t')) + line++; + id = line; + while (*line && (*line != ' ' && *line != '\t')) + line++; + if (!*line) + { + err = gpg_error (GPG_ERR_MISSING_VALUE); + goto leave; + } + *line = '\0'; + line++; + while (*line && (*line == ' ' || *line == '\t')) + line++; + timestamp_str = line; + while (*line && (*line != ' ' && *line != '\t')) + line++; + if (*line) + *line = '\0'; + + if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1)) + { + err = gpg_error (GPG_ERR_INV_TIME); + goto leave; + } + + err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip, + &shadow_info, CACHE_MODE_IGNORE, NULL, + &s_skey, NULL); + if (err) + { + xfree (shadow_info); + goto leave; + } + if (shadow_info) + { + /* Key is on a smartcard already. */ + xfree (shadow_info); + gcry_sexp_release (s_skey); + err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); + goto leave; + } + + keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); + keydata = xtrymalloc_secure (keydatalen + 30); + if (keydata == NULL) + { + err = gpg_error_from_syserror (); + gcry_sexp_release (s_skey); + goto leave; + } + + gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen); + gcry_sexp_release (s_skey); + keydatalen--; /* Decrement for last '\0'. */ + /* Add timestamp "created-at" in the private key */ + snprintf (keydata+keydatalen-1, 30, KEYTOCARD_TIMESTAMP_FORMAT, timestamp); + keydatalen += 10 + 19 - 1; + err = divert_writekey (ctrl, force, serialno, id, keydata, keydatalen); + xfree (keydata); + + leave: + return leave_cmd (ctx, err); +} + + + +static const char hlp_getval[] = + "GETVAL <key>\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 <key> [<percent_escaped_value>]\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 <what>\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: + <NAME> Delete envvar NAME + <KEY>= Set envvar NAME to the empty string + <KEY>=<VALUE> 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 new file mode 100644 index 0000000..78f2989 --- /dev/null +++ b/agent/cvt-openpgp.c @@ -0,0 +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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#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, -1)) + agent_write_private_key (grip, protectedkey, protectedkeylen, + 1, 0, NULL, NULL, NULL); + 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, 0, NULL, NULL, NULL); + } + } + + 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/cvt-openpgp.h b/agent/cvt-openpgp.h new file mode 100644 index 0000000..23092f6 --- /dev/null +++ b/agent/cvt-openpgp.h @@ -0,0 +1,37 @@ +/* cvt-openpgp.h - Convert an OpenPGP key to our internal format. + * Copyright (C) 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ +#ifndef GNUPG_AGENT_CVT_OPENPGP_H +#define GNUPG_AGENT_CVT_OPENPGP_H + +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); +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 convert_to_openpgp (ctrl_t ctrl, gcry_sexp_t s_key, + const char *passphrase, + unsigned char **r_transferkey, + size_t *r_transferkeylen); + +#endif /*GNUPG_AGENT_CVT_OPENPGP_H*/ diff --git a/agent/divert-scd.c b/agent/divert-scd.c new file mode 100644 index 0000000..b9ea7e7 --- /dev/null +++ b/agent/divert-scd.c @@ -0,0 +1,734 @@ +/* divert-scd.c - divert operations to the scdaemon + * Copyright (C) 2002, 2003, 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "agent.h" +#include "../common/i18n.h" +#include "../common/sexp-parse.h" + +/* Replace all linefeeds in STRING by "%0A" and return a new malloced + * string. May return NULL on memory error. */ +static char * +linefeed_to_percent0A (const char *string) +{ + const char *s; + size_t n; + char *buf, *p; + + for (n=0, s=string; *s; s++) + if (*s == '\n') + n += 3; + else + n++; + p = buf = xtrymalloc (n+1); + if (!buf) + return NULL; + for (s=string; *s; s++) + if (*s == '\n') + { + memcpy (p, "%0A", 3); + p += 3; + } + else + *p++ = *s; + *p = 0; + return buf; +} + + +/* Ask for the card using SHADOW_INFO. If GRIP is not NULL, the + * function also tries to find additional information from the shadow + * key file. */ +static int +ask_for_card (ctrl_t ctrl, const unsigned char *shadow_info, + const unsigned char *grip, char **r_kid) +{ + int rc, i; + char *serialno; + int no_card = 0; + char *desc; + char *want_sn, *want_kid, *want_sn_disp; + int got_sn_disp_from_meta = 0; + int len; + char *comment = NULL; + + *r_kid = NULL; + + rc = parse_shadow_info (shadow_info, &want_sn, &want_kid, NULL); + if (rc) + return rc; + want_sn_disp = xtrystrdup (want_sn); + if (!want_sn_disp) + { + rc = gpg_error_from_syserror (); + xfree (want_sn); + xfree (want_kid); + xfree (comment); + return rc; + } + + if (grip) + { + nvc_t keymeta; + const char *s; + size_t snlen; + nve_t item; + char **tokenfields = NULL; + + rc = agent_keymeta_from_file (ctrl, grip, &keymeta); + if (!rc) + { + snlen = strlen (want_sn); + s = NULL; + for (item = nvc_lookup (keymeta, "Token:"); + item; + item = nve_next_value (item, "Token:")) + if ((s = nve_value (item)) && !strncmp (s, want_sn, snlen)) + break; + if (s && (tokenfields = strtokenize (s, " \t\n"))) + { + if (tokenfields[0] && tokenfields[1] && tokenfields[2] + && tokenfields[3] && strlen (tokenfields[3]) > 1) + { + xfree (want_sn_disp); + want_sn_disp = percent_plus_unescape (tokenfields[3], 0xff); + if (!want_sn_disp) + { + rc = gpg_error_from_syserror (); + xfree (tokenfields); + nvc_release (keymeta); + xfree (want_sn); + xfree (want_kid); + xfree (comment); + return rc; + } + got_sn_disp_from_meta = 1; + } + + xfree (tokenfields); + } + + if ((s = nvc_get_string (keymeta, "Label:"))) + comment = linefeed_to_percent0A (s); + + nvc_release (keymeta); + } + } + + len = strlen (want_sn_disp); + if (got_sn_disp_from_meta) + ; /* We got the the display S/N from the key file. */ + else if (len == 32 && !strncmp (want_sn_disp, "D27600012401", 12)) + { + /* This is an OpenPGP card - reformat */ + memmove (want_sn_disp, want_sn_disp+16, 4); + want_sn_disp[4] = ' '; + memmove (want_sn_disp+5, want_sn_disp+20, 8); + want_sn_disp[13] = 0; + } + else if (len == 20 && want_sn_disp[19] == '0') + { + /* We assume that a 20 byte serial number is a standard one + * which has the property to have a zero in the last nibble (Due + * to BCD representation). We don't display this '0' because it + * may confuse the user. */ + want_sn_disp[19] = 0; + } + + for (;;) + { + rc = agent_card_serialno (ctrl, &serialno, want_sn); + if (!rc) + { + log_info ("detected card with S/N %s\n", serialno); + i = strcmp (serialno, want_sn); + xfree (serialno); + serialno = NULL; + if (!i) + { + xfree (want_sn_disp); + xfree (want_sn); + xfree (comment); + *r_kid = want_kid; + return 0; /* yes, we have the correct card */ + } + } + else if (gpg_err_code (rc) == GPG_ERR_ENODEV) + { + log_info ("no device present\n"); + rc = 0; + no_card = 1; + } + else if (gpg_err_code (rc) == GPG_ERR_CARD_NOT_PRESENT) + { + log_info ("no card present\n"); + rc = 0; + no_card = 2; + } + else + { + log_error ("error accessing card: %s\n", gpg_strerror (rc)); + } + + if (!rc) + { + if (asprintf (&desc, + "%s:%%0A%%0A" + " %s%%0A" + " %s", + no_card + ? L_("Please insert the card with serial number") + : L_("Please remove the current card and " + "insert the one with serial number"), + want_sn_disp, comment? comment:"") < 0) + { + rc = out_of_core (); + } + else + { + rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0); + if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK && + gpg_err_code (rc) == GPG_ERR_NO_PIN_ENTRY) + rc = gpg_error (GPG_ERR_CARD_NOT_PRESENT); + + xfree (desc); + } + } + if (rc) + { + xfree (want_sn_disp); + xfree (want_sn); + xfree (want_kid); + xfree (comment); + return rc; + } + } +} + + +/* Put the DIGEST into an DER encoded container and return it in R_VAL. */ +static int +encode_md_for_card (const unsigned char *digest, size_t digestlen, int algo, + unsigned char **r_val, size_t *r_len) +{ + unsigned char *frame; + unsigned char asn[100]; + size_t asnlen; + + *r_val = NULL; + *r_len = 0; + + asnlen = DIM(asn); + if (!algo || gcry_md_test_algo (algo)) + return gpg_error (GPG_ERR_DIGEST_ALGO); + if (gcry_md_algo_info (algo, GCRYCTL_GET_ASNOID, asn, &asnlen)) + { + log_error ("no object identifier for algo %d\n", algo); + return gpg_error (GPG_ERR_INTERNAL); + } + + frame = xtrymalloc (asnlen + digestlen); + if (!frame) + return out_of_core (); + memcpy (frame, asn, asnlen); + memcpy (frame+asnlen, digest, digestlen); + if (DBG_CRYPTO) + log_printhex (frame, asnlen+digestlen, "encoded hash:"); + + *r_val = frame; + *r_len = asnlen+digestlen; + return 0; +} + + +/* Return true if STRING ends in "%0A". */ +static int +has_percent0A_suffix (const char *string) +{ + size_t n; + + return (string + && (n = strlen (string)) >= 3 + && !strcmp (string + n - 3, "%0A")); +} + + +/* Callback used to ask for the PIN which should be set into BUF. The + buf has been allocated by the caller and is of size MAXBUF which + includes the terminating null. The function should return an UTF-8 + string with the passphrase, the buffer may optionally be padded + with arbitrary characters. + + If DESC_TEXT is not NULL it can be used as further informtion shown + atop of the INFO message. + + INFO gets displayed as part of a generic string. However if the + first character of INFO is a vertical bar all up to the next + verical bar are considered flags and only everything after the + second vertical bar gets displayed as the full prompt. + + Flags: + + 'N' = New PIN, this requests a second prompt to repeat the + PIN. If the PIN is not correctly repeated it starts from + all over. + 'A' = The PIN is an Admin PIN, SO-PIN or alike. + 'P' = The PIN is a PUK (Personal Unblocking Key). + 'R' = The PIN is a Reset Code. + + Example: + + "|AN|Please enter the new security officer's PIN" + + The text "Please ..." will get displayed and the flags 'A' and 'N' + are considered. + */ +static int +getpin_cb (void *opaque, const char *desc_text, const char *info, + char *buf, size_t maxbuf) +{ + struct pin_entry_info_s *pi; + int rc; + ctrl_t ctrl = opaque; + const char *ends, *s; + int any_flags = 0; + int newpin = 0; + int resetcode = 0; + int is_puk = 0; + const char *again_text = NULL; + const char *prompt = "PIN"; + + if (buf && maxbuf < 2) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Parse the flags. */ + if (info && *info =='|' && (ends=strchr (info+1, '|'))) + { + for (s=info+1; s < ends; s++) + { + if (*s == 'A') + prompt = L_("Admin PIN"); + else if (*s == 'P') + { + /* TRANSLATORS: A PUK is the Personal Unblocking Code + used to unblock a PIN. */ + prompt = L_("PUK"); + is_puk = 1; + } + else if (*s == 'N') + newpin = 1; + else if (*s == 'R') + { + prompt = L_("Reset Code"); + resetcode = 1; + } + } + info = ends+1; + any_flags = 1; + } + else if (info && *info == '|') + log_debug ("pin_cb called without proper PIN info hack\n"); + + /* If BUF has been passed as NULL, we are in pinpad mode: The + callback opens the popup and immediately returns. */ + if (!buf) + { + if (maxbuf == 0) /* Close the pinentry. */ + { + agent_popup_message_stop (ctrl); + rc = 0; + } + else if (maxbuf == 1) /* Open the pinentry. */ + { + if (info) + { + char *desc; + const char *desc2; + + if (!strcmp (info, "--ack")) + { + desc2 = L_("Push ACK button on card/token."); + + if (desc_text) + { + desc = strconcat (desc_text, + has_percent0A_suffix (desc_text) + ? "%0A" : "%0A%0A", + desc2, NULL); + desc2 = NULL; + } + else + desc = NULL; + } + else + { + desc2 = NULL; + + if (desc_text) + desc = strconcat (desc_text, + has_percent0A_suffix (desc_text) + ? "%0A" : "%0A%0A", + info, "%0A%0A", + L_("Use the reader's pinpad for input."), + NULL); + else + desc = strconcat (info, "%0A%0A", + L_("Use the reader's pinpad for input."), + NULL); + } + + if (!desc2 && !desc) + rc = gpg_error_from_syserror (); + else + { + rc = agent_popup_message_start (ctrl, + desc2? desc2:desc, NULL); + xfree (desc); + } + } + else + rc = agent_popup_message_start (ctrl, desc_text, NULL); + } + else + rc = gpg_error (GPG_ERR_INV_VALUE); + return rc; + } + + /* FIXME: keep PI and TRIES in OPAQUE. Frankly this is a whole + mess because we should call the card's verify function from the + pinentry check pin CB. */ + again: + pi = gcry_calloc_secure (1, sizeof (*pi) + maxbuf + 10); + if (!pi) + return gpg_error_from_syserror (); + pi->max_length = maxbuf-1; + pi->min_digits = 0; /* we want a real passphrase */ + pi->max_digits = 16; + pi->max_tries = 3; + + if (any_flags) + { + { + char *desc2; + + if (desc_text) + desc2 = strconcat (desc_text, + has_percent0A_suffix (desc_text) + ? "%0A" : "%0A%0A", + info, NULL); + else + desc2 = NULL; + rc = agent_askpin (ctrl, desc2? desc2 : info, + prompt, again_text, pi, NULL, 0); + xfree (desc2); + } + again_text = NULL; + if (!rc && newpin) + { + struct pin_entry_info_s *pi2; + pi2 = gcry_calloc_secure (1, sizeof (*pi) + maxbuf + 10); + if (!pi2) + { + rc = gpg_error_from_syserror (); + xfree (pi); + return rc; + } + pi2->max_length = maxbuf-1; + pi2->min_digits = 0; + pi2->max_digits = 16; + pi2->max_tries = 1; + rc = agent_askpin (ctrl, + (resetcode? + L_("Repeat this Reset Code"): + is_puk? + L_("Repeat this PUK"): + L_("Repeat this PIN")), + prompt, NULL, pi2, NULL, 0); + if (!rc && strcmp (pi->pin, pi2->pin)) + { + again_text = (resetcode? + L_("Reset Code not correctly repeated; try again"): + is_puk? + L_("PUK not correctly repeated; try again"): + L_("PIN not correctly repeated; try again")); + xfree (pi2); + xfree (pi); + goto again; + } + xfree (pi2); + } + } + else + { + char *desc, *desc2; + + if ( asprintf (&desc, + L_("Please enter the PIN%s%s%s to unlock the card"), + info? " (":"", + info? info:"", + info? ")":"") < 0) + desc = NULL; + if (desc_text) + desc2 = strconcat (desc_text, + has_percent0A_suffix (desc_text) + ? "%0A" : "%0A%0A", + desc, NULL); + else + desc2 = NULL; + rc = agent_askpin (ctrl, desc2? desc2 : desc? desc : info, + prompt, NULL, pi, NULL, 0); + xfree (desc2); + xfree (desc); + } + + if (!rc) + { + strncpy (buf, pi->pin, maxbuf-1); + buf[maxbuf-1] = 0; + } + xfree (pi); + return rc; +} + + + +/* This function is used when a sign operation has been diverted to a + * smartcard. DESC_TEXT is the original text for a prompt has send by + * gpg to gpg-agent. + * + * FIXME: Explain the other args. */ +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 rc; + char *kid; + size_t siglen; + unsigned char *sigval = NULL; + + (void)desc_text; + + rc = ask_for_card (ctrl, shadow_info, grip, &kid); + if (rc) + return rc; + + /* For OpenPGP cards we better use the keygrip as key reference. + * This has the advantage that app-openpgp can check that the stored + * key matches our expectation. This is important in case new keys + * have been created on the same card but the sub file has not been + * updated. In that case we would get a error from our final + * signature checking code or, if the pubkey algo is different, + * weird errors from the card (Conditions of use not satisfied). */ + if (kid && grip && !strncmp (kid, "OPENPGP.", 8)) + { + xfree (kid); + kid = bin2hex (grip, KEYGRIP_LEN, NULL); + if (!kid) + return gpg_error_from_syserror (); + } + + + if (algo == MD_USER_TLS_MD5SHA1) + { + int save = ctrl->use_auth_call; + ctrl->use_auth_call = 1; + rc = agent_card_pksign (ctrl, kid, getpin_cb, ctrl, NULL, + algo, digest, digestlen, &sigval, &siglen); + ctrl->use_auth_call = save; + } + else + { + unsigned char *data; + size_t ndata; + + rc = encode_md_for_card (digest, digestlen, algo, &data, &ndata); + if (!rc) + { + rc = agent_card_pksign (ctrl, kid, getpin_cb, ctrl, NULL, + algo, data, ndata, &sigval, &siglen); + xfree (data); + } + } + + if (!rc) + { + *r_sig = sigval; + *r_siglen = siglen; + } + + xfree (kid); + + return rc; +} + + +/* Decrypt the value given asn an S-expression in CIPHER using the + key identified by SHADOW_INFO and return the plaintext in an + allocated buffer in R_BUF. The padding information is stored at + R_PADDING with -1 for not known. */ +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 rc; + char *kid; + const unsigned char *s; + size_t n; + int depth; + const unsigned char *ciphertext; + size_t ciphertextlen; + char *plaintext; + size_t plaintextlen; + + (void)desc_text; + + *r_padding = -1; + s = cipher; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "enc-val")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + + /* First check whether we have a flags parameter and skip it. */ + if (smatch (&s, n, "flags")) + { + depth = 1; + if (sskip (&s, &depth) || depth) + return gpg_error (GPG_ERR_INV_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + } + + if (smatch (&s, n, "rsa")) + { + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "a")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + n = snext (&s); + } + else if (smatch (&s, n, "ecdh")) + { + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "s")) + { + n = snext (&s); + s += n; + if (*s++ != ')') + return gpg_error (GPG_ERR_INV_SEXP); + if (*s++ != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + } + if (!smatch (&s, n, "e")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + n = snext (&s); + } + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + + if (!n) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + ciphertext = s; + ciphertextlen = n; + + rc = ask_for_card (ctrl, shadow_info, grip, &kid); + if (rc) + return rc; + + /* For OpenPGP cards we better use the keygrip as key reference. + * This has the advantage that app-openpgp can check that the stored + * key matches our expectation. This is important in case new keys + * have been created on the same card but the sub file has not been + * updated. In that case we would get a error from our final + * signature checking code or, if the pubkey algo is different, + * weird errors from the card (Conditions of use not satisfied). */ + if (kid && grip && !strncmp (kid, "OPENPGP.", 8)) + { + xfree (kid); + kid = bin2hex (grip, KEYGRIP_LEN, NULL); + if (!kid) + return gpg_error_from_syserror (); + } + + rc = agent_card_pkdecrypt (ctrl, kid, getpin_cb, ctrl, NULL, + ciphertext, ciphertextlen, + &plaintext, &plaintextlen, r_padding); + if (!rc) + { + *r_buf = plaintext; + *r_len = plaintextlen; + } + xfree (kid); + return rc; +} + +int +divert_writekey (ctrl_t ctrl, int force, const char *serialno, + const char *id, const char *keydata, size_t keydatalen) +{ + return agent_card_writekey (ctrl, force, serialno, id, keydata, keydatalen, + getpin_cb, ctrl); +} + +int +divert_generic_cmd (ctrl_t ctrl, const char *cmdline, void *assuan_context) +{ + return agent_card_scd (ctrl, cmdline, getpin_cb, ctrl, assuan_context); +} diff --git a/agent/findkey.c b/agent/findkey.c new file mode 100644 index 0000000..dadcc3c --- /dev/null +++ b/agent/findkey.c @@ -0,0 +1,1827 @@ +/* findkey.c - Locate the secret key + * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, + * 2010, 2011 Free Software Foundation, Inc. + * Copyright (C) 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include <assert.h> +#include <unistd.h> +#include <sys/stat.h> +#include <assert.h> +#include <npth.h> /* (we use pth_sleep) */ + +#include "agent.h" +#include "../common/i18n.h" +#include "../common/ssh-utils.h" +#include "../common/name-value.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +/* Helper to pass data to the check callback of the unprotect function. */ +struct try_unprotect_arg_s +{ + ctrl_t ctrl; + const unsigned char *protected_key; + unsigned char *unprotected_key; + int change_required; /* Set by the callback to indicate that the + user should change the passphrase. */ +}; + + +/* Return the file name for the 20 byte keygrip GRIP. Return NULL on + * error. */ +static char * +fname_from_keygrip (const unsigned char *grip) +{ + char hexgrip[40+4+1]; + + bin2hex (grip, 20, hexgrip); + strcpy (hexgrip+40, ".key"); + + return make_filename_try (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, + hexgrip, NULL); +} + + +/* Note: Ownership of FNAME and FP are moved to this function. + * OLD_FORMAT is true if the file exists but is still in the + * non-extended mode format. If MAYBE_UPDATE is set the function + * assumes that the file exists but writes it only if it figures that + * an update is required. */ +static gpg_error_t +write_extended_private_key (int maybe_update, + char *fname, estream_t fp, + int old_format, int newkey, + const void *buf, size_t len, time_t timestamp, + const char *serialno, const char *keyref, + const char *dispserialno) +{ + gpg_error_t err; + nvc_t pk = NULL; + gcry_sexp_t key = NULL; + int remove = 0; + char *token0 = NULL; + char *token = NULL; + char *dispserialno_buffer = NULL; + char **tokenfields = NULL; + + if (old_format || newkey) + { + /* We must create a new NVC if the key is still in the old + * format and of course if it is a new key. */ + pk = nvc_new_private_key (); + if (!pk) + { + err = gpg_error_from_syserror (); + goto leave; + } + maybe_update = 0; /* Always write. */ + } + else + { /* Parse the existing NVC. */ + int lineno = 0; + + err = nvc_parse_private_key (&pk, &lineno, fp); + if (err) + { + log_error ("error parsing '%s' line %d: %s\n", + fname, lineno, gpg_strerror (err)); + goto leave; + } + } + es_clearerr (fp); + + err = gcry_sexp_sscan (&key, NULL, buf, len); + if (err) + goto leave; + + err = nvc_set_private_key (pk, key); + if (err) + goto leave; + + /* If a timestamp has been supplied and the key is new write a + * creation timestamp. Note that we can't add this item if we are + * still in the old format. We also add an extra 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; + } + + /* If requested write a Token line. */ + if (serialno && keyref) + { + nve_t item; + const char *s; + 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; + maybe_update = 0; /* Force write. */ + } + else + { + /* Token exists: Update the display s/n. It may have + * changed due to changes in a newer software version. */ + if (maybe_update && 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 (item, token); + if (err) + goto leave; + maybe_update = 0; /* Force write. */ + } + } + } + + err = es_fseek (fp, 0, SEEK_SET); + if (err) + goto leave; + + if (!maybe_update) + { + err = nvc_write (pk, fp); + if (!err) + err = es_fflush (fp); + if (err) + { + log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); + remove = 1; + goto leave; + } + + if (ftruncate (es_fileno (fp), es_ftello (fp))) + { + err = gpg_error_from_syserror (); + log_error ("error truncating '%s': %s\n", fname, gpg_strerror (err)); + remove = 1; + goto leave; + } + } + + if (es_fclose (fp)) + { + err = gpg_error_from_syserror (); + log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); + remove = 1; + goto leave; + } + else + fp = NULL; + + if (!maybe_update) + bump_key_eventcounter (); + + leave: + es_fclose (fp); + if (remove) + gnupg_remove (fname); + xfree (fname); + xfree (token); + xfree (token0); + xfree (dispserialno_buffer); + xfree (tokenfields); + gcry_sexp_release (key); + nvc_release (pk); + return err; +} + +/* Write an S-expression formatted key to our key storage. With FORCE + * passed as true an existing key with the given GRIP will get + * overwritten. If TIMESTAMP is not zero and the key does not yet + * exists it will be recorded as creation date. If SERIALNO, KEYREF, + * of DISPSERIALNO are not NULL they will be recorded as well. */ +int +agent_write_private_key (const unsigned char *grip, + const void *buffer, size_t length, + int force, time_t timestamp, + const char *serialno, const char *keyref, + const char *dispserialno) +{ + char *fname; + estream_t fp; + + fname = fname_from_keygrip (grip); + if (!fname) + return gpg_error_from_syserror (); + + /* FIXME: Write to a temp file first so that write failures during + key updates won't lead to a key loss. */ + + if (!force && !gnupg_access (fname, F_OK)) + { + log_error ("secret key file '%s' already exists\n", fname); + xfree (fname); + return gpg_error (GPG_ERR_EEXIST); + } + + fp = es_fopen (fname, force? "rb+,mode=-rw" : "wbx,mode=-rw"); + if (!fp) + { + gpg_error_t tmperr = gpg_error_from_syserror (); + + if (force && gpg_err_code (tmperr) == GPG_ERR_ENOENT) + { + fp = es_fopen (fname, "wbx,mode=-rw"); + if (!fp) + tmperr = gpg_error_from_syserror (); + } + if (!fp) + { + log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr)); + xfree (fname); + return tmperr; + } + } + else if (force) + { + gpg_error_t rc; + char first; + + /* See if an existing key is in extended format. */ + if (es_fread (&first, 1, 1, fp) != 1) + { + rc = gpg_error_from_syserror (); + log_error ("error reading first byte from '%s': %s\n", + fname, strerror (errno)); + xfree (fname); + es_fclose (fp); + return rc; + } + + rc = es_fseek (fp, 0, SEEK_SET); + if (rc) + { + log_error ("error seeking in '%s': %s\n", fname, strerror (errno)); + xfree (fname); + es_fclose (fp); + return rc; + } + + if (first != '(') + { + /* Key is already in the extended format. */ + return write_extended_private_key (0, fname, fp, 0, 0, + buffer, length, + timestamp, serialno, keyref, + dispserialno); + } + if (first == '(' && opt.enable_extended_key_format) + { + /* Key is in the old format - but we want the extended format. */ + return write_extended_private_key (0, fname, fp, 1, 0, + buffer, length, + timestamp, serialno, keyref, + dispserialno); + } + } + + if (opt.enable_extended_key_format) + return write_extended_private_key (0, fname, fp, 0, 1, + buffer, length, + timestamp, serialno, keyref, + dispserialno); + + if (es_fwrite (buffer, length, 1, fp) != 1) + { + gpg_error_t tmperr = gpg_error_from_syserror (); + log_error ("error writing '%s': %s\n", fname, gpg_strerror (tmperr)); + es_fclose (fp); + gnupg_remove (fname); + xfree (fname); + return tmperr; + } + + /* When force is given, the file might have to be truncated. */ + if (force && ftruncate (es_fileno (fp), es_ftello (fp))) + { + gpg_error_t tmperr = gpg_error_from_syserror (); + log_error ("error truncating '%s': %s\n", fname, gpg_strerror (tmperr)); + es_fclose (fp); + gnupg_remove (fname); + xfree (fname); + return tmperr; + } + + if (es_fclose (fp)) + { + gpg_error_t tmperr = gpg_error_from_syserror (); + log_error ("error closing '%s': %s\n", fname, gpg_strerror (tmperr)); + gnupg_remove (fname); + xfree (fname); + return tmperr; + } + bump_key_eventcounter (); + xfree (fname); + return 0; +} + + +/* Callback function to try the unprotection from the passphrase query + code. */ +static gpg_error_t +try_unprotect_cb (struct pin_entry_info_s *pi) +{ + struct try_unprotect_arg_s *arg = pi->check_cb_arg; + ctrl_t ctrl = arg->ctrl; + size_t dummy; + gpg_error_t err; + gnupg_isotime_t now, protected_at, tmptime; + char *desc = NULL; + + 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 + { + 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; + + assert (arg.unprotected_key); + canlen = gcry_sexp_canon_len (arg.unprotected_key, 0, NULL, NULL); + rc = gcry_sexp_sscan (&s_skey, &erroff, + (char*)arg.unprotected_key, canlen); + if (rc) + { + log_error ("failed to build S-Exp (off=%u): %s\n", + (unsigned int)erroff, gpg_strerror (rc)); + wipememory (arg.unprotected_key, canlen); + xfree (arg.unprotected_key); + xfree (pi); + return rc; + } + rc = agent_protect_and_store (ctrl, s_skey, NULL); + gcry_sexp_release (s_skey); + if (rc) + { + log_error ("changing the passphrase failed: %s\n", + gpg_strerror (rc)); + wipememory (arg.unprotected_key, canlen); + xfree (arg.unprotected_key); + xfree (pi); + return rc; + } + } + else + { + /* Passphrase is fine. */ + agent_put_cache (ctrl, hexgrip, cache_mode, pi->pin, + lookup_ttl? lookup_ttl (hexgrip) : 0); + agent_store_cache_hit (hexgrip); + if (r_passphrase && *pi->pin) + *r_passphrase = xtrystrdup (pi->pin); + } + xfree (*keybuf); + *keybuf = arg.unprotected_key; + } + xfree (pi); + return rc; +} + + +/* Read the key identified by GRIP from the private key directory and + * return it as an gcrypt S-expression object in RESULT. If R_KEYMETA + * is not NULl and the extended key format is used, the meta data + * items are stored there. However the "Key:" item is removed from + * it. On failure returns an error code and stores NULL at RESULT. */ +static gpg_error_t +read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta) +{ + gpg_error_t err; + char *fname; + estream_t fp; + struct stat st; + unsigned char *buf; + size_t buflen, erroff; + gcry_sexp_t s_skey; + char hexgrip[40+4+1]; + char first; + + *result = NULL; + if (r_keymeta) + *r_keymeta = NULL; + + bin2hex (grip, 20, hexgrip); + strcpy (hexgrip+40, ".key"); + + fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, + hexgrip, NULL); + fp = es_fopen (fname, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) != GPG_ERR_ENOENT) + log_error ("can't open '%s': %s\n", fname, gpg_strerror (err)); + xfree (fname); + return err; + } + + if (es_fread (&first, 1, 1, fp) != 1) + { + err = gpg_error_from_syserror (); + log_error ("error reading first byte from '%s': %s\n", + fname, gpg_strerror (err)); + xfree (fname); + es_fclose (fp); + return err; + } + + if (es_fseek (fp, 0, SEEK_SET)) + { + err = gpg_error_from_syserror (); + log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err)); + xfree (fname); + es_fclose (fp); + return err; + } + + if (first != '(') + { + /* Key is in extended format. */ + nvc_t pk; + int line; + + err = nvc_parse_private_key (&pk, &line, fp); + es_fclose (fp); + + if (err) + log_error ("error parsing '%s' line %d: %s\n", + fname, line, gpg_strerror (err)); + else + { + err = nvc_get_private_key (pk, result); + if (err) + log_error ("error getting private key from '%s': %s\n", + fname, gpg_strerror (err)); + else + nvc_delete_named (pk, "Key:"); + } + + if (!err && r_keymeta) + *r_keymeta = pk; + else + nvc_release (pk); + xfree (fname); + return err; + } + + if (fstat (es_fileno (fp), &st)) + { + err = gpg_error_from_syserror (); + log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err)); + xfree (fname); + es_fclose (fp); + return err; + } + + buflen = st.st_size; + buf = xtrymalloc (buflen+1); + if (!buf) + { + err = gpg_error_from_syserror (); + log_error ("error allocating %zu bytes for '%s': %s\n", + buflen, fname, gpg_strerror (err)); + xfree (fname); + es_fclose (fp); + xfree (buf); + return err; + + } + + if (es_fread (buf, buflen, 1, fp) != 1) + { + err = gpg_error_from_syserror (); + log_error ("error reading %zu bytes from '%s': %s\n", + buflen, fname, gpg_strerror (err)); + xfree (fname); + es_fclose (fp); + xfree (buf); + return err; + } + + /* Convert the file into a gcrypt S-expression object. */ + err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); + xfree (fname); + es_fclose (fp); + xfree (buf); + if (err) + { + log_error ("failed to build S-Exp (off=%u): %s\n", + (unsigned int)erroff, gpg_strerror (err)); + return err; + } + *result = s_skey; + return 0; +} + + +/* Remove the key identified by GRIP from the private key directory. */ +static gpg_error_t +remove_key_file (const unsigned char *grip) +{ + gpg_error_t err = 0; + char *fname; + char hexgrip[40+4+1]; + + bin2hex (grip, 20, hexgrip); + strcpy (hexgrip+40, ".key"); + fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, + hexgrip, NULL); + if (gnupg_remove (fname)) + err = gpg_error_from_syserror (); + xfree (fname); + return err; +} + + +/* Return the secret key as an S-Exp in RESULT after locating it using + the GRIP. If the operation shall be diverted to a token, an + allocated S-expression with the shadow_info part from the file is + stored at SHADOW_INFO; if not NULL will be stored at SHADOW_INFO. + CACHE_MODE defines now the cache shall be used. DESC_TEXT may be + set to present a custom description for the pinentry. LOOKUP_TTL + is an optional function to convey a TTL to the cache manager; we do + not simply pass the TTL value because the value is only needed if + an unprotect action was needed and looking up the TTL may have some + overhead (e.g. scanning the sshcontrol file). If a CACHE_NONCE is + given that cache item is first tried to get a passphrase. If + R_PASSPHRASE is not NULL, the function succeeded and the key was + protected the used passphrase (entered or from the cache) is stored + there; if not NULL will be stored. The caller needs to free the + returned passphrase. */ +gpg_error_t +agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, + const char *desc_text, + const unsigned char *grip, unsigned char **shadow_info, + cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, + gcry_sexp_t *result, char **r_passphrase) +{ + gpg_error_t err; + unsigned char *buf; + size_t len, buflen, erroff; + gcry_sexp_t s_skey; + + *result = NULL; + if (shadow_info) + *shadow_info = NULL; + if (r_passphrase) + *r_passphrase = NULL; + + err = read_key_file (grip, &s_skey, 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) + return err; + + switch (agent_private_key_type (buf)) + { + case PRIVATE_KEY_CLEAR: + break; /* no unprotection needed */ + case PRIVATE_KEY_OPENPGP_NONE: + { + unsigned char *buf_new; + size_t buf_newlen; + + err = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen); + if (err) + log_error ("failed to convert unprotected openpgp key: %s\n", + gpg_strerror (err)); + else + { + xfree (buf); + buf = buf_new; + } + } + break; + case PRIVATE_KEY_PROTECTED: + { + char *desc_text_final; + char *comment = 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; +} + + +/* Return the key for the keygrip GRIP. The result is stored at + RESULT. This function extracts the key from the private key + database and returns it as an S-expression object as it is. On + failure an error code is returned and NULL stored at RESULT. */ +gpg_error_t +agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t *result) +{ + gpg_error_t err; + gcry_sexp_t s_skey; + + (void)ctrl; + + *result = NULL; + + err = read_key_file (grip, &s_skey, NULL); + if (!err) + *result = s_skey; + return err; +} + + +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); + 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); + 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. */ + 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)"); + assert (argidx < DIM (args)); + args[argidx++] = &array[idx]; + } + *p++ = ')'; + if (uri) + { + p = stpcpy (p, "(uri %b)"); + 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)"); + assert (argidx+1 < DIM (args)); + comment_intlen = (int)comment_length; + args[argidx++] = (void *)&comment_intlen; + args[argidx++] = (void*)&comment; + } + *p++ = ')'; + *p = 0; + 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); + 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); + assert (n); + *r_shadow_info = xtrymalloc (n); + if (!*r_shadow_info) + err = gpg_error_from_syserror (); + else + memcpy (*r_shadow_info, s, n); + } + } + break; + default: + err = gpg_error (GPG_ERR_BAD_SECKEY); + break; + } + + if (!err && r_keytype) + *r_keytype = keytype; + + xfree (buf); + return err; +} + + + +/* Delete the key with GRIP from the disk after having asked for + * confirmation using DESC_TEXT. If FORCE is set the function won't + * require a confirmation via Pinentry or warns if the key is also + * used by ssh. If ONLY_STUBS is set only stub keys (references to + * smartcards) will be affected. + * + * Common error codes are: + * GPG_ERR_NO_SECKEY + * GPG_ERR_KEY_ON_CARD + * GPG_ERR_NOT_CONFIRMED + * GPG_ERR_FORBIDDEN - Not a stub key and ONLY_STUBS requested. + */ +gpg_error_t +agent_delete_key (ctrl_t ctrl, const char *desc_text, + const unsigned char *grip, int force, int only_stubs) +{ + gpg_error_t err; + gcry_sexp_t s_skey = NULL; + unsigned char *buf = NULL; + size_t len; + char *desc_text_final = NULL; + char *comment = NULL; + ssh_control_file_t cf = NULL; + char hexgrip[40+4+1]; + char *default_desc = NULL; + int key_type; + + err = read_key_file (grip, &s_skey, NULL); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + err = gpg_error (GPG_ERR_NO_SECKEY); + if (err) + goto leave; + + err = make_canon_sexp (s_skey, &buf, &len); + if (err) + goto leave; + + key_type = agent_private_key_type (buf); + if (only_stubs && key_type != PRIVATE_KEY_SHADOWED) + { + err = gpg_error (GPG_ERR_FORBIDDEN); + goto leave; + } + + switch (key_type) + { + case PRIVATE_KEY_CLEAR: + case PRIVATE_KEY_OPENPGP_NONE: + case PRIVATE_KEY_PROTECTED: + bin2hex (grip, 20, hexgrip); + if (!force) + { + if (!desc_text) + { + default_desc = xtryasprintf + (L_("Do you really want to delete the key identified by keygrip%%0A" + " %s%%0A %%C%%0A?"), hexgrip); + desc_text = default_desc; + } + + /* Note, that we will take the comment as a C string for + display purposes; i.e. all stuff beyond a Nul character is + ignored. */ + { + gcry_sexp_t comment_sexp; + + comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); + if (comment_sexp) + comment = gcry_sexp_nth_string (comment_sexp, 1); + gcry_sexp_release (comment_sexp); + } + + if (desc_text) + err = agent_modify_description (desc_text, comment, s_skey, + &desc_text_final); + if (err) + goto leave; + + err = agent_get_confirmation (ctrl, desc_text_final, + L_("Delete key"), L_("No"), 0); + if (err) + goto leave; + + cf = ssh_open_control_file (); + if (cf) + { + if (!ssh_search_control_file (cf, hexgrip, NULL, NULL, NULL)) + { + err = agent_get_confirmation + (ctrl, + L_("Warning: This key is also listed for use with SSH!\n" + "Deleting the key might remove your ability to " + "access remote machines."), + L_("Delete key"), L_("No"), 0); + if (err) + goto leave; + } + } + } + err = remove_key_file (grip); + break; + + case PRIVATE_KEY_SHADOWED: + err = remove_key_file (grip); + break; + + default: + log_error ("invalid private key format\n"); + err = gpg_error (GPG_ERR_BAD_SECKEY); + break; + } + + leave: + ssh_close_control_file (cf); + gcry_free (comment); + xfree (desc_text_final); + xfree (default_desc); + xfree (buf); + gcry_sexp_release (s_skey); + return err; +} + + +/* Write an S-expression formatted shadow key to our key storage. + Shadow key is created by an S-expression public key in PKBUF and + card's SERIALNO and the IDSTRING. With FORCE passed as true an + existing key with the given GRIP will get overwritten. If + DISPSERIALNO is not NULL the human readable s/n will also be + recorded in the key file. If MAYBE_UPDATE is set it is assumed that + the shadow key already exists and we test whether we should update + it (FORCE is ignored in this case). */ +gpg_error_t +agent_write_shadow_key (int maybe_update, const unsigned char *grip, + const char *serialno, const char *keyid, + const unsigned char *pkbuf, int force, + const char *dispserialno) +{ + gpg_error_t err; + unsigned char *shadow_info; + unsigned char *shdkey; + size_t len; + char *fname = NULL; + estream_t fp = NULL; + char first; + + if (maybe_update && !opt.enable_extended_key_format) + return 0; /* Silently ignore. */ + + /* 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); + + if (maybe_update) /* Update mode. */ + { + fname = fname_from_keygrip (grip); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + + fp = es_fopen (fname, "rb+,mode=-rw"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("shadow key file '%s' disappeared\n", fname); + goto leave; + } + + /* See if an existing key is in extended format. */ + 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; + } + + /* "(first == '(')" indicates that the key is in the old format. */ + err = write_extended_private_key (maybe_update, + fname, fp, (first == '('), 0, + shdkey, len, + 0, serialno, keyid, + dispserialno); + fname = NULL; /* Ownership was transferred. */ + fp = NULL; /* Ditto. */ + } + else /* Standard mode */ + { + err = agent_write_private_key (grip, shdkey, len, force, 0, + serialno, keyid, dispserialno); + } + + leave: + xfree (fname); + es_fclose (fp); + xfree (shdkey); + if (err) + log_error ("error %s key: %s\n", maybe_update? "updating":"writing", + gpg_strerror (err)); + + return err; +} diff --git a/agent/genkey.c b/agent/genkey.c new file mode 100644 index 0000000..a944ac7 --- /dev/null +++ b/agent/genkey.c @@ -0,0 +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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <unistd.h> + +#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, -1); + if (rc) + { + xfree (buf); + return rc; + } + xfree (buf); + buf = p; + } + + rc = agent_write_private_key (grip, buf, len, force, timestamp, + NULL, NULL, NULL); + 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/gpg-agent-w32info.rc b/agent/gpg-agent-w32info.rc new file mode 100644 index 0000000..a0311b2 --- /dev/null +++ b/agent/gpg-agent-w32info.rc @@ -0,0 +1,52 @@ +/* gpg-agent-w32info.rc -*- c -*- + * Copyright (C) 2013 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s private key daemon\0" + VALUE "InternalName", "gpg-agent\0" + VALUE "OriginalFilename", "gpg-agent.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "gpg-agent.w32-manifest" diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c new file mode 100644 index 0000000..53b86dd --- /dev/null +++ b/agent/gpg-agent.c @@ -0,0 +1,3266 @@ +/* gpg-agent.c - The GnuPG Agent + * Copyright (C) 2000-2020 Free Software Foundation, Inc. + * Copyright (C) 2000-2019 Werner Koch + * Copyright (C) 2015-2020 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <time.h> +#include <fcntl.h> +#include <sys/stat.h> +#ifdef HAVE_W32_SYSTEM +# ifndef WINVER +# define WINVER 0x0500 /* Same as in common/sysutils.c */ +# endif +# ifdef HAVE_WINSOCK2_H +# include <winsock2.h> +# endif +# include <aclapi.h> +# include <sddl.h> +#else /*!HAVE_W32_SYSTEM*/ +# include <sys/socket.h> +# include <sys/un.h> +#endif /*!HAVE_W32_SYSTEM*/ +#include <unistd.h> +#ifdef HAVE_SIGNAL_H +# include <signal.h> +#endif +#include <npth.h> + +#define INCLUDED_BY_MAIN_MODULE 1 +#define GNUPG_COMMON_NEED_AFLOCAL +#include "agent.h" +#include <assuan.h> /* Malloc hooks and socket wrappers. */ + +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/gc-opt-flags.h" +#include "../common/exechelp.h" +#include "../common/asshelp.h" +#include "../common/init.h" + + +enum cmd_and_opt_values +{ aNull = 0, + oCsh = 'c', + oQuiet = 'q', + oSh = 's', + oVerbose = 'v', + + oNoVerbose = 500, + aGPGConfList, + aGPGConfTest, + aUseStandardSocketP, + oOptions, + oDebug, + oDebugAll, + oDebugLevel, + oDebugWait, + oDebugQuickRandom, + oDebugPinentry, + oNoOptions, + oHomedir, + oNoDetach, + oGrab, + oNoGrab, + oLogFile, + oServer, + oDaemon, + oSupervised, + oBatch, + + oPinentryProgram, + oPinentryTouchFile, + oPinentryInvisibleChar, + oPinentryTimeout, + oPinentryFormattedPassphrase, + oDisplay, + oTTYname, + oTTYtype, + oLCctype, + oLCmessages, + oXauthority, + oScdaemonProgram, + oDefCacheTTL, + oDefCacheTTLSSH, + oMaxCacheTTL, + oMaxCacheTTLSSH, + oEnforcePassphraseConstraints, + oMinPassphraseLen, + oMinPassphraseNonalpha, + oCheckPassphrasePattern, + oCheckSymPassphrasePattern, + oMaxPassphraseDays, + oEnablePassphraseHistory, + oDisableExtendedKeyFormat, + oEnableExtendedKeyFormat, + oStealSocket, + oUseStandardSocket, + oNoUseStandardSocket, + oExtraSocket, + oBrowserSocket, + oFakedSystemTime, + + oIgnoreCacheForSigning, + oAllowMarkTrusted, + oNoAllowMarkTrusted, + oNoUserTrustlist, + oSysTrustlistName, + oAllowPresetPassphrase, + oAllowLoopbackPinentry, + oNoAllowLoopbackPinentry, + oNoAllowExternalCache, + oAllowEmacsPinentry, + oKeepTTY, + oKeepDISPLAY, + oSSHSupport, + oSSHFingerprintDigest, + oPuttySupport, + oDisableScdaemon, + oDisableCheckOwnSocket, + oS2KCount, + oS2KCalibration, + oAutoExpandSecmem, + oListenBacklog, + + oWriteEnvFile, + + oNoop +}; + + +#ifndef ENAMETOOLONG +# define ENAMETOOLONG EINVAL +#endif + +static ARGPARSE_OPTS opts[] = { + + ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), + ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), + ARGPARSE_c (aUseStandardSocketP, "use-standard-socket-p", "@"), + + + ARGPARSE_header (NULL, N_("Options used for startup")), + + ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")), + ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")), +#ifndef HAVE_W32_SYSTEM + ARGPARSE_s_n (oSupervised, "supervised", N_("run in supervised mode")), +#endif + ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), + ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), + ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")), + ARGPARSE_s_n (oStealSocket, "steal-socket", "@"), + ARGPARSE_s_s (oDisplay, "display", "@"), + ARGPARSE_s_s (oTTYname, "ttyname", "@"), + ARGPARSE_s_s (oTTYtype, "ttytype", "@"), + ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), + ARGPARSE_s_s (oLCmessages, "lc-messages", "@"), + ARGPARSE_s_s (oXauthority, "xauthority", "@"), + ARGPARSE_s_s (oHomedir, "homedir", "@"), + ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")), + ARGPARSE_noconffile (oNoOptions, "no-options", "@"), + + + ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")), + + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_n (oDebugAll, "debug-all", "@"), + ARGPARSE_s_s (oDebugLevel, "debug-level", "@"), + ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), + ARGPARSE_s_n (oDebugQuickRandom, "debug-quick-random", "@"), + ARGPARSE_s_n (oDebugPinentry, "debug-pinentry", "@"), + ARGPARSE_s_s (oLogFile, "log-file", + /* */ N_("|FILE|write server mode logs to FILE")), + + + ARGPARSE_header ("Configuration", + N_("Options controlling the configuration")), + + ARGPARSE_s_n (oDisableScdaemon, "disable-scdaemon", + /* */ N_("do not use the SCdaemon") ), + ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program", + /* */ N_("|PGM|use PGM as the SCdaemon program") ), + ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), + + ARGPARSE_s_s (oExtraSocket, "extra-socket", + /* */ N_("|NAME|accept some commands via NAME")), + + ARGPARSE_s_s (oBrowserSocket, "browser-socket", "@"), + ARGPARSE_s_n (oKeepTTY, "keep-tty", + /* */ N_("ignore requests to change the TTY")), + ARGPARSE_s_n (oKeepDISPLAY, "keep-display", + /* */ N_("ignore requests to change the X display")), + ARGPARSE_s_n (oSSHSupport, "enable-ssh-support", N_("enable ssh support")), + ARGPARSE_s_s (oSSHFingerprintDigest, "ssh-fingerprint-digest", + N_("|ALGO|use ALGO to show ssh fingerprints")), + ARGPARSE_s_n (oPuttySupport, "enable-putty-support", +#ifdef HAVE_W32_SYSTEM + /* */ N_("enable putty support") +#else + /* */ "@" +#endif + ), + ARGPARSE_s_n (oDisableExtendedKeyFormat, "disable-extended-key-format", "@"), + ARGPARSE_s_n (oEnableExtendedKeyFormat, "enable-extended-key-format", "@"), + ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), + ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"), + ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), + + + ARGPARSE_header ("Security", N_("Options controlling the security")), + + ARGPARSE_s_u (oDefCacheTTL, "default-cache-ttl", + N_("|N|expire cached PINs after N seconds")), + ARGPARSE_s_u (oDefCacheTTLSSH, "default-cache-ttl-ssh", + /* */ N_("|N|expire SSH keys after N seconds")), + ARGPARSE_s_u (oMaxCacheTTL, "max-cache-ttl", + /* */ N_("|N|set maximum PIN cache lifetime to N seconds")), + ARGPARSE_s_u (oMaxCacheTTLSSH, "max-cache-ttl-ssh", + /* */ N_("|N|set maximum SSH key lifetime to N seconds")), + ARGPARSE_s_n (oIgnoreCacheForSigning, "ignore-cache-for-signing", + /* */ N_("do not use the PIN cache when signing")), + ARGPARSE_s_n (oNoAllowExternalCache, "no-allow-external-cache", + /* */ N_("disallow the use of an external password cache")), + ARGPARSE_s_n (oNoAllowMarkTrusted, "no-allow-mark-trusted", + /* */ N_("disallow clients to mark keys as \"trusted\"")), + ARGPARSE_s_n (oAllowMarkTrusted, "allow-mark-trusted", "@"), + ARGPARSE_s_n (oNoUserTrustlist, "no-user-trustlist", "@"), + ARGPARSE_s_s (oSysTrustlistName, "sys-trustlist-name", "@"), + ARGPARSE_s_n (oAllowPresetPassphrase, "allow-preset-passphrase", + /* */ N_("allow presetting passphrase")), + ARGPARSE_s_u (oS2KCount, "s2k-count", "@"), + ARGPARSE_s_u (oS2KCalibration, "s2k-calibration", "@"), + + ARGPARSE_header ("Passphrase policy", + N_("Options enforcing a passphrase policy")), + + ARGPARSE_s_n (oEnforcePassphraseConstraints, "enforce-passphrase-constraints", + N_("do not allow bypassing the passphrase policy")), + ARGPARSE_s_u (oMinPassphraseLen, "min-passphrase-len", + N_("|N|set minimal required length for new passphrases to N")), + ARGPARSE_s_u (oMinPassphraseNonalpha, "min-passphrase-nonalpha", + N_("|N|require at least N non-alpha" + " characters for a new passphrase")), + ARGPARSE_s_s (oCheckPassphrasePattern, "check-passphrase-pattern", + N_("|FILE|check new passphrases against pattern in FILE")), + ARGPARSE_s_s (oCheckSymPassphrasePattern, "check-sym-passphrase-pattern", + "@"), + ARGPARSE_s_u (oMaxPassphraseDays, "max-passphrase-days", + N_("|N|expire the passphrase after N days")), + ARGPARSE_s_n (oEnablePassphraseHistory, "enable-passphrase-history", + N_("do not allow the reuse of old passphrases")), + + + ARGPARSE_header ("Pinentry", N_("Options controlling the PIN-Entry")), + + ARGPARSE_s_n (oBatch, "batch", N_("never use the PIN-entry")), + ARGPARSE_s_n (oNoAllowLoopbackPinentry, "no-allow-loopback-pinentry", + N_("disallow caller to override the pinentry")), + ARGPARSE_s_n (oAllowLoopbackPinentry, "allow-loopback-pinentry", "@"), + ARGPARSE_s_n (oGrab, "grab", N_("let PIN-Entry grab keyboard and mouse")), + ARGPARSE_s_n (oNoGrab, "no-grab", "@"), + ARGPARSE_s_s (oPinentryProgram, "pinentry-program", + /* */ N_("|PGM|use PGM as the PIN-Entry program")), + ARGPARSE_s_s (oPinentryTouchFile, "pinentry-touch-file", "@"), + ARGPARSE_s_s (oPinentryInvisibleChar, "pinentry-invisible-char", "@"), + ARGPARSE_s_u (oPinentryTimeout, "pinentry-timeout", + N_("|N|set the Pinentry timeout to N seconds")), + ARGPARSE_s_n (oPinentryFormattedPassphrase, "pinentry-formatted-passphrase", + "@"), + ARGPARSE_s_n (oAllowEmacsPinentry, "allow-emacs-pinentry", + N_("allow passphrase to be prompted through Emacs")), + + /* Dummy options for backward compatibility. */ + ARGPARSE_o_s (oWriteEnvFile, "write-env-file", "@"), + ARGPARSE_s_n (oUseStandardSocket, "use-standard-socket", "@"), + ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"), + + /* Dummy options. */ + + + ARGPARSE_end () /* End of list */ +}; + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_MPI_VALUE , "mpi" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_MEMORY_VALUE , "memory" }, + { DBG_CACHE_VALUE , "cache" }, + { DBG_MEMSTAT_VALUE, "memstat" }, + { DBG_HASHING_VALUE, "hashing" }, + { DBG_IPC_VALUE , "ipc" }, + { 77, NULL } /* 77 := Do not exit on "help" or "?". */ + }; + + + +#define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */ +#define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */ +#define MAX_CACHE_TTL (120*60) /* 2 hours */ +#define MAX_CACHE_TTL_SSH (120*60) /* 2 hours */ +#define MIN_PASSPHRASE_LEN (8) +#define MIN_PASSPHRASE_NONALPHA (1) +#define MAX_PASSPHRASE_DAYS (0) + +/* The timer tick used for housekeeping stuff. Note that on Windows + * we use a SetWaitableTimer seems to signal earlier than about 2 + * seconds. Thus we use 4 seconds on all platforms except for + * Windowsce. CHECK_OWN_SOCKET_INTERVAL defines how often we check + * our own socket in standard socket mode. If that value is 0 we + * don't check at all. All values are in seconds. */ +#if defined(HAVE_W32CE_SYSTEM) +# define TIMERTICK_INTERVAL (60) +# define CHECK_OWN_SOCKET_INTERVAL (0) /* Never */ +#else +# define TIMERTICK_INTERVAL (4) +# define CHECK_OWN_SOCKET_INTERVAL (60) +#endif + + +/* Flag indicating that the ssh-agent subsystem has been enabled. */ +static int ssh_support; + +#ifdef HAVE_W32_SYSTEM +/* Flag indicating that support for Putty has been enabled. */ +static int putty_support; +/* A magic value used with WM_COPYDATA. */ +#define PUTTY_IPC_MAGIC 0x804e50ba +/* To avoid surprises we limit the size of the mapped IPC file to this + value. Putty currently (0.62) uses 8k, thus 16k should be enough + for the foreseeable future. */ +#define PUTTY_IPC_MAXLEN 16384 +#endif /*HAVE_W32_SYSTEM*/ + +/* The list of open file descriptors at startup. Note that this list + * has been allocated using the standard malloc. */ +#ifndef HAVE_W32_SYSTEM +static int *startup_fd_list; +#endif + +/* The signal mask at startup and a flag telling whether it is valid. */ +#ifdef HAVE_SIGPROCMASK +static sigset_t startup_signal_mask; +static int startup_signal_mask_valid; +#endif + +/* Flag to indicate that a shutdown was requested. */ +static int shutdown_pending; + +/* Counter for the currently running own socket checks. */ +static int check_own_socket_running; + +/* Flags to indicate that check_own_socket shall not be called. */ +static int disable_check_own_socket; + +/* Flag indicating that we are in supervised mode. */ +static int is_supervised; + +/* Flag indicating to start the daemon even if one already runs. */ +static int steal_socket; + +/* Flag to inhibit socket removal in cleanup. */ +static int inhibit_socket_removal; + +/* It is possible that we are currently running under setuid permissions */ +static int maybe_setuid = 1; + +/* Name of the communication socket used for native gpg-agent + requests. The second variable is either NULL or a malloced string + with the real socket name in case it has been redirected. */ +static char *socket_name; +static char *redir_socket_name; + +/* Name of the optional extra socket used for native gpg-agent requests. */ +static char *socket_name_extra; +static char *redir_socket_name_extra; + +/* Name of the optional browser socket used for native gpg-agent requests. */ +static char *socket_name_browser; +static char *redir_socket_name_browser; + +/* Name of the communication socket used for ssh-agent protocol. */ +static char *socket_name_ssh; +static char *redir_socket_name_ssh; + +/* We need to keep track of the server's nonces (these are dummies for + POSIX systems). */ +static assuan_sock_nonce_t socket_nonce; +static assuan_sock_nonce_t socket_nonce_extra; +static assuan_sock_nonce_t socket_nonce_browser; +static assuan_sock_nonce_t socket_nonce_ssh; + +/* Value for the listen() backlog argument. We use the same value for + * all sockets - 64 is on current Linux half of the default maximum. + * Let's try this as default. Change at runtime with --listen-backlog. */ +static int listen_backlog = 64; + +/* Default values for options passed to the pinentry. */ +static char *default_display; +static char *default_ttyname; +static char *default_ttytype; +static char *default_lc_ctype; +static char *default_lc_messages; +static char *default_xauthority; + +/* Name of a config file which was last read on startup or, if missing, + * the name of the standard config file. Any value here enables the + * rereading of the standard config files on SIGHUP. */ +static char *config_filename; + +/* Helper to implement --debug-level */ +static const char *debug_level; + +/* Keep track of the current log file so that we can avoid updating + the log file after a SIGHUP if it didn't changed. Malloced. */ +static char *current_logfile; + +/* The handle_tick() function may test whether a parent is still + * running. We record the PID of the parent here or -1 if it should + * be watched. */ +static pid_t parent_pid = (pid_t)(-1); + +/* This flag is true if the inotify mechanism for detecting the + * removal of the homedir is active. This flag is used to disable the + * alternative but portable stat based check. */ +static int have_homedir_inotify; + +/* Depending on how gpg-agent was started, the homedir inotify watch + * may not be reliable. This flag is set if we assume that inotify + * works reliable. */ +static int reliable_homedir_inotify; + +/* Number of active connections. */ +static int active_connections; + +/* This object is used to dispatch progress messages from Libgcrypt to + * the right thread. Given that we will have at max only a few dozen + * connections at a time, using a linked list is the easiest way to + * handle this. */ +struct progress_dispatch_s +{ + struct progress_dispatch_s *next; + /* The control object of the connection. If this is NULL no + * connection is associated with this item and it is free for reuse + * by new connections. */ + ctrl_t ctrl; + + /* The thread id of (npth_self) of the connection. */ + npth_t tid; + + /* The callback set by the connection. This is similar to the + * Libgcrypt callback but with the control object passed as the + * first argument. */ + void (*cb)(ctrl_t ctrl, + const char *what, int printchar, + int current, int total); +}; +struct progress_dispatch_s *progress_dispatch_list; + + + + +/* + Local prototypes. + */ + +static char *create_socket_name (char *standard_name, int with_homedir); +static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin, + char **r_redir_name, + assuan_sock_nonce_t *nonce); +static void create_directories (void); + +static void agent_libgcrypt_progress_cb (void *data, const char *what, + int printchar, + int current, int total); +static void agent_init_default_ctrl (ctrl_t ctrl); +static void agent_deinit_default_ctrl (ctrl_t ctrl); + +static void handle_connections (gnupg_fd_t listen_fd, + gnupg_fd_t listen_fd_extra, + gnupg_fd_t listen_fd_browser, + gnupg_fd_t listen_fd_ssh); +static void check_own_socket (void); +static int check_for_running_agent (int silent); + +/* Pth wrapper function definitions. */ +ASSUAN_SYSTEM_NPTH_IMPL; + + +/* + Functions. + */ + +/* Allocate a string describing a library version by calling a GETFNC. + This function is expected to be called only once. GETFNC is + expected to have a semantic like gcry_check_version (). */ +static char * +make_libversion (const char *libname, const char *(*getfnc)(const char*)) +{ + const char *s; + char *result; + + if (maybe_setuid) + { + gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ + maybe_setuid = 0; + } + s = getfnc (NULL); + result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); + strcpy (stpcpy (stpcpy (result, libname), " "), s); + return result; +} + +/* Return strings describing this program. The case values are + described in common/argparse.c:strusage. The values here override + the default values given by strusage. */ +static const char * +my_strusage (int level) +{ + static char *ver_gcry; + const char *p; + + switch (level) + { + case 9: p = "GPL-3.0-or-later"; break; + case 11: p = "@GPG_AGENT@ (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug + reporting address. This is so that we can change the + reporting address without breaking the translations. */ + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 20: + if (!ver_gcry) + ver_gcry = make_libversion ("libgcrypt", gcry_check_version); + p = ver_gcry; + break; + + case 1: + case 40: p = _("Usage: @GPG_AGENT@ [options] (-h for help)"); + break; + case 41: p = _("Syntax: @GPG_AGENT@ [options] [command [args]]\n" + "Secret key management for @GNUPG@\n"); + break; + + default: p = NULL; + } + return p; +} + + + +/* Setup the debugging. With the global variable DEBUG_LEVEL set to NULL + only the active debug flags are propagated to the subsystems. With + DEBUG_LEVEL set, a specific set of debug flags is set; thus overriding + all flags already set. Note that we don't fail here, because it is + important to keep gpg-agent running even after re-reading the + options due to a SIGHUP. */ +static void +set_debug (void) +{ + int numok = (debug_level && digitp (debug_level)); + int numlvl = numok? atoi (debug_level) : 0; + + if (!debug_level) + ; + else if (!strcmp (debug_level, "none") || (numok && numlvl < 1)) + opt.debug = 0; + else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2)) + opt.debug = DBG_IPC_VALUE; + else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) + opt.debug = DBG_IPC_VALUE; + else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) + opt.debug = (DBG_IPC_VALUE | DBG_CACHE_VALUE); + else if (!strcmp (debug_level, "guru") || numok) + { + opt.debug = ~0; + /* Unless the "guru" string has been used we don't want to allow + hashing debugging. The rationale is that people tend to + select the highest debug value and would then clutter their + disk with debug files which may reveal confidential data. */ + if (numok) + opt.debug &= ~(DBG_HASHING_VALUE); + } + else + { + log_error (_("invalid debug-level '%s' given\n"), debug_level); + opt.debug = 0; /* Reset debugging, so that prior debug + statements won't have an undesired effect. */ + } + + if (opt.debug && !opt.verbose) + opt.verbose = 1; + if (opt.debug && opt.quiet) + opt.quiet = 0; + + if (opt.debug & DBG_MPI_VALUE) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); + if (opt.debug & DBG_CRYPTO_VALUE ) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + + if (opt.debug) + parse_debug_flag (NULL, &opt.debug, debug_flags); +} + + +/* Helper for cleanup to remove one socket with NAME. REDIR_NAME is + the corresponding real name if the socket has been redirected. */ +static void +remove_socket (char *name, char *redir_name) +{ + if (name && *name) + { + if (redir_name) + name = redir_name; + + gnupg_remove (name); + *name = 0; + } +} + + +/* Discover which inherited file descriptors correspond to which + * services/sockets offered by gpg-agent, using the LISTEN_FDS and + * LISTEN_FDNAMES convention. The understood labels are "ssh", + * "extra", and "browser". "std" or other labels will be interpreted + * as the standard socket. + * + * This function is designed to log errors when the expected file + * descriptors don't make sense, but to do its best to continue to + * work even in the face of minor misconfigurations. + * + * For more information on the LISTEN_FDS convention, see + * sd_listen_fds(3) on certain Linux distributions. + */ +#ifndef HAVE_W32_SYSTEM +static void +map_supervised_sockets (gnupg_fd_t *r_fd, + gnupg_fd_t *r_fd_extra, + gnupg_fd_t *r_fd_browser, + gnupg_fd_t *r_fd_ssh) +{ + struct { + const char *label; + int **fdaddr; + char **nameaddr; + } tbl[] = { + { "ssh", &r_fd_ssh, &socket_name_ssh }, + { "browser", &r_fd_browser, &socket_name_browser }, + { "extra", &r_fd_extra, &socket_name_extra }, + { "std", &r_fd, &socket_name } /* (Must be the last item.) */ + }; + const char *envvar; + char **fdnames; + int nfdnames; + int fd_count; + + *r_fd = *r_fd_extra = *r_fd_browser = *r_fd_ssh = -1; + + /* Print a warning if LISTEN_PID does not match outr pid. */ + envvar = getenv ("LISTEN_PID"); + if (!envvar) + log_error ("no LISTEN_PID environment variable found in " + "--supervised mode (ignoring)\n"); + else if (strtoul (envvar, NULL, 10) != (unsigned long)getpid ()) + log_error ("environment variable LISTEN_PID (%lu) does not match" + " our pid (%lu) in --supervised mode (ignoring)\n", + (unsigned long)strtoul (envvar, NULL, 10), + (unsigned long)getpid ()); + + /* Parse LISTEN_FDNAMES into the array FDNAMES. */ + envvar = getenv ("LISTEN_FDNAMES"); + if (envvar) + { + fdnames = strtokenize (envvar, ":"); + if (!fdnames) + { + log_error ("strtokenize failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + agent_exit (1); + } + for (nfdnames=0; fdnames[nfdnames]; nfdnames++) + ; + } + else + { + fdnames = NULL; + nfdnames = 0; + } + + /* Parse LISTEN_FDS into fd_count or provide a replacement. */ + envvar = getenv ("LISTEN_FDS"); + if (envvar) + fd_count = atoi (envvar); + else if (fdnames) + { + log_error ("no LISTEN_FDS environment variable found in --supervised" + " mode (relying on LISTEN_FDNAMES instead)\n"); + fd_count = nfdnames; + } + else + { + log_error ("no LISTEN_FDS or LISTEN_FDNAMES environment variables " + "found in --supervised mode" + " (assuming 1 active descriptor)\n"); + fd_count = 1; + } + + if (fd_count < 1) + { + log_error ("--supervised mode expects at least one file descriptor" + " (was told %d, carrying on as though it were 1)\n", + fd_count); + fd_count = 1; + } + + /* Assign the descriptors to the return values. */ + if (!fdnames) + { + struct stat statbuf; + + if (fd_count != 1) + log_error ("no LISTEN_FDNAMES and LISTEN_FDS (%d) != 1" + " in --supervised mode." + " (ignoring all sockets but the first one)\n", + fd_count); + if (fstat (3, &statbuf) == -1 && errno ==EBADF) + log_fatal ("file descriptor 3 must be valid in --supervised mode" + " if LISTEN_FDNAMES is not set\n"); + *r_fd = 3; + socket_name = gnupg_get_socket_name (3); + } + else if (fd_count != nfdnames) + { + log_fatal ("number of items in LISTEN_FDNAMES (%d) does not match " + "LISTEN_FDS (%d) in --supervised mode\n", + nfdnames, fd_count); + } + else + { + int i, j, fd; + char *name; + + for (i = 0; i < nfdnames; i++) + { + for (j = 0; j < DIM (tbl); j++) + { + if (!strcmp (fdnames[i], tbl[j].label) || j == DIM(tbl)-1) + { + fd = 3 + i; + if (**tbl[j].fdaddr == -1) + { + name = gnupg_get_socket_name (fd); + if (name) + { + **tbl[j].fdaddr = fd; + *tbl[j].nameaddr = name; + log_info ("using fd %d for %s socket (%s)\n", + fd, tbl[j].label, name); + } + else + { + log_error ("cannot listen on fd %d for %s socket\n", + fd, tbl[j].label); + close (fd); + } + } + else + { + log_error ("cannot listen on more than one %s socket\n", + tbl[j].label); + close (fd); + } + break; + } + } + } + } + + xfree (fdnames); +} +#endif /*!HAVE_W32_SYSTEM*/ + + +/* Cleanup code for this program. This is either called has an atexit + handler or directly. */ +static void +cleanup (void) +{ + static int done; + + if (done) + return; + done = 1; + deinitialize_module_cache (); + if (!is_supervised && !inhibit_socket_removal) + { + remove_socket (socket_name, redir_socket_name); + if (opt.extra_socket > 1) + remove_socket (socket_name_extra, redir_socket_name_extra); + if (opt.browser_socket > 1) + remove_socket (socket_name_browser, redir_socket_name_browser); + remove_socket (socket_name_ssh, redir_socket_name_ssh); + } +} + + + +/* Handle options which are allowed to be reset after program start. + Return true when the current option in PARGS could be handled and + false if not. As a special feature, passing a value of NULL for + PARGS, resets the options to the default. REREAD should be set + true if it is not the initial option parsing. */ +static int +parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) +{ + int i; + + if (!pargs) + { /* reset mode */ + opt.quiet = 0; + opt.verbose = 0; + opt.debug = 0; + opt.no_grab = 1; + opt.debug_pinentry = 0; + opt.pinentry_program = NULL; + opt.pinentry_touch_file = NULL; + xfree (opt.pinentry_invisible_char); + opt.pinentry_invisible_char = NULL; + opt.pinentry_timeout = 0; + opt.pinentry_formatted_passphrase = 0; + opt.scdaemon_program = NULL; + opt.def_cache_ttl = DEFAULT_CACHE_TTL; + opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH; + opt.max_cache_ttl = MAX_CACHE_TTL; + opt.max_cache_ttl_ssh = MAX_CACHE_TTL_SSH; + opt.enforce_passphrase_constraints = 0; + opt.min_passphrase_len = MIN_PASSPHRASE_LEN; + opt.min_passphrase_nonalpha = MIN_PASSPHRASE_NONALPHA; + opt.check_passphrase_pattern = NULL; + opt.check_sym_passphrase_pattern = NULL; + opt.max_passphrase_days = MAX_PASSPHRASE_DAYS; + opt.enable_passphrase_history = 0; + opt.enable_extended_key_format = 1; + opt.ignore_cache_for_signing = 0; + opt.allow_mark_trusted = 1; + opt.sys_trustlist_name = NULL; + opt.allow_external_cache = 1; + opt.allow_loopback_pinentry = 1; + opt.allow_emacs_pinentry = 0; + opt.disable_scdaemon = 0; + disable_check_own_socket = 0; + /* Note: When changing the next line, change also gpgconf_list. */ + opt.ssh_fingerprint_digest = GCRY_MD_MD5; + opt.s2k_count = 0; + set_s2k_calibration_time (0); /* Set to default. */ + return 1; + } + + switch (pargs->r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + + case oDebug: + parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags); + break; + case oDebugAll: opt.debug = ~0; break; + case oDebugLevel: debug_level = pargs->r.ret_str; break; + case oDebugPinentry: opt.debug_pinentry = 1; break; + + case oLogFile: + if (!reread) + return 0; /* not handeld */ + if (!current_logfile || !pargs->r.ret_str + || strcmp (current_logfile, pargs->r.ret_str)) + { + log_set_file (pargs->r.ret_str); + xfree (current_logfile); + current_logfile = xtrystrdup (pargs->r.ret_str); + } + break; + + case oNoGrab: opt.no_grab |= 1; break; + case oGrab: opt.no_grab |= 2; break; + + case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break; + case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break; + case oPinentryInvisibleChar: + xfree (opt.pinentry_invisible_char); + opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break; + break; + case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break; + case oPinentryFormattedPassphrase: + opt.pinentry_formatted_passphrase = 1; + break; + case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break; + case oDisableScdaemon: opt.disable_scdaemon = 1; break; + case oDisableCheckOwnSocket: disable_check_own_socket = 1; break; + + case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break; + case oDefCacheTTLSSH: opt.def_cache_ttl_ssh = pargs->r.ret_ulong; break; + case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break; + case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break; + + case oEnforcePassphraseConstraints: + opt.enforce_passphrase_constraints=1; + break; + case oMinPassphraseLen: opt.min_passphrase_len = pargs->r.ret_ulong; break; + case oMinPassphraseNonalpha: + opt.min_passphrase_nonalpha = pargs->r.ret_ulong; + break; + case oCheckPassphrasePattern: + opt.check_passphrase_pattern = pargs->r.ret_str; + break; + case oCheckSymPassphrasePattern: + opt.check_sym_passphrase_pattern = pargs->r.ret_str; + break; + case oMaxPassphraseDays: + opt.max_passphrase_days = pargs->r.ret_ulong; + break; + case oEnablePassphraseHistory: + opt.enable_passphrase_history = 1; + break; + + case oEnableExtendedKeyFormat: + opt.enable_extended_key_format = 2; + break; + case oDisableExtendedKeyFormat: + if (opt.enable_extended_key_format != 2) + opt.enable_extended_key_format = 0; + break; + + case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break; + + case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break; + case oNoAllowMarkTrusted: opt.allow_mark_trusted = 0; break; + case oNoUserTrustlist: opt.no_user_trustlist = 1; break; + case oSysTrustlistName: opt.sys_trustlist_name = pargs->r.ret_str; break; + + case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break; + + case oAllowLoopbackPinentry: opt.allow_loopback_pinentry = 1; break; + case oNoAllowLoopbackPinentry: opt.allow_loopback_pinentry = 0; break; + + case oNoAllowExternalCache: opt.allow_external_cache = 0; + break; + + case oAllowEmacsPinentry: opt.allow_emacs_pinentry = 1; + break; + + case oSSHFingerprintDigest: + i = gcry_md_map_name (pargs->r.ret_str); + if (!i) + log_error (_("selected digest algorithm is invalid\n")); + else + opt.ssh_fingerprint_digest = i; + break; + + case oS2KCount: + opt.s2k_count = pargs->r.ret_ulong; + break; + + case oS2KCalibration: + set_s2k_calibration_time (pargs->r.ret_ulong); + break; + + case oNoop: break; + + default: + return 0; /* not handled */ + } + + return 1; /* handled */ +} + + +/* Fixup some options after all have been processed. */ +static void +finalize_rereadable_options (void) +{ + /* Hack to allow --grab to override --no-grab. */ + if ((opt.no_grab & 2)) + opt.no_grab = 0; + + /* With --no-user-trustlist it does not make sense to allow the mark + * trusted feature. */ + if (opt.no_user_trustlist) + opt.allow_mark_trusted = 0; +} + + +static void +thread_init_once (void) +{ + static int npth_initialized = 0; + + if (!npth_initialized) + { + npth_initialized++; + npth_init (); + } + gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); + /* Now that we have set the syscall clamp we need to tell Libgcrypt + * that it should get them from libgpg-error. Note that Libgcrypt + * has already been initialized but at that point nPth was not + * initialized and thus Libgcrypt could not set its system call + * clamp. */ + gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0); +} + + +static void +initialize_modules (void) +{ + thread_init_once (); + assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); + initialize_module_cache (); + initialize_module_call_pinentry (); + initialize_module_call_scd (); + initialize_module_trustlist (); +} + + +/* The main entry point. */ +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int orig_argc; + char **orig_argv; + char *last_configname = NULL; + const char *configname = NULL; + int debug_argparser = 0; + const char *shell; + int pipe_server = 0; + int is_daemon = 0; + int nodetach = 0; + int csh_style = 0; + char *logfile = NULL; + int debug_wait = 0; + int gpgconf_list = 0; + gpg_error_t err; + struct assuan_malloc_hooks malloc_hooks; + + early_system_init (); + + /* Before we do anything else we save the list of currently open + file descriptors and the signal mask. This info is required to + do the exec call properly. We don't need it on Windows. */ +#ifndef HAVE_W32_SYSTEM + startup_fd_list = get_all_open_fds (); +#endif /*!HAVE_W32_SYSTEM*/ +#ifdef HAVE_SIGPROCMASK + if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask)) + startup_signal_mask_valid = 1; +#endif /*HAVE_SIGPROCMASK*/ + + /* Set program name etc. */ + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + /* Please note that we may running SUID(ROOT), so be very CAREFUL + when adding any stuff between here and the call to INIT_SECMEM() + somewhere after the option parsing */ + log_set_prefix (GPG_AGENT_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID); + + /* Make sure that our subsystems are ready. */ + i18n_init (); + init_common_subsystems (&argc, &argv); + + malloc_hooks.malloc = gcry_malloc; + malloc_hooks.realloc = gcry_realloc; + malloc_hooks.free = gcry_free; + assuan_set_malloc_hooks (&malloc_hooks); + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + assuan_sock_init (); + assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH); + setup_libassuan_logging (&opt.debug, NULL); + + setup_libgcrypt_logging (); + gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); + gcry_set_progress_handler (agent_libgcrypt_progress_cb, NULL); + + disable_core_dumps (); + + /* Set default options. */ + parse_rereadable_options (NULL, 0); /* Reset them to default values. */ + + shell = getenv ("SHELL"); + if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) + csh_style = 1; + + /* Record some of the original environment strings. */ + { + const char *s; + int idx; + static const char *names[] = + { "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL }; + + err = 0; + opt.startup_env = session_env_new (); + if (!opt.startup_env) + err = gpg_error_from_syserror (); + for (idx=0; !err && names[idx]; idx++) + { + s = getenv (names[idx]); + if (s) + err = session_env_setenv (opt.startup_env, names[idx], s); + } + if (!err) + { + s = gnupg_ttyname (0); + if (s) + err = session_env_setenv (opt.startup_env, "GPG_TTY", s); + } + if (err) + log_fatal ("error recording startup environment: %s\n", + gpg_strerror (err)); + + /* Fixme: Better use the locale function here. */ + opt.startup_lc_ctype = getenv ("LC_CTYPE"); + if (opt.startup_lc_ctype) + opt.startup_lc_ctype = xstrdup (opt.startup_lc_ctype); + opt.startup_lc_messages = getenv ("LC_MESSAGES"); + if (opt.startup_lc_messages) + opt.startup_lc_messages = xstrdup (opt.startup_lc_messages); + } + + /* Check whether we have a config file on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + while (gnupg_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oDebug: + case oDebugAll: + debug_argparser++; + break; + + case oHomedir: + gnupg_set_homedir (pargs.r.ret_str); + break; + + case oDebugQuickRandom: + gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); + break; + } + } + /* Reset the flags. */ + pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + + /* Initialize the secure memory. */ + gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0); + maybe_setuid = 0; + + /* + * Now we are now working under our real uid + */ + + gnupg_set_confdir (GNUPG_CONFDIR_SYS, gnupg_sysconfdir ()); + gnupg_set_confdir (GNUPG_CONFDIR_USER, gnupg_homedir ()); + + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + /* We are re-using the struct, thus the reset flag. We OR the + * flags so that the internal intialized flag won't be cleared. */ + pargs.flags |= (ARGPARSE_FLAG_RESET + | ARGPARSE_FLAG_KEEP + | ARGPARSE_FLAG_SYS + | ARGPARSE_FLAG_USER); + + while (gnupg_argparser (&pargs, opts, GPG_AGENT_NAME EXTSEP_S "conf")) + { + if (pargs.r_opt == ARGPARSE_CONFFILE) + { + if (debug_argparser) + log_info (_("reading options from '%s'\n"), + pargs.r_type? pargs.r.ret_str: "[cmdline]"); + if (pargs.r_type) + { + xfree (last_configname); + last_configname = xstrdup (pargs.r.ret_str); + configname = last_configname; + } + else + configname = NULL; + continue; + } + if (parse_rereadable_options (&pargs, 0)) + continue; /* Already handled */ + switch (pargs.r_opt) + { + case aGPGConfList: gpgconf_list = 1; break; + case aGPGConfTest: gpgconf_list = 2; break; + case aUseStandardSocketP: gpgconf_list = 3; break; + case oBatch: opt.batch=1; break; + + case oDebugWait: debug_wait = pargs.r.ret_int; break; + + case oNoVerbose: opt.verbose = 0; break; + case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; + case oNoDetach: nodetach = 1; break; + case oLogFile: logfile = pargs.r.ret_str; break; + case oCsh: csh_style = 1; break; + case oSh: csh_style = 0; break; + case oServer: pipe_server = 1; break; + case oDaemon: is_daemon = 1; break; + case oStealSocket: steal_socket = 1; break; + case oSupervised: is_supervised = 1; break; + + case oDisplay: default_display = xstrdup (pargs.r.ret_str); break; + case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break; + case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break; + case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break; + case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str); + break; + case oXauthority: default_xauthority = xstrdup (pargs.r.ret_str); + break; + + case oUseStandardSocket: + case oNoUseStandardSocket: + obsolete_option (configname, pargs.lineno, "use-standard-socket"); + break; + + case oFakedSystemTime: + { + time_t faked_time = isotime2epoch (pargs.r.ret_str); + if (faked_time == (time_t)(-1)) + faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10); + gnupg_set_time (faked_time, 0); + } + break; + + case oKeepTTY: opt.keep_tty = 1; break; + case oKeepDISPLAY: opt.keep_display = 1; break; + + case oSSHSupport: + ssh_support = 1; + break; + + case oPuttySupport: +# ifdef HAVE_W32_SYSTEM + putty_support = 1; +# endif + break; + + case oExtraSocket: + opt.extra_socket = 1; /* (1 = points into argv) */ + socket_name_extra = pargs.r.ret_str; + break; + + case oBrowserSocket: + opt.browser_socket = 1; /* (1 = points into argv) */ + socket_name_browser = pargs.r.ret_str; + break; + + case oAutoExpandSecmem: + /* Try to enable this option. It will officially only be + * supported by Libgcrypt 1.9 but 1.8.2 already supports it + * on the quiet and thus we use the numeric value value. */ + gcry_control (78 /*GCRYCTL_AUTO_EXPAND_SECMEM*/, + (unsigned int)pargs.r.ret_ulong, 0); + break; + + case oListenBacklog: + listen_backlog = pargs.r.ret_int; + break; + + case oDebugQuickRandom: + /* Only used by the first stage command line parser. */ + break; + + case oWriteEnvFile: + obsolete_option (configname, pargs.lineno, "write-env-file"); + break; + + default: + if (configname) + pargs.err = ARGPARSE_PRINT_WARNING; + else + pargs.err = ARGPARSE_PRINT_ERROR; + break; + } + } + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (_("Note: '%s' is not considered an option\n"), argv[i]); + } + + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (!last_configname) + config_filename = make_filename (gnupg_homedir (), + GPG_AGENT_NAME EXTSEP_S "conf", + NULL); + else + { + config_filename = last_configname; + last_configname = NULL; + } + + if (log_get_errorcount(0)) + exit(2); + + finalize_rereadable_options (); + + +#ifdef ENABLE_NLS + /* gpg-agent usually does not output any messages because it runs in + the background. For log files it is acceptable to have messages + always encoded in utf-8. We switch here to utf-8, so that + commands like --help still give native messages. It is far + easier to switch only once instead of for every message and it + actually helps when more then one thread is active (avoids an + extra copy step). */ + bind_textdomain_codeset (PACKAGE_GT, "UTF-8"); +#endif + + if (!pipe_server && !is_daemon && !gpgconf_list && !is_supervised) + { + /* We have been called without any command and thus we merely + check whether an agent is already running. We do this right + here so that we don't clobber a logfile with this check but + print the status directly to stderr. */ + opt.debug = 0; + set_debug (); + check_for_running_agent (0); + agent_exit (0); + } + + if (is_supervised) + ; + else if (!opt.extra_socket) + opt.extra_socket = 1; + else if (socket_name_extra + && (!strcmp (socket_name_extra, "none") + || !strcmp (socket_name_extra, "/dev/null"))) + { + /* User requested not to create this socket. */ + opt.extra_socket = 0; + socket_name_extra = NULL; + } + + if (is_supervised) + ; + else if (!opt.browser_socket) + opt.browser_socket = 1; + else if (socket_name_browser + && (!strcmp (socket_name_browser, "none") + || !strcmp (socket_name_browser, "/dev/null"))) + { + /* User requested not to create this socket. */ + opt.browser_socket = 0; + socket_name_browser = NULL; + } + + set_debug (); + + if (atexit (cleanup)) + { + log_error ("atexit failed\n"); + cleanup (); + exit (1); + } + + /* Try to create missing directories. */ + if (!gpgconf_list) + create_directories (); + + if (debug_wait && pipe_server) + { + thread_init_once (); + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + gnupg_sleep (debug_wait); + log_debug ("... okay\n"); + } + + if (gpgconf_list == 3) + { + /* We now use the standard socket always - return true for + backward compatibility. */ + agent_exit (0); + } + else if (gpgconf_list == 2) + agent_exit (0); + else if (gpgconf_list) + { + es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT); + es_printf ("default-cache-ttl:%lu:%d:\n", + GC_OPT_FLAG_DEFAULT, DEFAULT_CACHE_TTL ); + es_printf ("default-cache-ttl-ssh:%lu:%d:\n", + GC_OPT_FLAG_DEFAULT, DEFAULT_CACHE_TTL_SSH ); + es_printf ("max-cache-ttl:%lu:%d:\n", + GC_OPT_FLAG_DEFAULT, MAX_CACHE_TTL ); + es_printf ("max-cache-ttl-ssh:%lu:%d:\n", + GC_OPT_FLAG_DEFAULT, MAX_CACHE_TTL_SSH ); + es_printf ("enforce-passphrase-constraints:%lu:\n", + GC_OPT_FLAG_NONE); + es_printf ("min-passphrase-len:%lu:%d:\n", + GC_OPT_FLAG_DEFAULT, MIN_PASSPHRASE_LEN ); + es_printf ("min-passphrase-nonalpha:%lu:%d:\n", + GC_OPT_FLAG_DEFAULT, MIN_PASSPHRASE_NONALPHA); + es_printf ("max-passphrase-days:%lu:%d:\n", + GC_OPT_FLAG_DEFAULT, MAX_PASSPHRASE_DAYS); + es_printf ("ssh-fingerprint-digest:%lu:\"%s:\n", + GC_OPT_FLAG_DEFAULT, "md5"); + + agent_exit (0); + } + + /* Now start with logging to a file if this is desired. */ + if (logfile) + { + log_set_file (logfile); + log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX + | GPGRT_LOG_WITH_TIME + | GPGRT_LOG_WITH_PID)); + current_logfile = xstrdup (logfile); + } + + /* Make sure that we have a default ttyname. */ + if (!default_ttyname && gnupg_ttyname (1)) + default_ttyname = xstrdup (gnupg_ttyname (1)); + if (!default_ttytype && getenv ("TERM")) + default_ttytype = xstrdup (getenv ("TERM")); + + + if (pipe_server) + { + /* This is the simple pipe based server */ + ctrl_t ctrl; + + initialize_modules (); + + ctrl = xtrycalloc (1, sizeof *ctrl); + if (!ctrl) + { + log_error ("error allocating connection control data: %s\n", + strerror (errno) ); + agent_exit (1); + } + ctrl->session_env = session_env_new (); + if (!ctrl->session_env) + { + log_error ("error allocating session environment block: %s\n", + strerror (errno) ); + xfree (ctrl); + agent_exit (1); + } + agent_init_default_ctrl (ctrl); + start_command_handler (ctrl, GNUPG_INVALID_FD, GNUPG_INVALID_FD); + agent_deinit_default_ctrl (ctrl); + xfree (ctrl); + } + else if (is_supervised) + { +#ifndef HAVE_W32_SYSTEM + gnupg_fd_t fd, fd_extra, fd_browser, fd_ssh; + + initialize_modules (); + + /* when supervised and sending logs to stderr, the process + supervisor should handle log entry metadata (pid, name, + timestamp) */ + if (!logfile) + log_set_prefix (NULL, 0); + + log_info ("%s %s starting in supervised mode.\n", + strusage(11), strusage(13) ); + + /* See below in "regular server mode" on why we remove certain + * envvars. */ + if (!opt.keep_display) + gnupg_unsetenv ("DISPLAY"); + gnupg_unsetenv ("INSIDE_EMACS"); + + /* Virtually create the sockets. Note that we use -1 here + * because the whole thing works only on Unix. */ + map_supervised_sockets (&fd, &fd_extra, &fd_browser, &fd_ssh); + if (fd == -1) + log_fatal ("no standard socket provided\n"); + +#ifdef HAVE_SIGPROCMASK + if (startup_signal_mask_valid) + { + if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL)) + log_error ("error restoring signal mask: %s\n", + strerror (errno)); + } + else + log_info ("no saved signal mask\n"); +#endif /*HAVE_SIGPROCMASK*/ + + log_info ("listening on: std=%d extra=%d browser=%d ssh=%d\n", + fd, fd_extra, fd_browser, fd_ssh); + handle_connections (fd, fd_extra, fd_browser, fd_ssh); +#endif /*!HAVE_W32_SYSTEM*/ + } + else if (!is_daemon) + ; /* NOTREACHED */ + else + { /* Regular server mode */ + gnupg_fd_t fd; + gnupg_fd_t fd_extra = GNUPG_INVALID_FD; + gnupg_fd_t fd_browser = GNUPG_INVALID_FD; + gnupg_fd_t fd_ssh = GNUPG_INVALID_FD; +#ifndef HAVE_W32_SYSTEM + pid_t pid; +#endif + + /* Remove the DISPLAY variable so that a pinentry does not + default to a specific display. There is still a default + display when gpg-agent was started using --display or a + client requested this using an OPTION command. Note, that we + don't do this when running in reverse daemon mode (i.e. when + exec the program given as arguments). */ +#ifndef HAVE_W32_SYSTEM + if (!opt.keep_display && !argc) + gnupg_unsetenv ("DISPLAY"); +#endif + + /* Remove the INSIDE_EMACS variable so that a pinentry does not + always try to interact with Emacs. The variable is set when + a client requested this using an OPTION command. */ + gnupg_unsetenv ("INSIDE_EMACS"); + + /* Create the sockets. */ + socket_name = create_socket_name (GPG_AGENT_SOCK_NAME, 1); + fd = create_server_socket (socket_name, 1, 0, + &redir_socket_name, &socket_nonce); + + if (opt.extra_socket) + { + if (socket_name_extra) + socket_name_extra = create_socket_name (socket_name_extra, 0); + else + socket_name_extra = create_socket_name + /**/ (GPG_AGENT_EXTRA_SOCK_NAME, 1); + opt.extra_socket = 2; /* Indicate that it has been malloced. */ + fd_extra = create_server_socket (socket_name_extra, 0, 0, + &redir_socket_name_extra, + &socket_nonce_extra); + } + + if (opt.browser_socket) + { + if (socket_name_browser) + socket_name_browser = create_socket_name (socket_name_browser, 0); + else + socket_name_browser= create_socket_name + /**/ (GPG_AGENT_BROWSER_SOCK_NAME, 1); + opt.browser_socket = 2; /* Indicate that it has been malloced. */ + fd_browser = create_server_socket (socket_name_browser, 0, 0, + &redir_socket_name_browser, + &socket_nonce_browser); + } + + socket_name_ssh = create_socket_name (GPG_AGENT_SSH_SOCK_NAME, 1); + fd_ssh = create_server_socket (socket_name_ssh, 0, 1, + &redir_socket_name_ssh, + &socket_nonce_ssh); + + /* If we are going to exec a program in the parent, we record + the PID, so that the child may check whether the program is + still alive. */ + if (argc) + parent_pid = getpid (); + + fflush (NULL); + +#ifdef HAVE_W32_SYSTEM + + (void)csh_style; + (void)nodetach; + initialize_modules (); + +#else /*!HAVE_W32_SYSTEM*/ + + pid = fork (); + if (pid == (pid_t)-1) + { + log_fatal ("fork failed: %s\n", strerror (errno) ); + exit (1); + } + else if (pid) + { /* We are the parent */ + char *infostr_ssh_sock, *infostr_ssh_valid; + + /* Close the socket FD. */ + close (fd); + + /* The signal mask might not be correct right now and thus + we restore it. That is not strictly necessary but some + programs falsely assume a cleared signal mask. */ + +#ifdef HAVE_SIGPROCMASK + if (startup_signal_mask_valid) + { + if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL)) + log_error ("error restoring signal mask: %s\n", + strerror (errno)); + } + else + log_info ("no saved signal mask\n"); +#endif /*HAVE_SIGPROCMASK*/ + + /* Create the SSH info string if enabled. */ + if (ssh_support) + { + if (asprintf (&infostr_ssh_sock, "SSH_AUTH_SOCK=%s", + socket_name_ssh) < 0) + { + log_error ("out of core\n"); + kill (pid, SIGTERM); + exit (1); + } + if (asprintf (&infostr_ssh_valid, "gnupg_SSH_AUTH_SOCK_by=%lu", + (unsigned long)getpid()) < 0) + { + log_error ("out of core\n"); + kill (pid, SIGTERM); + exit (1); + } + } + + *socket_name = 0; /* Don't let cleanup() remove the socket - + the child should do this from now on */ + if (opt.extra_socket) + *socket_name_extra = 0; + if (opt.browser_socket) + *socket_name_browser = 0; + *socket_name_ssh = 0; + + if (argc) + { /* Run the program given on the commandline. */ + if (ssh_support && (putenv (infostr_ssh_sock) + || putenv (infostr_ssh_valid))) + { + log_error ("failed to set environment: %s\n", + strerror (errno) ); + kill (pid, SIGTERM ); + exit (1); + } + + /* Close all the file descriptors except the standard + ones and those open at startup. We explicitly don't + close 0,1,2 in case something went wrong collecting + them at startup. */ + close_all_fds (3, startup_fd_list); + + /* Run the command. */ + execvp (argv[0], argv); + log_error ("failed to run the command: %s\n", strerror (errno)); + kill (pid, SIGTERM); + exit (1); + } + else + { + /* Print the environment string, so that the caller can use + shell's eval to set it */ + if (csh_style) + { + if (ssh_support) + { + *strchr (infostr_ssh_sock, '=') = ' '; + es_printf ("setenv %s;\n", infostr_ssh_sock); + } + } + else + { + if (ssh_support) + { + es_printf ("%s; export SSH_AUTH_SOCK;\n", + infostr_ssh_sock); + } + } + if (ssh_support) + { + xfree (infostr_ssh_sock); + xfree (infostr_ssh_valid); + } + exit (0); + } + /*NOTREACHED*/ + } /* End parent */ + + /* + This is the child + */ + + initialize_modules (); + + /* Detach from tty and put process into a new session */ + if (!nodetach ) + { + int i; + unsigned int oldflags; + + /* Close stdin, stdout and stderr unless it is the log stream */ + for (i=0; i <= 2; i++) + { + if (!log_test_fd (i) && i != fd ) + { + if ( ! close (i) + && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1) + { + log_error ("failed to open '%s': %s\n", + "/dev/null", strerror (errno)); + cleanup (); + exit (1); + } + } + } + if (setsid() == -1) + { + log_error ("setsid() failed: %s\n", strerror(errno) ); + cleanup (); + exit (1); + } + + log_get_prefix (&oldflags); + log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED); + opt.running_detached = 1; + + /* Unless we are running with a program given on the command + * line we can assume that the inotify things works and thus + * we can avoid tye regular stat calls. */ + if (!argc) + reliable_homedir_inotify = 1; + } + + { + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + } +#endif /*!HAVE_W32_SYSTEM*/ + + if (gnupg_chdir (gnupg_daemon_rootdir ())) + { + log_error ("chdir to '%s' failed: %s\n", + gnupg_daemon_rootdir (), strerror (errno)); + exit (1); + } + + log_info ("%s %s started\n", strusage(11), strusage(13) ); + handle_connections (fd, fd_extra, fd_browser, fd_ssh); + assuan_sock_close (fd); + } + + return 0; +} + + +/* Exit entry point. This function should be called instead of a + plain exit. */ +void +agent_exit (int rc) +{ + /*FIXME: update_random_seed_file();*/ + + /* We run our cleanup handler because that may close cipher contexts + stored in secure memory and thus this needs to be done before we + explicitly terminate secure memory. */ + cleanup (); + +#if 1 + /* at this time a bit annoying */ + if (opt.debug & DBG_MEMSTAT_VALUE) + { + gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); + gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); + } + if (opt.debug) + gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); +#endif + gcry_control (GCRYCTL_TERM_SECMEM ); + rc = rc? rc : log_get_errorcount(0)? 2 : 0; + exit (rc); +} + + +/* This is our callback function for gcrypt progress messages. It is + set once at startup and dispatches progress messages to the + corresponding threads of the agent. */ +static void +agent_libgcrypt_progress_cb (void *data, const char *what, int printchar, + int current, int total) +{ + struct progress_dispatch_s *dispatch; + npth_t mytid = npth_self (); + + (void)data; + + for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) + if (dispatch->ctrl && dispatch->tid == mytid) + break; + if (dispatch && dispatch->cb) + dispatch->cb (dispatch->ctrl, what, printchar, current, total); +} + + +/* If a progress dispatcher callback has been associated with the + * current connection unregister it. */ +static void +unregister_progress_cb (void) +{ + struct progress_dispatch_s *dispatch; + npth_t mytid = npth_self (); + + for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) + if (dispatch->ctrl && dispatch->tid == mytid) + break; + if (dispatch) + { + dispatch->ctrl = NULL; + dispatch->cb = NULL; + } +} + + +/* Setup a progress callback CB for the current connection. Using a + * CB of NULL disables the callback. */ +void +agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what, + int printchar, int current, int total), + ctrl_t ctrl) +{ + struct progress_dispatch_s *dispatch, *firstfree; + npth_t mytid = npth_self (); + + firstfree = NULL; + for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) + { + if (dispatch->ctrl && dispatch->tid == mytid) + break; + if (!dispatch->ctrl && !firstfree) + firstfree = dispatch; + } + if (!dispatch) /* None allocated: Reuse or allocate a new one. */ + { + if (firstfree) + { + dispatch = firstfree; + } + else if ((dispatch = xtrycalloc (1, sizeof *dispatch))) + { + dispatch->next = progress_dispatch_list; + progress_dispatch_list = dispatch; + } + else + { + log_error ("error allocating new progress dispatcher slot: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + return; + } + dispatch->ctrl = ctrl; + dispatch->tid = mytid; + } + + dispatch->cb = cb; +} + + +/* Each thread has its own local variables conveyed by a control + structure usually identified by an argument named CTRL. This + function is called immediately after allocating the control + structure. Its purpose is to setup the default values for that + structure. Note that some values may have already been set. */ +static void +agent_init_default_ctrl (ctrl_t ctrl) +{ + assert (ctrl->session_env); + + /* Note we ignore malloc errors because we can't do much about it + and the request will fail anyway shortly after this + initialization. */ + session_env_setenv (ctrl->session_env, "DISPLAY", default_display); + session_env_setenv (ctrl->session_env, "GPG_TTY", default_ttyname); + session_env_setenv (ctrl->session_env, "TERM", default_ttytype); + session_env_setenv (ctrl->session_env, "XAUTHORITY", default_xauthority); + session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", NULL); + + if (ctrl->lc_ctype) + xfree (ctrl->lc_ctype); + ctrl->lc_ctype = default_lc_ctype? xtrystrdup (default_lc_ctype) : NULL; + + if (ctrl->lc_messages) + xfree (ctrl->lc_messages); + ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages) + /**/ : NULL; + ctrl->cache_ttl_opt_preset = CACHE_TTL_OPT_PRESET; +} + + +/* Release all resources allocated by default in the control + structure. This is the counterpart to agent_init_default_ctrl. */ +static void +agent_deinit_default_ctrl (ctrl_t ctrl) +{ + unregister_progress_cb (); + session_env_release (ctrl->session_env); + + if (ctrl->lc_ctype) + xfree (ctrl->lc_ctype); + if (ctrl->lc_messages) + xfree (ctrl->lc_messages); +} + + +/* Because the ssh protocol does not send us information about the + current TTY setting, we use this function to use those from startup + or those explicitly set. This is also used for the restricted mode + where we ignore requests to change the environment. */ +gpg_error_t +agent_copy_startup_env (ctrl_t ctrl) +{ + gpg_error_t err = 0; + int iterator = 0; + const char *name, *value; + + while (!err && (name = session_env_list_stdenvnames (&iterator, NULL))) + { + if ((value = session_env_getenv (opt.startup_env, name))) + err = session_env_setenv (ctrl->session_env, name, value); + } + + if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype) + if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype))) + err = gpg_error_from_syserror (); + + if (!err && !ctrl->lc_messages && opt.startup_lc_messages) + if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages))) + err = gpg_error_from_syserror (); + + if (err) + log_error ("error setting default session environment: %s\n", + gpg_strerror (err)); + + return err; +} + + +/* Reread parts of the configuration. Note, that this function is + obviously not thread-safe and should only be called from the PTH + signal handler. + + Fixme: Due to the way the argument parsing works, we create a + memory leak here for all string type arguments. There is currently + no clean way to tell whether the memory for the argument has been + allocated or points into the process' original arguments. Unless + we have a mechanism to tell this, we need to live on with this. */ +static void +reread_configuration (void) +{ + ARGPARSE_ARGS pargs; + char *twopart; + int dummy; + + if (!config_filename) + return; /* No config file. */ + + twopart = strconcat (GPG_AGENT_NAME EXTSEP_S "conf" PATHSEP_S, + config_filename, NULL); + if (!twopart) + return; /* Out of core. */ + + parse_rereadable_options (NULL, 1); /* Start from the default values. */ + + memset (&pargs, 0, sizeof pargs); + dummy = 0; + pargs.argc = &dummy; + pargs.flags = (ARGPARSE_FLAG_KEEP + |ARGPARSE_FLAG_SYS + |ARGPARSE_FLAG_USER); + while (gnupg_argparser (&pargs, opts, twopart)) + { + if (pargs.r_opt == ARGPARSE_CONFFILE) + { + log_info (_("reading options from '%s'\n"), + pargs.r_type? pargs.r.ret_str: "[cmdline]"); + } + else if (pargs.r_opt < -1) + pargs.err = ARGPARSE_PRINT_WARNING; + else /* Try to parse this option - ignore unchangeable ones. */ + parse_rereadable_options (&pargs, 1); + } + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + xfree (twopart); + finalize_rereadable_options (); + set_debug (); +} + + +/* Return the file name of the socket we are using for native + requests. */ +const char * +get_agent_socket_name (void) +{ + const char *s = socket_name; + + return (s && *s)? s : NULL; +} + +/* Return the file name of the socket we are using for SSH + requests. */ +const char * +get_agent_ssh_socket_name (void) +{ + const char *s = socket_name_ssh; + + return (s && *s)? s : NULL; +} + + +/* Return the number of active connections. */ +int +get_agent_active_connection_count (void) +{ + return active_connections; +} + + +/* Under W32, this function returns the handle of the scdaemon + notification event. Calling it the first time creates that + event. */ +#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) +void * +get_agent_scd_notify_event (void) +{ + static HANDLE the_event = INVALID_HANDLE_VALUE; + + if (the_event == INVALID_HANDLE_VALUE) + { + HANDLE h, h2; + SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; + + /* We need to use a manual reset event object due to the way our + w32-pth wait function works: If we would use an automatic + reset event we are not able to figure out which handle has + been signaled because at the time we single out the signaled + handles using WFSO the event has already been reset due to + the WFMO. */ + h = CreateEvent (&sa, TRUE, FALSE, NULL); + if (!h) + log_error ("can't create scd notify event: %s\n", w32_strerror (-1) ); + else if (!DuplicateHandle (GetCurrentProcess(), h, + GetCurrentProcess(), &h2, + EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) + { + log_error ("setting synchronize for scd notify event failed: %s\n", + w32_strerror (-1) ); + CloseHandle (h); + } + else + { + CloseHandle (h); + the_event = h2; + } + } + + return the_event; +} +#endif /*HAVE_W32_SYSTEM && !HAVE_W32CE_SYSTEM*/ + + + +/* Create a name for the socket in the home directory as using + STANDARD_NAME. We also check for valid characters as well as + against a maximum allowed length for a unix domain socket is done. + The function terminates the process in case of an error. Returns: + Pointer to an allocated string with the absolute name of the socket + used. */ +static char * +create_socket_name (char *standard_name, int with_homedir) +{ + char *name; + + if (with_homedir) + name = make_filename (gnupg_socketdir (), standard_name, NULL); + else + name = make_filename (standard_name, NULL); + if (strchr (name, PATHSEP_C)) + { + log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S); + agent_exit (2); + } + return name; +} + + + +/* Create a Unix domain socket with NAME. Returns the file descriptor + or terminates the process in case of an error. Note that this + function needs to be used for the regular socket first (indicated + by PRIMARY) and only then for the extra and the ssh sockets. If + the socket has been redirected the name of the real socket is + stored as a malloced string at R_REDIR_NAME. If CYGWIN is set a + Cygwin compatible socket is created (Windows only). */ +static gnupg_fd_t +create_server_socket (char *name, int primary, int cygwin, + char **r_redir_name, assuan_sock_nonce_t *nonce) +{ + struct sockaddr *addr; + struct sockaddr_un *unaddr; + socklen_t len; + gnupg_fd_t fd; + int rc; + + xfree (*r_redir_name); + *r_redir_name = NULL; + + fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0); + if (fd == ASSUAN_INVALID_FD) + { + log_error (_("can't create socket: %s\n"), strerror (errno)); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + agent_exit (2); + } + + if (cygwin) + assuan_sock_set_flag (fd, "cygwin", 1); + + unaddr = xmalloc (sizeof *unaddr); + addr = (struct sockaddr*)unaddr; + + { + int redirected; + + if (assuan_sock_set_sockaddr_un (name, addr, &redirected)) + { + if (errno == ENAMETOOLONG) + log_error (_("socket name '%s' is too long\n"), name); + else + log_error ("error preparing socket '%s': %s\n", + name, gpg_strerror (gpg_error_from_syserror ())); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + xfree (unaddr); + agent_exit (2); + } + if (redirected) + { + *r_redir_name = xstrdup (unaddr->sun_path); + if (opt.verbose) + log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name); + } + } + + len = SUN_LEN (unaddr); + rc = assuan_sock_bind (fd, addr, len); + + /* Our error code mapping on W32CE returns EEXIST thus we also test + for this. */ + if (rc == -1 + && (errno == EADDRINUSE +#ifdef HAVE_W32_SYSTEM + || errno == EEXIST +#endif + )) + { + /* Check whether a gpg-agent is already running. We do this + test only if this is the primary socket. For secondary + sockets we assume that a test for gpg-agent has already been + done and reuse the requested socket. Testing the ssh-socket + is not possible because at this point, though we know the new + Assuan socket, the Assuan server and thus the ssh-agent + server is not yet operational; this would lead to a hang. */ + if (primary && !check_for_running_agent (1)) + { + if (steal_socket) + log_info (N_("trying to steal socket from running %s\n"), + "gpg-agent"); + else + { + log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX); + log_set_file (NULL); + log_error (_("a gpg-agent is already running - " + "not starting a new one\n")); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + assuan_sock_close (fd); + xfree (unaddr); + agent_exit (2); + } + } + gnupg_remove (unaddr->sun_path); + rc = assuan_sock_bind (fd, addr, len); + } + if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce))) + log_error (_("error getting nonce for the socket\n")); + if (rc == -1) + { + /* We use gpg_strerror here because it allows us to get strings + for some W32 socket error codes. */ + log_error (_("error binding socket to '%s': %s\n"), + unaddr->sun_path, + gpg_strerror (gpg_error_from_syserror ())); + + assuan_sock_close (fd); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + xfree (unaddr); + agent_exit (2); + } + + if (gnupg_chmod (unaddr->sun_path, "-rwx")) + log_error (_("can't set permissions of '%s': %s\n"), + unaddr->sun_path, strerror (errno)); + + if (listen (FD2INT(fd), listen_backlog ) == -1) + { + log_error ("listen(fd,%d) failed: %s\n", + listen_backlog, strerror (errno)); + *name = 0; /* Inhibit removal of the socket by cleanup(). */ + assuan_sock_close (fd); + xfree (unaddr); + agent_exit (2); + } + + if (opt.verbose) + log_info (_("listening on socket '%s'\n"), unaddr->sun_path); + + xfree (unaddr); + return fd; +} + + +/* Check that the directory for storing the private keys exists and + create it if not. This function won't fail as it is only a + convenience function and not strictly necessary. */ +static void +create_private_keys_directory (const char *home) +{ + char *fname; + struct stat statbuf; + + fname = make_filename (home, GNUPG_PRIVATE_KEYS_DIR, NULL); + if (gnupg_stat (fname, &statbuf) && errno == ENOENT) + { + if (gnupg_mkdir (fname, "-rwx")) + log_error (_("can't create directory '%s': %s\n"), + fname, strerror (errno) ); + else if (!opt.quiet) + log_info (_("directory '%s' created\n"), fname); + + if (gnupg_chmod (fname, "-rwx")) + log_error (_("can't set permissions of '%s': %s\n"), + fname, strerror (errno)); + } + else + { + /* The file exists or another error. Make sure we have sensible + * permissions. We enforce rwx for user but keep existing group + * permissions. Permissions for other are always cleared. */ + if (gnupg_chmod (fname, "-rwx...---")) + log_error (_("can't set permissions of '%s': %s\n"), + fname, strerror (errno)); + } + xfree (fname); +} + + +/* Create the directory only if the supplied directory name is the + same as the default one. This way we avoid to create arbitrary + directories when a non-default home directory is used. To cope + with HOME, we compare only the suffix if we see that the default + homedir does start with a tilde. We don't stop here in case of + problems because other functions will throw an error anyway.*/ +static void +create_directories (void) +{ + struct stat statbuf; + const char *defhome = standard_homedir (); + char *home; + + home = make_filename (gnupg_homedir (), NULL); + if (gnupg_stat (home, &statbuf)) + { + if (errno == ENOENT) + { + if ( +#ifdef HAVE_W32_SYSTEM + ( !compare_filenames (home, defhome) ) +#else + (*defhome == '~' + && (strlen (home) >= strlen (defhome+1) + && !strcmp (home + strlen(home) + - strlen (defhome+1), defhome+1))) + || (*defhome != '~' && !strcmp (home, defhome) ) +#endif + ) + { + if (gnupg_mkdir (home, "-rwx")) + log_error (_("can't create directory '%s': %s\n"), + home, strerror (errno) ); + else + { + if (!opt.quiet) + log_info (_("directory '%s' created\n"), home); + create_private_keys_directory (home); + } + } + } + else + log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno)); + } + else if ( !S_ISDIR(statbuf.st_mode)) + { + log_error (_("can't use '%s' as home directory\n"), home); + } + else /* exists and is a directory. */ + { + create_private_keys_directory (home); + } + xfree (home); +} + + + +/* This is the worker for the ticker. It is called every few seconds + and may only do fast operations. */ +static void +handle_tick (void) +{ + static time_t last_minute; + struct stat statbuf; + + if (!last_minute) + last_minute = time (NULL); + + /* Check whether the scdaemon has died and cleanup in this case. */ + agent_scd_check_aliveness (); + + /* If we are running as a child of another process, check whether + the parent is still alive and shutdown if not. */ +#ifndef HAVE_W32_SYSTEM + if (parent_pid != (pid_t)(-1)) + { + if (kill (parent_pid, 0)) + { + shutdown_pending = 2; + log_info ("parent process died - shutting down\n"); + log_info ("%s %s stopped\n", strusage(11), strusage(13) ); + cleanup (); + agent_exit (0); + } + } +#endif /*HAVE_W32_SYSTEM*/ + + /* Code to be run from time to time. */ +#if CHECK_OWN_SOCKET_INTERVAL > 0 + if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL)) + { + check_own_socket (); + last_minute = time (NULL); + } +#endif + + /* Need to check for expired cache entries. */ + agent_cache_housekeeping (); + + /* Check whether the homedir is still available. */ + if (!shutdown_pending + && (!have_homedir_inotify || !reliable_homedir_inotify) + && gnupg_stat (gnupg_homedir (), &statbuf) && errno == ENOENT) + { + shutdown_pending = 1; + log_info ("homedir has been removed - shutting down\n"); + } +} + + +/* A global function which allows us to call the reload stuff from + other places too. This is only used when build for W32. */ +void +agent_sighup_action (void) +{ + log_info ("SIGHUP received - " + "re-reading configuration and flushing cache\n"); + + agent_flush_cache (); + reread_configuration (); + agent_reload_trustlist (); + /* We flush the module name cache so that after installing a + "pinentry" binary that one can be used in case the + "pinentry-basic" fallback was in use. */ + gnupg_module_name_flush_some (); + + if (opt.disable_scdaemon) + agent_card_killscd (); +} + + +/* A helper function to handle SIGUSR2. */ +static void +agent_sigusr2_action (void) +{ + if (opt.verbose) + log_info ("SIGUSR2 received - updating card event counter\n"); + /* Nothing to check right now. We only increment a counter. */ + bump_card_eventcounter (); +} + + +#ifndef HAVE_W32_SYSTEM +/* The signal handler for this program. It is expected to be run in + its own thread and not in the context of a signal handler. */ +static void +handle_signal (int signo) +{ + switch (signo) + { +#ifndef HAVE_W32_SYSTEM + case SIGHUP: + agent_sighup_action (); + break; + + case SIGUSR1: + log_info ("SIGUSR1 received - printing internal information:\n"); + /* Fixme: We need to see how to integrate pth dumping into our + logging system. */ + /* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */ + agent_query_dump_state (); + agent_scd_dump_state (); + break; + + case SIGUSR2: + agent_sigusr2_action (); + break; + + case SIGTERM: + if (!shutdown_pending) + log_info ("SIGTERM received - shutting down ...\n"); + else + log_info ("SIGTERM received - still %i open connections\n", + active_connections); + shutdown_pending++; + if (shutdown_pending > 2) + { + log_info ("shutdown forced\n"); + log_info ("%s %s stopped\n", strusage(11), strusage(13) ); + cleanup (); + agent_exit (0); + } + break; + + case SIGINT: + log_info ("SIGINT received - immediate shutdown\n"); + log_info( "%s %s stopped\n", strusage(11), strusage(13)); + cleanup (); + agent_exit (0); + break; +#endif + default: + log_info ("signal %d received - no action defined\n", signo); + } +} +#endif + +/* Check the nonce on a new connection. This is a NOP unless we + are using our Unix domain socket emulation under Windows. */ +static int +check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce) +{ + if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce)) + { + log_info (_("error reading nonce on fd %d: %s\n"), + FD2INT(ctrl->thread_startup.fd), strerror (errno)); + assuan_sock_close (ctrl->thread_startup.fd); + xfree (ctrl); + return -1; + } + else + return 0; +} + + +#ifdef HAVE_W32_SYSTEM +/* The window message processing function for Putty. Warning: This + code runs as a native Windows thread. Use of our own functions + needs to be bracket with pth_leave/pth_enter. */ +static LRESULT CALLBACK +putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + int ret = 0; + int w32rc; + COPYDATASTRUCT *cds; + const char *mapfile; + HANDLE maphd; + PSID mysid = NULL; + PSID mapsid = NULL; + void *data = NULL; + PSECURITY_DESCRIPTOR psd = NULL; + ctrl_t ctrl = NULL; + + if (msg != WM_COPYDATA) + { + return DefWindowProc (hwnd, msg, wparam, lparam); + } + + cds = (COPYDATASTRUCT*)lparam; + if (cds->dwData != PUTTY_IPC_MAGIC) + return 0; /* Ignore data with the wrong magic. */ + mapfile = cds->lpData; + if (!cds->cbData || mapfile[cds->cbData - 1]) + return 0; /* Ignore empty and non-properly terminated strings. */ + + if (DBG_IPC) + { + npth_protect (); + log_debug ("ssh map file '%s'", mapfile); + npth_unprotect (); + } + + maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile); + if (DBG_IPC) + { + npth_protect (); + log_debug ("ssh map handle %p\n", maphd); + npth_unprotect (); + } + + if (!maphd || maphd == INVALID_HANDLE_VALUE) + return 0; + + npth_protect (); + + mysid = w32_get_user_sid (); + if (!mysid) + { + log_error ("error getting my sid\n"); + goto leave; + } + + w32rc = GetSecurityInfo (maphd, SE_KERNEL_OBJECT, + OWNER_SECURITY_INFORMATION, + &mapsid, NULL, NULL, NULL, + &psd); + if (w32rc) + { + log_error ("error getting sid of ssh map file: rc=%d", w32rc); + goto leave; + } + + if (DBG_IPC) + { + char *sidstr; + + if (!ConvertSidToStringSid (mysid, &sidstr)) + sidstr = NULL; + log_debug (" my sid: '%s'", sidstr? sidstr: "[error]"); + LocalFree (sidstr); + if (!ConvertSidToStringSid (mapsid, &sidstr)) + sidstr = NULL; + log_debug ("ssh map file sid: '%s'", sidstr? sidstr: "[error]"); + LocalFree (sidstr); + } + + if (!EqualSid (mysid, mapsid)) + { + log_error ("ssh map file has a non-matching sid\n"); + goto leave; + } + + data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0); + if (DBG_IPC) + log_debug ("ssh IPC buffer at %p\n", data); + if (!data) + goto leave; + + /* log_printhex (data, 20, "request:"); */ + + ctrl = xtrycalloc (1, sizeof *ctrl); + if (!ctrl) + { + log_error ("error allocating connection control data: %s\n", + strerror (errno) ); + goto leave; + } + ctrl->session_env = session_env_new (); + if (!ctrl->session_env) + { + log_error ("error allocating session environment block: %s\n", + strerror (errno) ); + goto leave; + } + + agent_init_default_ctrl (ctrl); + if (!serve_mmapped_ssh_request (ctrl, data, PUTTY_IPC_MAXLEN)) + ret = 1; /* Valid ssh message has been constructed. */ + agent_deinit_default_ctrl (ctrl); + /* log_printhex (data, 20, " reply:"); */ + + leave: + xfree (ctrl); + if (data) + UnmapViewOfFile (data); + xfree (mapsid); + if (psd) + LocalFree (psd); + xfree (mysid); + CloseHandle (maphd); + + npth_unprotect (); + + return ret; +} +#endif /*HAVE_W32_SYSTEM*/ + + +#ifdef HAVE_W32_SYSTEM +/* The thread handling Putty's IPC requests. */ +static void * +putty_message_thread (void *arg) +{ + WNDCLASS wndwclass = {0, putty_message_proc, 0, 0, + NULL, NULL, NULL, NULL, NULL, "Pageant"}; + HWND hwnd; + MSG msg; + + (void)arg; + + if (opt.verbose) + log_info ("putty message loop thread started\n"); + + /* The message loop runs as thread independent from our nPth system. + This also means that we need to make sure that we switch back to + our system before calling any no-windows function. */ + npth_unprotect (); + + /* First create a window to make sure that a message queue exists + for this thread. */ + if (!RegisterClass (&wndwclass)) + { + npth_protect (); + log_error ("error registering Pageant window class"); + return NULL; + } + hwnd = CreateWindowEx (0, "Pageant", "Pageant", 0, + 0, 0, 0, 0, + HWND_MESSAGE, /* hWndParent */ + NULL, /* hWndMenu */ + NULL, /* hInstance */ + NULL); /* lpParm */ + if (!hwnd) + { + npth_protect (); + log_error ("error creating Pageant window"); + return NULL; + } + + while (GetMessage(&msg, NULL, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + /* Back to nPth. */ + npth_protect (); + + if (opt.verbose) + log_info ("putty message loop thread stopped\n"); + return NULL; +} +#endif /*HAVE_W32_SYSTEM*/ + + +static void * +do_start_connection_thread (ctrl_t ctrl) +{ + active_connections++; + agent_init_default_ctrl (ctrl); + if (opt.verbose && !DBG_IPC) + log_info (_("handler 0x%lx for fd %d started\n"), + (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + + start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd); + if (opt.verbose && !DBG_IPC) + log_info (_("handler 0x%lx for fd %d terminated\n"), + (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + + agent_deinit_default_ctrl (ctrl); + xfree (ctrl); + active_connections--; + return NULL; +} + + +/* This is the standard connection thread's main function. */ +static void * +start_connection_thread_std (void *arg) +{ + ctrl_t ctrl = arg; + + if (check_nonce (ctrl, &socket_nonce)) + { + log_error ("handler 0x%lx nonce check FAILED\n", + (unsigned long) npth_self()); + return NULL; + } + + return do_start_connection_thread (ctrl); +} + + +/* This is the extra socket connection thread's main function. */ +static void * +start_connection_thread_extra (void *arg) +{ + ctrl_t ctrl = arg; + + if (check_nonce (ctrl, &socket_nonce_extra)) + { + log_error ("handler 0x%lx nonce check FAILED\n", + (unsigned long) npth_self()); + return NULL; + } + + ctrl->restricted = 1; + return do_start_connection_thread (ctrl); +} + + +/* This is the browser socket connection thread's main function. */ +static void * +start_connection_thread_browser (void *arg) +{ + ctrl_t ctrl = arg; + + if (check_nonce (ctrl, &socket_nonce_browser)) + { + log_error ("handler 0x%lx nonce check FAILED\n", + (unsigned long) npth_self()); + return NULL; + } + + ctrl->restricted = 2; + return do_start_connection_thread (ctrl); +} + + +/* This is the ssh connection thread's main function. */ +static void * +start_connection_thread_ssh (void *arg) +{ + ctrl_t ctrl = arg; + + if (check_nonce (ctrl, &socket_nonce_ssh)) + return NULL; + + active_connections++; + agent_init_default_ctrl (ctrl); + if (opt.verbose) + log_info (_("ssh handler 0x%lx for fd %d started\n"), + (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + + start_command_handler_ssh (ctrl, ctrl->thread_startup.fd); + if (opt.verbose) + log_info (_("ssh handler 0x%lx for fd %d terminated\n"), + (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + + agent_deinit_default_ctrl (ctrl); + xfree (ctrl); + active_connections--; + return NULL; +} + + +/* Connection handler loop. Wait for connection requests and spawn a + thread after accepting a connection. */ +static void +handle_connections (gnupg_fd_t listen_fd, + gnupg_fd_t listen_fd_extra, + gnupg_fd_t listen_fd_browser, + gnupg_fd_t listen_fd_ssh) +{ + gpg_error_t err; + npth_attr_t tattr; + struct sockaddr_un paddr; + socklen_t plen; + fd_set fdset, read_fdset; + int ret; + gnupg_fd_t fd; + int nfd; + int saved_errno; + struct timespec abstime; + struct timespec curtime; + struct timespec timeout; +#ifdef HAVE_W32_SYSTEM + HANDLE events[2]; + unsigned int events_set; +#endif + int sock_inotify_fd = -1; + int home_inotify_fd = -1; + struct { + const char *name; + void *(*func) (void *arg); + gnupg_fd_t l_fd; + } listentbl[] = { + { "std", start_connection_thread_std }, + { "extra", start_connection_thread_extra }, + { "browser", start_connection_thread_browser }, + { "ssh", start_connection_thread_ssh } + }; + + + ret = npth_attr_init(&tattr); + if (ret) + log_fatal ("error allocating thread attributes: %s\n", + strerror (ret)); + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + +#ifndef HAVE_W32_SYSTEM + npth_sigev_init (); + npth_sigev_add (SIGHUP); + npth_sigev_add (SIGUSR1); + npth_sigev_add (SIGUSR2); + npth_sigev_add (SIGINT); + npth_sigev_add (SIGTERM); + npth_sigev_fini (); +#else +# ifdef HAVE_W32CE_SYSTEM + /* Use a dummy event. */ + sigs = 0; + ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); +# else + events[0] = get_agent_scd_notify_event (); + events[1] = INVALID_HANDLE_VALUE; +# endif +#endif + + if (disable_check_own_socket) + sock_inotify_fd = -1; + else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name))) + { + if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED) + log_info ("error enabling daemon termination by socket removal: %s\n", + gpg_strerror (err)); + } + + if (disable_check_own_socket) + home_inotify_fd = -1; + else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd, + gnupg_homedir ()))) + { + if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED) + log_info ("error enabling daemon termination by homedir removal: %s\n", + gpg_strerror (err)); + } + else + have_homedir_inotify = 1; + + /* On Windows we need to fire up a separate thread to listen for + requests from Putty (an SSH client), so we can replace Putty's + Pageant (its ssh-agent implementation). */ +#ifdef HAVE_W32_SYSTEM + if (putty_support) + { + npth_t thread; + + ret = npth_create (&thread, &tattr, putty_message_thread, NULL); + if (ret) + { + log_error ("error spawning putty message loop: %s\n", strerror (ret)); + } + } +#endif /*HAVE_W32_SYSTEM*/ + + /* Set a flag to tell call-scd.c that it may enable event + notifications. */ + opt.sigusr2_enabled = 1; + + FD_ZERO (&fdset); + FD_SET (FD2INT (listen_fd), &fdset); + nfd = FD2INT (listen_fd); + if (listen_fd_extra != GNUPG_INVALID_FD) + { + FD_SET ( FD2INT(listen_fd_extra), &fdset); + if (FD2INT (listen_fd_extra) > nfd) + nfd = FD2INT (listen_fd_extra); + } + if (listen_fd_browser != GNUPG_INVALID_FD) + { + FD_SET ( FD2INT(listen_fd_browser), &fdset); + if (FD2INT (listen_fd_browser) > nfd) + nfd = FD2INT (listen_fd_browser); + } + if (listen_fd_ssh != GNUPG_INVALID_FD) + { + FD_SET ( FD2INT(listen_fd_ssh), &fdset); + if (FD2INT (listen_fd_ssh) > nfd) + nfd = FD2INT (listen_fd_ssh); + } + if (sock_inotify_fd != -1) + { + FD_SET (sock_inotify_fd, &fdset); + if (sock_inotify_fd > nfd) + nfd = sock_inotify_fd; + } + if (home_inotify_fd != -1) + { + FD_SET (home_inotify_fd, &fdset); + if (home_inotify_fd > nfd) + nfd = home_inotify_fd; + } + + listentbl[0].l_fd = listen_fd; + listentbl[1].l_fd = listen_fd_extra; + listentbl[2].l_fd = listen_fd_browser; + listentbl[3].l_fd = listen_fd_ssh; + + npth_clock_gettime (&abstime); + abstime.tv_sec += TIMERTICK_INTERVAL; + + for (;;) + { + /* Shutdown test. */ + if (shutdown_pending) + { + if (active_connections == 0) + break; /* ready */ + + /* Do not accept new connections but keep on running the + * loop to cope with the timer events. + * + * Note that we do not close the listening socket because a + * client trying to connect to that socket would instead + * restart a new dirmngr instance - which is unlikely the + * intention of a shutdown. */ + FD_ZERO (&fdset); + nfd = -1; + if (sock_inotify_fd != -1) + { + FD_SET (sock_inotify_fd, &fdset); + nfd = sock_inotify_fd; + } + if (home_inotify_fd != -1) + { + FD_SET (home_inotify_fd, &fdset); + if (home_inotify_fd > nfd) + nfd = home_inotify_fd; + } + } + + /* POSIX says that fd_set should be implemented as a structure, + thus a simple assignment is fine to copy the entire set. */ + read_fdset = fdset; + + npth_clock_gettime (&curtime); + if (!(npth_timercmp (&curtime, &abstime, <))) + { + /* Timeout. */ + handle_tick (); + npth_clock_gettime (&abstime); + abstime.tv_sec += TIMERTICK_INTERVAL; + } + npth_timersub (&abstime, &curtime, &timeout); + +#ifndef HAVE_W32_SYSTEM + ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, + npth_sigev_sigmask ()); + saved_errno = errno; + + { + int signo; + while (npth_sigev_get_pending (&signo)) + handle_signal (signo); + } +#else + ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, + events, &events_set); + saved_errno = errno; + + /* This is valid even if npth_eselect returns an error. */ + if (events_set & 1) + agent_sigusr2_action (); +#endif + + if (ret == -1 && saved_errno != EINTR) + { + log_error (_("npth_pselect failed: %s - waiting 1s\n"), + strerror (saved_errno)); + npth_sleep (1); + continue; + } + if (ret <= 0) + /* Interrupt or timeout. Will be handled when calculating the + next timeout. */ + continue; + + /* The inotify fds are set even when a shutdown is pending (see + * above). So we must handle them in any case. To avoid that + * they trigger a second time we close them immediately. */ + if (sock_inotify_fd != -1 + && FD_ISSET (sock_inotify_fd, &read_fdset) + && gnupg_inotify_has_name (sock_inotify_fd, GPG_AGENT_SOCK_NAME)) + { + shutdown_pending = 1; + close (sock_inotify_fd); + sock_inotify_fd = -1; + log_info ("socket file has been removed - shutting down\n"); + } + + if (home_inotify_fd != -1 + && FD_ISSET (home_inotify_fd, &read_fdset)) + { + shutdown_pending = 1; + close (home_inotify_fd); + home_inotify_fd = -1; + log_info ("homedir has been removed - shutting down\n"); + } + + if (!shutdown_pending) + { + int idx; + ctrl_t ctrl; + npth_t thread; + + for (idx=0; idx < DIM(listentbl); idx++) + { + if (listentbl[idx].l_fd == GNUPG_INVALID_FD) + continue; + if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset)) + continue; + + plen = sizeof paddr; + fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd), + (struct sockaddr *)&paddr, &plen)); + if (fd == GNUPG_INVALID_FD) + { + log_error ("accept failed for %s: %s\n", + listentbl[idx].name, strerror (errno)); + } + else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl))) + { + log_error ("error allocating connection data for %s: %s\n", + listentbl[idx].name, strerror (errno) ); + assuan_sock_close (fd); + } + else if ( !(ctrl->session_env = session_env_new ())) + { + log_error ("error allocating session env block for %s: %s\n", + listentbl[idx].name, strerror (errno) ); + xfree (ctrl); + assuan_sock_close (fd); + } + else + { + ctrl->thread_startup.fd = fd; + ret = npth_create (&thread, &tattr, + listentbl[idx].func, ctrl); + if (ret) + { + log_error ("error spawning connection handler for %s:" + " %s\n", listentbl[idx].name, strerror (ret)); + assuan_sock_close (fd); + xfree (ctrl); + } + } + } + } + } + + if (sock_inotify_fd != -1) + close (sock_inotify_fd); + if (home_inotify_fd != -1) + close (home_inotify_fd); + cleanup (); + log_info (_("%s %s stopped\n"), strusage(11), strusage(13)); + npth_attr_destroy (&tattr); +} + + + +/* Helper for check_own_socket. */ +static gpg_error_t +check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length) +{ + membuf_t *mb = opaque; + put_membuf (mb, buffer, length); + return 0; +} + + +/* The thread running the actual check. We need to run this in a + separate thread so that check_own_thread can be called from the + timer tick. */ +static void * +check_own_socket_thread (void *arg) +{ + int rc; + char *sockname = arg; + assuan_context_t ctx = NULL; + membuf_t mb; + char *buffer; + + check_own_socket_running++; + + rc = assuan_new (&ctx); + if (rc) + { + log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); + goto leave; + } + assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1); + + rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0); + if (rc) + { + log_error ("can't connect my own socket: %s\n", gpg_strerror (rc)); + goto leave; + } + + init_membuf (&mb, 100); + rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb, + NULL, NULL, NULL, NULL); + put_membuf (&mb, "", 1); + buffer = get_membuf (&mb, NULL); + if (rc || !buffer) + { + log_error ("sending command \"%s\" to my own socket failed: %s\n", + "GETINFO pid", gpg_strerror (rc)); + rc = 1; + } + else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ()) + { + log_error ("socket is now serviced by another server\n"); + rc = 1; + } + else if (opt.verbose > 1) + log_error ("socket is still served by this server\n"); + + xfree (buffer); + + leave: + xfree (sockname); + if (ctx) + assuan_release (ctx); + if (rc) + { + /* We may not remove the socket as it is now in use by another + server. */ + inhibit_socket_removal = 1; + shutdown_pending = 2; + log_info ("this process is useless - shutting down\n"); + } + check_own_socket_running--; + return NULL; +} + + +/* Check whether we are still listening on our own socket. In case + another gpg-agent process started after us has taken ownership of + our socket, we would linger around without any real task. Thus we + better check once in a while whether we are really needed. */ +static void +check_own_socket (void) +{ + char *sockname; + npth_t thread; + npth_attr_t tattr; + int err; + + if (disable_check_own_socket) + return; + + if (check_own_socket_running || shutdown_pending) + return; /* Still running or already shutting down. */ + + sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); + if (!sockname) + return; /* Out of memory. */ + + err = npth_attr_init (&tattr); + if (err) + return; + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + err = npth_create (&thread, &tattr, check_own_socket_thread, sockname); + if (err) + log_error ("error spawning check_own_socket_thread: %s\n", strerror (err)); + npth_attr_destroy (&tattr); +} + + + +/* Figure out whether an agent is available and running. Prints an + error if not. If SILENT is true, no messages are printed. + Returns 0 if the agent is running. */ +static int +check_for_running_agent (int silent) +{ + gpg_error_t err; + char *sockname; + assuan_context_t ctx = NULL; + + sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); + if (!sockname) + return gpg_error_from_syserror (); + + err = assuan_new (&ctx); + if (!err) + err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0); + xfree (sockname); + if (err) + { + if (!silent) + log_error (_("no gpg-agent running in this session\n")); + + if (ctx) + assuan_release (ctx); + return -1; + } + + if (!opt.quiet && !silent) + log_info ("gpg-agent running and available\n"); + + assuan_release (ctx); + return 0; +} diff --git a/agent/gpg-agent.w32-manifest.in b/agent/gpg-agent.w32-manifest.in new file mode 100644 index 0000000..d865aef --- /dev/null +++ b/agent/gpg-agent.w32-manifest.in @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<description>GNU Privacy Guard (Private key daemon)</description> +<assemblyIdentity + type="win32" + name="GnuPG.gpg-agent" + version="@BUILD_VERSION@" + /> +<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- 10 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><!-- 8.1 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><!-- 8 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><!-- 7 --> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><!-- Vista --> + </application> +</compatibility> +</assembly> diff --git a/agent/learncard.c b/agent/learncard.c new file mode 100644 index 0000000..2e491e1 --- /dev/null +++ b/agent/learncard.c @@ -0,0 +1,452 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "agent.h" +#include <assuan.h> + +/* 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. */ +int +agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force) +{ + 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 (0, grip, serialno, item->id, pubkey, + force, 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/pkdecrypt.c b/agent/pkdecrypt.c new file mode 100644 index 0000000..6aed96b --- /dev/null +++ b/agent/pkdecrypt.c @@ -0,0 +1,147 @@ +/* pkdecrypt.c - public key decryption (well, actually using a secret key) + * Copyright (C) 2001, 2003 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "agent.h" + + +/* DECRYPT the stuff in ciphertext which is expected to be a S-Exp. + Try to get the key from CTRL and write the decoded stuff back to + OUTFP. The padding information is stored at R_PADDING with -1 + for not known. */ +int +agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, + const unsigned char *ciphertext, size_t ciphertextlen, + membuf_t *outbuf, int *r_padding) +{ + gcry_sexp_t s_skey = NULL, s_cipher = NULL, s_plain = NULL; + unsigned char *shadow_info = NULL; + int rc; + char *buf = NULL; + size_t len; + + *r_padding = -1; + + if (!ctrl->have_keygrip) + { + log_error ("speculative decryption not yet supported\n"); + rc = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + + rc = gcry_sexp_sscan (&s_cipher, NULL, (char*)ciphertext, ciphertextlen); + if (rc) + { + log_error ("failed to convert ciphertext: %s\n", gpg_strerror (rc)); + rc = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + if (DBG_CRYPTO) + { + log_printhex (ctrl->keygrip, 20, "keygrip:"); + log_printhex (ciphertext, ciphertextlen, "cipher: "); + } + rc = agent_key_from_file (ctrl, NULL, desc_text, + ctrl->keygrip, &shadow_info, + CACHE_MODE_NORMAL, NULL, &s_skey, NULL); + if (rc) + { + if (gpg_err_code (rc) != GPG_ERR_NO_SECKEY) + log_error ("failed to read the secret key\n"); + goto leave; + } + + if (shadow_info) + { /* divert operation to the smartcard */ + + if (!gcry_sexp_canon_len (ciphertext, ciphertextlen, NULL, NULL)) + { + rc = gpg_error (GPG_ERR_INV_SEXP); + goto leave; + } + + rc = divert_pkdecrypt (ctrl, desc_text, ciphertext, + ctrl->keygrip, shadow_info, + &buf, &len, r_padding); + if (rc) + { + log_error ("smartcard decryption failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + put_membuf_printf (outbuf, "(5:value%u:", (unsigned int)len); + put_membuf (outbuf, buf, len); + put_membuf (outbuf, ")", 2); + } + else + { /* No smartcard, but a private key */ +/* if (DBG_CRYPTO ) */ +/* { */ +/* log_debug ("skey: "); */ +/* gcry_sexp_dump (s_skey); */ +/* } */ + + rc = gcry_pk_decrypt (&s_plain, s_cipher, s_skey); + if (rc) + { + log_error ("decryption failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (DBG_CRYPTO) + { + log_debug ("plain: "); + gcry_sexp_dump (s_plain); + } + len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, NULL, 0); + assert (len); + buf = xmalloc (len); + len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, buf, len); + assert (len); + if (*buf == '(') + put_membuf (outbuf, buf, len); + else + { + /* Old style libgcrypt: This is only an S-expression + part. Turn it into a complete S-expression. */ + put_membuf (outbuf, "(5:value", 8); + put_membuf (outbuf, buf, len); + put_membuf (outbuf, ")", 2); + } + } + + + leave: + gcry_sexp_release (s_skey); + gcry_sexp_release (s_plain); + gcry_sexp_release (s_cipher); + xfree (buf); + xfree (shadow_info); + return rc; +} diff --git a/agent/pksign.c b/agent/pksign.c new file mode 100644 index 0000000..09d61b8 --- /dev/null +++ b/agent/pksign.c @@ -0,0 +1,572 @@ +/* pksign.c - public key signing (well, actually using a secret key) + * Copyright (C) 2001-2004, 2010 Free Software Foundation, Inc. + * Copyright (C) 2001-2004, 2010, 2013 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "agent.h" +#include "../common/i18n.h" + + +static int +do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash, + int raw_value) +{ + gcry_sexp_t hash; + int rc; + + if (!raw_value) + { + const char *s; + char tmp[16+1]; + int i; + + s = gcry_md_algo_name (algo); + if (s && strlen (s) < 16) + { + for (i=0; i < strlen (s); i++) + tmp[i] = tolower (s[i]); + tmp[i] = '\0'; + } + + rc = gcry_sexp_build (&hash, NULL, + "(data (flags pkcs1) (hash %s %b))", + tmp, (int)mdlen, md); + } + else + { + gcry_mpi_t mpi; + + rc = gcry_mpi_scan (&mpi, GCRYMPI_FMT_USG, md, mdlen, NULL); + if (!rc) + { + rc = gcry_sexp_build (&hash, NULL, + "(data (flags raw) (value %m))", + mpi); + gcry_mpi_release (mpi); + } + else + hash = NULL; + + } + + *r_hash = hash; + return rc; +} + + +/* Return the number of bits of the Q parameter from the DSA key + KEY. */ +static unsigned int +get_dsa_qbits (gcry_sexp_t key) +{ + gcry_sexp_t l1, l2; + gcry_mpi_t q; + unsigned int nbits; + + l1 = gcry_sexp_find_token (key, "private-key", 0); + if (!l1) + l1 = gcry_sexp_find_token (key, "protected-private-key", 0); + if (!l1) + l1 = gcry_sexp_find_token (key, "shadowed-private-key", 0); + if (!l1) + l1 = gcry_sexp_find_token (key, "public-key", 0); + if (!l1) + return 0; /* Does not contain a key object. */ + l2 = gcry_sexp_cadr (l1); + gcry_sexp_release (l1); + l1 = gcry_sexp_find_token (l2, "q", 1); + gcry_sexp_release (l2); + if (!l1) + return 0; /* Invalid object. */ + q = gcry_sexp_nth_mpi (l1, 1, GCRYMPI_FMT_USG); + gcry_sexp_release (l1); + if (!q) + return 0; /* Missing value. */ + nbits = gcry_mpi_get_nbits (q); + gcry_mpi_release (q); + + return nbits; +} + + +/* Return an appropriate hash algorithm to be used with RFC-6979 for a + message digest of length MDLEN. Although a fallback of SHA-256 is + used the current implementation in Libgcrypt will reject a hash + algorithm which does not match the length of the message. */ +static const char * +rfc6979_hash_algo_string (size_t mdlen) +{ + switch (mdlen) + { + case 20: return "sha1"; + case 28: return "sha224"; + case 32: return "sha256"; + case 48: return "sha384"; + case 64: return "sha512"; + default: return "sha256"; + } +} + + +/* Encode a message digest for use with the EdDSA algorithm + (i.e. curve Ed25519). */ +static gpg_error_t +do_encode_eddsa (const byte *md, size_t mdlen, gcry_sexp_t *r_hash) +{ + gpg_error_t err; + gcry_sexp_t hash; + + *r_hash = NULL; + err = gcry_sexp_build (&hash, NULL, + "(data(flags eddsa)(hash-algo sha512)(value %b))", + (int)mdlen, md); + if (!err) + *r_hash = hash; + return err; +} + + +/* Encode a message digest for use with an DSA algorithm. */ +static gpg_error_t +do_encode_dsa (const byte *md, size_t mdlen, int pkalgo, gcry_sexp_t pkey, + gcry_sexp_t *r_hash) +{ + gpg_error_t err; + gcry_sexp_t hash; + unsigned int qbits; + + *r_hash = NULL; + + if (pkalgo == GCRY_PK_ECDSA) + qbits = gcry_pk_get_nbits (pkey); + else if (pkalgo == GCRY_PK_DSA) + qbits = get_dsa_qbits (pkey); + else + return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + + if (pkalgo == GCRY_PK_DSA && (qbits%8)) + { + /* FIXME: We check the QBITS but print a message about the hash + length. */ + log_error (_("DSA requires the hash length to be a" + " multiple of 8 bits\n")); + return gpg_error (GPG_ERR_INV_LENGTH); + } + + /* Don't allow any Q smaller than 160 bits. We don't want someone + to issue signatures from a key with a 16-bit Q or something like + that, which would look correct but allow trivial forgeries. Yes, + I know this rules out using MD5 with DSA. ;) */ + if (qbits < 160) + { + log_error (_("%s key uses an unsafe (%u bit) hash\n"), + gcry_pk_algo_name (pkalgo), qbits); + return gpg_error (GPG_ERR_INV_LENGTH); + } + + /* ECDSA 521 is special has it is larger than the largest hash + we have (SHA-512). Thus we change the size for further + processing to 512. */ + if (pkalgo == GCRY_PK_ECDSA && qbits > 512) + qbits = 512; + + /* Check if we're too short. Too long is safe as we'll + automatically left-truncate. */ + if (mdlen < qbits/8) + { + log_error (_("a %zu bit hash is not valid for a %u bit %s key\n"), + mdlen*8, + gcry_pk_get_nbits (pkey), + gcry_pk_algo_name (pkalgo)); + return gpg_error (GPG_ERR_INV_LENGTH); + } + + /* Truncate. */ + if (mdlen > qbits/8) + mdlen = qbits/8; + + /* Create the S-expression. */ + err = gcry_sexp_build (&hash, NULL, + "(data (flags rfc6979) (hash %s %b))", + rfc6979_hash_algo_string (mdlen), + (int)mdlen, md); + if (!err) + *r_hash = hash; + return err; +} + + +/* Special version of do_encode_md to take care of pkcs#1 padding. + For TLS-MD5SHA1 we need to do the padding ourself as Libgrypt does + not know about this special scheme. Fixme: We should have a + pkcs1-only-padding flag for Libgcrypt. */ +static int +do_encode_raw_pkcs1 (const byte *md, size_t mdlen, unsigned int nbits, + gcry_sexp_t *r_hash) +{ + int rc; + gcry_sexp_t hash; + unsigned char *frame; + size_t i, n, nframe; + + nframe = (nbits+7) / 8; + if ( !mdlen || mdlen + 8 + 4 > nframe ) + { + /* Can't encode this hash into a frame of size NFRAME. */ + return gpg_error (GPG_ERR_TOO_SHORT); + } + + frame = xtrymalloc (nframe); + if (!frame) + return gpg_error_from_syserror (); + + /* Assemble the pkcs#1 block type 1. */ + n = 0; + frame[n++] = 0; + frame[n++] = 1; /* Block type. */ + i = nframe - mdlen - 3 ; + assert (i >= 8); /* At least 8 bytes of padding. */ + memset (frame+n, 0xff, i ); + n += i; + frame[n++] = 0; + memcpy (frame+n, md, mdlen ); + n += mdlen; + assert (n == nframe); + + /* Create the S-expression. */ + rc = gcry_sexp_build (&hash, NULL, + "(data (flags raw) (value %b))", + (int)nframe, frame); + xfree (frame); + + *r_hash = hash; + return rc; +} + + + +/* SIGN whatever information we have accumulated in CTRL and return + * the signature S-expression. LOOKUP is an optional function to + * provide a way for lower layers to ask for the caching TTL. If a + * CACHE_NONCE is given that cache item is first tried to get a + * passphrase. If OVERRIDEDATA is not NULL, OVERRIDEDATALEN bytes + * from this buffer are used instead of the data in CTRL. The + * override feature is required to allow the use of Ed25519 with ssh + * because Ed25519 does the hashing itself. */ +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 err = 0; + gcry_sexp_t s_skey = NULL; + gcry_sexp_t s_sig = NULL; + gcry_sexp_t s_hash = NULL; + gcry_sexp_t s_pkey = NULL; + unsigned char *shadow_info = NULL; + const unsigned char *data; + int datalen; + int check_signature = 0; + + if (overridedata) + { + data = overridedata; + datalen = overridedatalen; + } + else + { + data = ctrl->digest.value; + datalen = ctrl->digest.valuelen; + } + + if (!ctrl->have_keygrip) + return gpg_error (GPG_ERR_NO_SECKEY); + + err = agent_key_from_file (ctrl, cache_nonce, desc_text, ctrl->keygrip, + &shadow_info, cache_mode, lookup_ttl, + &s_skey, NULL); + if (err) + { + if (gpg_err_code (err) != GPG_ERR_NO_SECKEY) + log_error ("failed to read the secret key\n"); + goto leave; + } + + if (shadow_info) + { + /* Divert operation to the smartcard */ + size_t len; + unsigned char *buf = NULL; + int key_type; + int is_RSA = 0; + int is_ECDSA = 0; + int is_EdDSA = 0; + + err = agent_public_key_from_file (ctrl, ctrl->keygrip, &s_pkey); + if (err) + { + log_error ("failed to read the public key\n"); + goto leave; + } + + if (agent_is_eddsa_key (s_skey)) + is_EdDSA = 1; + else + { + key_type = agent_is_dsa_key (s_skey); + if (key_type == 0) + is_RSA = 1; + else if (key_type == GCRY_PK_ECDSA) + is_ECDSA = 1; + } + + { + char *desc2 = NULL; + + if (desc_text) + agent_modify_description (desc_text, NULL, s_skey, &desc2); + + err = divert_pksign (ctrl, desc2? desc2 : desc_text, + data, datalen, + ctrl->digest.algo, + ctrl->keygrip, + shadow_info, &buf, &len); + xfree (desc2); + } + if (err) + { + log_error ("smartcard signing failed: %s\n", gpg_strerror (err)); + goto leave; + } + + if (is_RSA) + { + check_signature = 1; + if (*buf & 0x80) + { + len++; + buf = xtryrealloc (buf, len); + if (!buf) + goto leave; + + memmove (buf + 1, buf, len - 1); + *buf = 0; + } + + err = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%b)))", + (int)len, buf); + } + else if (is_EdDSA) + { + err = gcry_sexp_build (&s_sig, NULL, "(sig-val(eddsa(r%b)(s%b)))", + (int)len/2, buf, (int)len/2, buf + len/2); + } + else if (is_ECDSA) + { + unsigned char *r_buf_allocated = NULL; + unsigned char *s_buf_allocated = NULL; + unsigned char *r_buf, *s_buf; + int r_buflen, s_buflen; + + r_buflen = s_buflen = len/2; + + if (*buf & 0x80) + { + r_buflen++; + r_buf_allocated = xtrymalloc (r_buflen); + if (!r_buf_allocated) + { + err = gpg_error_from_syserror (); + goto leave; + } + + r_buf = r_buf_allocated; + memcpy (r_buf + 1, buf, len/2); + *r_buf = 0; + } + else + r_buf = buf; + + if (*(buf + len/2) & 0x80) + { + s_buflen++; + s_buf_allocated = xtrymalloc (s_buflen); + if (!s_buf_allocated) + { + err = gpg_error_from_syserror (); + xfree (r_buf_allocated); + goto leave; + } + + s_buf = s_buf_allocated; + memcpy (s_buf + 1, buf + len/2, len/2); + *s_buf = 0; + } + else + s_buf = buf + len/2; + + err = gcry_sexp_build (&s_sig, NULL, "(sig-val(ecdsa(r%b)(s%b)))", + r_buflen, r_buf, + s_buflen, s_buf); + xfree (r_buf_allocated); + xfree (s_buf_allocated); + } + else + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + xfree (buf); + if (err) + { + log_error ("failed to convert sigbuf returned by divert_pksign " + "into S-Exp: %s", gpg_strerror (err)); + goto leave; + } + } + else + { + /* No smartcard, but a private key */ + int dsaalgo = 0; + + /* Put the hash into a sexp */ + if (agent_is_eddsa_key (s_skey)) + err = do_encode_eddsa (data, datalen, + &s_hash); + else if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1) + err = do_encode_raw_pkcs1 (data, datalen, + gcry_pk_get_nbits (s_skey), + &s_hash); + else if ( (dsaalgo = agent_is_dsa_key (s_skey)) ) + err = do_encode_dsa (data, datalen, + dsaalgo, s_skey, + &s_hash); + else + err = do_encode_md (data, datalen, + ctrl->digest.algo, + &s_hash, + ctrl->digest.raw_value); + if (err) + goto leave; + + if (DBG_CRYPTO) + { + gcry_log_debugsxp ("skey", s_skey); + gcry_log_debugsxp ("hash", s_hash); + } + + /* sign */ + err = gcry_pk_sign (&s_sig, s_hash, s_skey); + if (err) + { + log_error ("signing failed: %s\n", gpg_strerror (err)); + goto leave; + } + + if (DBG_CRYPTO) + gcry_log_debugsxp ("rslt", s_sig); + } + + /* Check that the signature verification worked and nothing is + * fooling us e.g. by a bug in the signature create code or by + * deliberately introduced faults. Because Libgcrypt 1.7 does this + * for RSA internally there is no need to do it here again. */ + if (check_signature) + { + gcry_sexp_t sexp_key = s_pkey? s_pkey: s_skey; + + if (s_hash == NULL) + { + if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1) + err = do_encode_raw_pkcs1 (data, datalen, + gcry_pk_get_nbits (sexp_key), &s_hash); + else + err = do_encode_md (data, datalen, ctrl->digest.algo, &s_hash, + ctrl->digest.raw_value); + } + + if (!err) + err = gcry_pk_verify (s_sig, s_hash, sexp_key); + + if (err) + { + log_error (_("checking created signature failed: %s\n"), + gpg_strerror (err)); + gcry_sexp_release (s_sig); + s_sig = NULL; + } + } + + leave: + + *signature_sexp = s_sig; + + gcry_sexp_release (s_pkey); + gcry_sexp_release (s_skey); + gcry_sexp_release (s_hash); + xfree (shadow_info); + + return err; +} + + +/* SIGN whatever information we have accumulated in CTRL and write it + * back to OUTFP. If a CACHE_NONCE is given that cache item is first + * tried to get a passphrase. */ +gpg_error_t +agent_pksign (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, + membuf_t *outbuf, cache_mode_t cache_mode) +{ + gpg_error_t err; + gcry_sexp_t s_sig = NULL; + char *buf = NULL; + size_t len = 0; + + err = agent_pksign_do (ctrl, cache_nonce, desc_text, &s_sig, cache_mode, + NULL, NULL, 0); + if (err) + goto leave; + + len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, NULL, 0); + log_assert (len); + buf = xtrymalloc (len); + if (!buf) + { + err = gpg_error_from_syserror (); + goto leave; + } + len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len); + log_assert (len); + put_membuf (outbuf, buf, len); + + leave: + gcry_sexp_release (s_sig); + xfree (buf); + + return err; +} diff --git a/agent/preset-passphrase.c b/agent/preset-passphrase.c new file mode 100644 index 0000000..c5aeafe --- /dev/null +++ b/agent/preset-passphrase.c @@ -0,0 +1,272 @@ +/* preset-passphrase.c - A tool to preset a passphrase. + * Copyright (C) 2004 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 <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/stat.h> +#include <unistd.h> +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif +#ifdef HAVE_LANGINFO_CODESET +#include <langinfo.h> +#endif +#ifdef HAVE_DOSISH_SYSTEM +#include <fcntl.h> /* for setmode() */ +#endif +#ifdef HAVE_W32_SYSTEM +# ifdef HAVE_WINSOCK2_H +# include <winsock2.h> +# endif +# include <windows.h> /* To initialize the sockets. fixme */ +#endif + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "agent.h" +#include "../common/simple-pwquery.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/init.h" + + +enum cmd_and_opt_values +{ aNull = 0, + oVerbose = 'v', + oPassphrase = 'P', + + oPreset = 'c', + oForget = 'f', + + oNoVerbose = 500, + + oHomedir, + +aTest }; + + +static const char *opt_passphrase; + +static ARGPARSE_OPTS opts[] = { + + { 301, NULL, 0, N_("@Options:\n ") }, + + { oVerbose, "verbose", 0, "verbose" }, + { oPassphrase, "passphrase", 2, "|STRING|use passphrase STRING" }, + { oPreset, "preset", 256, "preset passphrase"}, + { oForget, "forget", 256, "forget passphrase"}, + + { oHomedir, "homedir", 2, "@" }, + + 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-preset-passphrase (@GNUPG@)"; + 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-preset-passphrase [options] KEYGRIP (-h for help)\n"); + break; + case 41: + p = _("Syntax: gpg-preset-passphrase [options] KEYGRIP\n" + "Password cache maintenance\n"); + break; + + default: p = NULL; + } + return p; +} + + + + +static void +preset_passphrase (const char *keygrip) +{ + int rc; + char *line; + /* FIXME: Use secure memory. */ + char passphrase[500]; + char *passphrase_esc; + + if (!opt_passphrase) + { + rc = read (0, passphrase, sizeof (passphrase) - 1); + if (rc < 0) + { + log_error ("reading passphrase failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + return; + } + passphrase[rc] = '\0'; + line = strchr (passphrase, '\n'); + if (line) + { + if (line > passphrase && line[-1] == '\r') + line--; + *line = '\0'; + } + + /* FIXME: How to handle empty passwords? */ + } + + { + const char *s = opt_passphrase ? opt_passphrase : passphrase; + passphrase_esc = bin2hex (s, strlen (s), NULL); + } + if (!passphrase_esc) + { + log_error ("can not escape string: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + return; + } + + rc = asprintf (&line, "PRESET_PASSPHRASE %s -1 %s\n", keygrip, + passphrase_esc); + wipememory (passphrase_esc, strlen (passphrase_esc)); + xfree (passphrase_esc); + + if (rc < 0) + { + log_error ("caching passphrase failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + return; + } + if (!opt_passphrase) + wipememory (passphrase, sizeof (passphrase)); + + rc = simple_query (line); + if (rc) + { + log_error ("caching passphrase failed: %s\n", gpg_strerror (rc)); + return; + } + + wipememory (line, strlen (line)); + xfree (line); +} + + +static void +forget_passphrase (const char *keygrip) +{ + int rc; + char *line; + + rc = asprintf (&line, "CLEAR_PASSPHRASE %s\n", keygrip); + if (rc < 0) + rc = gpg_error_from_syserror (); + else + rc = simple_query (line); + if (rc) + { + log_error ("clearing passphrase failed: %s\n", gpg_strerror (rc)); + return; + } + + xfree (line); +} + + +int +main (int argc, char **argv) +{ + ARGPARSE_ARGS pargs; + int cmd = 0; + const char *keygrip = NULL; + + early_system_init (); + set_strusage (my_strusage); + log_set_prefix ("gpg-preset-passphrase", GPGRT_LOG_WITH_PREFIX); + + /* Make sure that our subsystems are ready. */ + i18n_init (); + init_common_subsystems (&argc, &argv); + + 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 oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; + + case oPreset: cmd = oPreset; break; + case oForget: cmd = oForget; break; + case oPassphrase: opt_passphrase = pargs.r.ret_str; break; + + default : pargs.err = 2; break; + } + } + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + if (log_get_errorcount(0)) + exit(2); + + if (argc == 1) + keygrip = *argv; + else + usage (1); + + /* Tell simple-pwquery about the standard socket name. */ + { + char *tmp = make_filename (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); + simple_pw_set_socket (tmp); + xfree (tmp); + } + + if (cmd == oPreset) + preset_passphrase (keygrip); + else if (cmd == oForget) + forget_passphrase (keygrip); + else + log_error ("one of the options --preset or --forget must be given\n"); + + agent_exit (0); + return 8; /*NOTREACHED*/ +} + + +void +agent_exit (int rc) +{ + rc = rc? rc : log_get_errorcount(0)? 2 : 0; + exit (rc); +} diff --git a/agent/protect-tool.c b/agent/protect-tool.c new file mode 100644 index 0000000..fb2c71d --- /dev/null +++ b/agent/protect-tool.c @@ -0,0 +1,837 @@ +/* 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 <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/stat.h> +#include <unistd.h> +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif +#ifdef HAVE_LANGINFO_CODESET +#include <langinfo.h> +#endif +#ifdef HAVE_DOSISH_SYSTEM +#include <fcntl.h> /* for setmode() */ +#endif + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "agent.h" +#include "../common/i18n.h" +#include "../common/get-passphrase.h" +#include "../common/sysutils.h" +#include "../common/init.h" + + +enum cmd_and_opt_values +{ + aNull = 0, + oVerbose = 'v', + oArmor = 'a', + oPassphrase = 'P', + + oProtect = 'p', + oUnprotect = 'u', + + oNoVerbose = 500, + oShadow, + oShowShadowInfo, + oShowKeygrip, + oS2Kcalibration, + oCanonical, + + oStore, + oForce, + oHaveCert, + oNoFailOnExist, + oHomedir, + oPrompt, + oStatusMsg, + oDebugUseOCB, + + oAgentProgram +}; + + +struct rsa_secret_key_s +{ + gcry_mpi_t n; /* public modulus */ + gcry_mpi_t e; /* public exponent */ + gcry_mpi_t d; /* exponent */ + gcry_mpi_t p; /* prime p. */ + gcry_mpi_t q; /* prime q. */ + gcry_mpi_t u; /* inverse of p mod q. */ +}; + + +static int opt_armor; +static int opt_canonical; +static int opt_store; +static int opt_force; +static int opt_no_fail_on_exist; +static int opt_have_cert; +static const char *opt_passphrase; +static char *opt_prompt; +static int opt_status_msg; +static const char *opt_agent_program; +static int opt_debug_use_ocb; + +static char *get_passphrase (int promptno); +static void release_passphrase (char *pw); + + +static 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, + opt_debug_use_ocb? 1 : -1); + release_passphrase (pw); + xfree (key); + if (rc) + { + log_error ("protecting the key failed: %s\n", gpg_strerror (rc)); + return; + } + + if (opt_armor) + { + char *p = make_advanced (result, resultlen); + xfree (result); + if (!p) + return; + result = (unsigned char*)p; + resultlen = strlen (p); + } + + fwrite (result, resultlen, 1, stdout); + xfree (result); +} + + +static void +read_and_unprotect (ctrl_t ctrl, const char *fname) +{ + int rc; + 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: opt_debug_use_ocb = 1; 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, + time_t timestamp, + const char *serialno, const char *keyref, + const char *dispserialno) +{ + char hexgrip[40+4+1]; + char *p; + + (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/agent/protect.c b/agent/protect.c new file mode 100644 index 0000000..87df685 --- /dev/null +++ b/agent/protect.c @@ -0,0 +1,1761 @@ +/* protect.c - Un/Protect a secret key + * Copyright (C) 1998-2003, 2007, 2009, 2011 Free Software Foundation, Inc. + * Copyright (C) 1998-2003, 2007, 2009, 2011, 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <ctype.h> +#include <assert.h> +#include <unistd.h> +#include <sys/stat.h> +#ifdef HAVE_W32_SYSTEM +# ifdef HAVE_WINSOCK2_H +# include <winsock2.h> +# endif +# include <windows.h> +#else +# include <sys/times.h> +#endif + +#include "agent.h" + +#include "cvt-openpgp.h" +#include "../common/sexp-parse.h" + + +/* The protection mode for encryption. The supported modes for + decryption are listed in agent_unprotect(). */ +#define PROT_CIPHER GCRY_CIPHER_AES128 +#define PROT_CIPHER_STRING "aes" +#define PROT_CIPHER_KEYLEN (128/8) + +/* Decode an rfc4880 encoded S2K count. */ +#define S2K_DECODE_COUNT(_val) ((16ul + ((_val) & 15)) << (((_val) >> 4) + 6)) + + +/* A table containing the information needed to create a protected + private key. */ +static const struct { + const char *algo; + const char *parmlist; + int prot_from, prot_to; + int ecc_hack; +} protect_info[] = { + { "rsa", "nedpqu", 2, 5 }, + { "dsa", "pqgyx", 4, 4 }, + { "elg", "pgyx", 3, 3 }, + { "ecdsa","pabgnqd", 6, 6, 1 }, + { "ecdh", "pabgnqd", 6, 6, 1 }, + { "ecc", "pabgnqd", 6, 6, 1 }, + { NULL } +}; + + +/* The number of milliseconds we use in the S2K function and the + * calibrated count value. A count value of zero indicates that the + * calibration has not yet been done or needs to be done again. */ +static unsigned int s2k_calibration_time = AGENT_S2K_CALIBRATION; +static unsigned long s2k_calibrated_count; + + +/* A helper object for time measurement. */ +struct calibrate_time_s +{ +#ifdef HAVE_W32_SYSTEM + FILETIME creation_time, exit_time, kernel_time, user_time; +#else + clock_t ticks; +#endif +}; + + +static int +hash_passphrase (const char *passphrase, int hashalgo, + int s2kmode, + const unsigned char *s2ksalt, unsigned long s2kcount, + unsigned char *key, size_t keylen); + + + + +/* Get the process time and store it in DATA. */ +static void +calibrate_get_time (struct calibrate_time_s *data) +{ +#ifdef HAVE_W32_SYSTEM +# ifdef HAVE_W32CE_SYSTEM + GetThreadTimes (GetCurrentThread (), + &data->creation_time, &data->exit_time, + &data->kernel_time, &data->user_time); +# else + GetProcessTimes (GetCurrentProcess (), + &data->creation_time, &data->exit_time, + &data->kernel_time, &data->user_time); +# endif +#elif defined (CLOCK_THREAD_CPUTIME_ID) + struct timespec tmp; + + clock_gettime (CLOCK_THREAD_CPUTIME_ID, &tmp); + data->ticks = (clock_t)(((unsigned long long)tmp.tv_sec * 1000000000 + + tmp.tv_nsec) * CLOCKS_PER_SEC / 1000000000); +#else + data->ticks = clock (); +#endif +} + + +static unsigned long +calibrate_elapsed_time (struct calibrate_time_s *starttime) +{ + struct calibrate_time_s stoptime; + + calibrate_get_time (&stoptime); +#ifdef HAVE_W32_SYSTEM + { + unsigned long long t1, t2; + + t1 = (((unsigned long long)starttime->kernel_time.dwHighDateTime << 32) + + starttime->kernel_time.dwLowDateTime); + t1 += (((unsigned long long)starttime->user_time.dwHighDateTime << 32) + + starttime->user_time.dwLowDateTime); + t2 = (((unsigned long long)stoptime.kernel_time.dwHighDateTime << 32) + + stoptime.kernel_time.dwLowDateTime); + t2 += (((unsigned long long)stoptime.user_time.dwHighDateTime << 32) + + stoptime.user_time.dwLowDateTime); + return (unsigned long)((t2 - t1)/10000); + } +#else + return (unsigned long)((((double) (stoptime.ticks - starttime->ticks)) + /CLOCKS_PER_SEC)*1000); +#endif +} + + +/* Run a test hashing for COUNT and return the time required in + milliseconds. */ +static unsigned long +calibrate_s2k_count_one (unsigned long count) +{ + int rc; + char keybuf[PROT_CIPHER_KEYLEN]; + struct calibrate_time_s starttime; + + calibrate_get_time (&starttime); + rc = hash_passphrase ("123456789abcdef0", GCRY_MD_SHA1, + 3, "saltsalt", count, keybuf, sizeof keybuf); + if (rc) + BUG (); + return calibrate_elapsed_time (&starttime); +} + + +/* Measure the time we need to do the hash operations and deduce an + S2K count which requires roughly some targeted amount of time. */ +static unsigned long +calibrate_s2k_count (void) +{ + unsigned long count; + unsigned long ms; + + for (count = 65536; count; count *= 2) + { + ms = calibrate_s2k_count_one (count); + if (opt.verbose > 1) + log_info ("S2K calibration: %lu -> %lums\n", count, ms); + if (ms > s2k_calibration_time) + break; + } + + count = (unsigned long)(((double)count / ms) * s2k_calibration_time); + count /= 1024; + count *= 1024; + if (count < 65536) + count = 65536; + + if (opt.verbose) + { + ms = calibrate_s2k_count_one (count); + log_info ("S2K calibration: %lu -> %lums\n", count, ms); + } + + return count; +} + + +/* Set the calibration time. This may be called early at startup or + * at any time. Thus it should one set variables. */ +void +set_s2k_calibration_time (unsigned int milliseconds) +{ + if (!milliseconds) + milliseconds = AGENT_S2K_CALIBRATION; + else if (milliseconds > 60 * 1000) + milliseconds = 60 * 1000; /* Cap at 60 seconds. */ + s2k_calibration_time = milliseconds; + s2k_calibrated_count = 0; /* Force re-calibration. */ +} + + +/* Return the calibrated S2K count. This is only public for the use + * of the Assuan getinfo s2k_count_cal command. */ +unsigned long +get_calibrated_s2k_count (void) +{ + if (!s2k_calibrated_count) + s2k_calibrated_count = calibrate_s2k_count (); + + /* Enforce a lower limit. */ + return s2k_calibrated_count < 65536 ? 65536 : s2k_calibrated_count; +} + + +/* Return the standard S2K count. */ +unsigned long +get_standard_s2k_count (void) +{ + if (opt.s2k_count) + return opt.s2k_count < 65536 ? 65536 : opt.s2k_count; + + return get_calibrated_s2k_count (); +} + + +/* Return the milliseconds required for the standard S2K + * operation. */ +unsigned long +get_standard_s2k_time (void) +{ + return calibrate_s2k_count_one (get_standard_s2k_count ()); +} + + +/* Same as get_standard_s2k_count but return the count in the encoding + as described by rfc4880. */ +unsigned char +get_standard_s2k_count_rfc4880 (void) +{ + unsigned long iterations; + unsigned int count; + unsigned char result; + unsigned char c=0; + + iterations = get_standard_s2k_count (); + if (iterations >= 65011712) + return 255; + + /* Need count to be in the range 16-31 */ + for (count=iterations>>6; count>=32; count>>=1) + c++; + + result = (c<<4)|(count-16); + + if (S2K_DECODE_COUNT(result) < iterations) + result++; + + return result; + +} + + + +/* Calculate the MIC for a private key or shared secret S-expression. + SHA1HASH should point to a 20 byte buffer. This function is + suitable for all algorithms. */ +static gpg_error_t +calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash) +{ + const unsigned char *hash_begin, *hash_end; + const unsigned char *s; + size_t n; + int is_shared_secret; + + s = plainkey; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "private-key")) + is_shared_secret = 0; + else if (smatch (&s, n, "shared-secret")) + is_shared_secret = 1; + else + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + hash_begin = s; + if (!is_shared_secret) + { + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; /* Skip the algorithm name. */ + } + + while (*s == '(') + { + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; + if ( *s != ')' ) + return gpg_error (GPG_ERR_INV_SEXP); + s++; + } + if (*s != ')') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + hash_end = s; + + gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash, + hash_begin, hash_end - hash_begin); + + return 0; +} + + + +/* Encrypt the parameter block starting at PROTBEGIN with length + PROTLEN using the utf8 encoded key PASSPHRASE and return the entire + encrypted block in RESULT or return with an error code. SHA1HASH + is the 20 byte SHA-1 hash required for the integrity code. + + The parameter block is expected to be an incomplete canonical + encoded S-Expression of the form (example in advanced format): + + (d #046129F..[some bytes not shown]..81#) + (p #00e861b..[some bytes not shown]..f1#) + (q #00f7a7c..[some bytes not shown]..61#) + (u #304559a..[some bytes not shown]..9b#) + + the returned block is the S-Expression: + + (protected mode (parms) encrypted_octet_string) + +*/ +static int +do_encryption (const unsigned char *hashbegin, size_t hashlen, + const unsigned char *protbegin, size_t protlen, + const char *passphrase, + const char *timestamp_exp, size_t timestamp_exp_len, + unsigned char **result, size_t *resultlen, + unsigned long s2k_count, int use_ocb) +{ + gcry_cipher_hd_t hd; + const char *modestr; + unsigned char hashvalue[20]; + int blklen, enclen, outlen; + unsigned char *iv = NULL; + unsigned int ivsize; /* Size of the buffer allocated for IV. */ + const unsigned char *s2ksalt; /* Points into IV. */ + int rc; + char *outbuf = NULL; + char *p; + int saltpos, ivpos, encpos; + + s2ksalt = iv; /* Silence compiler warning. */ + + *resultlen = 0; + *result = NULL; + + modestr = (use_ocb? "openpgp-s2k3-ocb-aes" + /* */: "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc"); + + rc = gcry_cipher_open (&hd, PROT_CIPHER, + use_ocb? GCRY_CIPHER_MODE_OCB : + GCRY_CIPHER_MODE_CBC, + GCRY_CIPHER_SECURE); + if (rc) + return rc; + + /* We need to work on a copy of the data because this makes it + * easier to add the trailer and the padding and more important we + * have to prefix the text with 2 parenthesis. In CBC mode we + * have to allocate enough space for: + * + * ((<parameter_list>)(4:hash4:sha120:<hashvalue>)) + padding + * + * we always append a full block of random bytes as padding but + * encrypt only what is needed for a full blocksize. In OCB mode we + * have to allocate enough space for just: + * + * ((<parameter_list>)) + */ + blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER); + if (use_ocb) + { + /* (( )) */ + outlen = 2 + protlen + 2 ; + enclen = outlen + 16 /* taglen */; + outbuf = gcry_malloc_secure (enclen); + } + else + { + /* (( )( 4:hash 4:sha1 20:<hash> )) <padding> */ + outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen; + enclen = outlen/blklen * blklen; + outbuf = gcry_malloc_secure (outlen); + } + if (!outbuf) + { + rc = out_of_core (); + goto leave; + } + + /* Allocate a buffer for the nonce and the salt. */ + if (!rc) + { + /* Allocate random bytes to be used as IV, padding and s2k salt + * or in OCB mode for a nonce and the s2k salt. The IV/nonce is + * set later because for OCB we need to set the key first. */ + ivsize = (use_ocb? 12 : (blklen*2)) + 8; + iv = xtrymalloc (ivsize); + if (!iv) + rc = gpg_error_from_syserror (); + else + { + gcry_create_nonce (iv, ivsize); + s2ksalt = iv + ivsize - 8; + } + } + + /* Hash the passphrase and set the key. */ + if (!rc) + { + unsigned char *key; + size_t keylen = PROT_CIPHER_KEYLEN; + + key = gcry_malloc_secure (keylen); + if (!key) + rc = out_of_core (); + else + { + rc = hash_passphrase (passphrase, GCRY_MD_SHA1, + 3, s2ksalt, + s2k_count? s2k_count:get_standard_s2k_count(), + key, keylen); + if (!rc) + rc = gcry_cipher_setkey (hd, key, keylen); + xfree (key); + } + } + + if (rc) + goto leave; + + /* Set the IV/nonce. */ + rc = gcry_cipher_setiv (hd, iv, use_ocb? 12 : blklen); + if (rc) + goto leave; + + if (use_ocb) + { + /* In OCB Mode we use only the public key parameters as AAD. */ + rc = gcry_cipher_authenticate (hd, hashbegin, protbegin - hashbegin); + if (!rc) + rc = gcry_cipher_authenticate (hd, timestamp_exp, timestamp_exp_len); + if (!rc) + rc = gcry_cipher_authenticate + (hd, protbegin+protlen, hashlen - (protbegin+protlen - hashbegin)); + } + else + { + /* Hash the entire expression for CBC mode. Because + * TIMESTAMP_EXP won't get protected, we can't simply hash a + * continuous buffer but need to call md_write several times. */ + gcry_md_hd_t md; + + rc = gcry_md_open (&md, GCRY_MD_SHA1, 0 ); + if (!rc) + { + gcry_md_write (md, hashbegin, protbegin - hashbegin); + gcry_md_write (md, protbegin, protlen); + gcry_md_write (md, timestamp_exp, timestamp_exp_len); + gcry_md_write (md, protbegin+protlen, + hashlen - (protbegin+protlen - hashbegin)); + memcpy (hashvalue, gcry_md_read (md, GCRY_MD_SHA1), 20); + gcry_md_close (md); + } + } + + + /* Encrypt. */ + if (!rc) + { + p = outbuf; + *p++ = '('; + *p++ = '('; + memcpy (p, protbegin, protlen); + p += protlen; + if (use_ocb) + { + *p++ = ')'; + *p++ = ')'; + } + else + { + memcpy (p, ")(4:hash4:sha120:", 17); + p += 17; + memcpy (p, hashvalue, 20); + p += 20; + *p++ = ')'; + *p++ = ')'; + memcpy (p, iv+blklen, blklen); /* Add padding. */ + p += blklen; + } + assert ( p - outbuf == outlen); + if (use_ocb) + { + gcry_cipher_final (hd); + rc = gcry_cipher_encrypt (hd, outbuf, outlen, NULL, 0); + if (!rc) + { + log_assert (outlen + 16 == enclen); + rc = gcry_cipher_gettag (hd, outbuf + outlen, 16); + } + } + else + { + rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0); + } + } + + if (rc) + goto leave; + + /* Release cipher handle and check for errors. */ + gcry_cipher_close (hd); + + /* Now allocate the buffer we want to return. This is + + (protected openpgp-s2k3-sha1-aes-cbc + ((sha1 salt no_of_iterations) 16byte_iv) + encrypted_octet_string) + + in canoncical format of course. We use asprintf and %n modifier + and dummy values as placeholders. */ + { + char countbuf[35]; + + snprintf (countbuf, sizeof countbuf, "%lu", + s2k_count ? s2k_count : get_standard_s2k_count ()); + p = xtryasprintf + ("(9:protected%d:%s((4:sha18:%n_8bytes_%u:%s)%d:%n%*s)%d:%n%*s)", + (int)strlen (modestr), modestr, + &saltpos, + (unsigned int)strlen (countbuf), countbuf, + use_ocb? 12 : blklen, &ivpos, use_ocb? 12 : blklen, "", + enclen, &encpos, enclen, ""); + if (!p) + { + gpg_error_t tmperr = out_of_core (); + xfree (iv); + xfree (outbuf); + return tmperr; + } + + } + *resultlen = strlen (p); + *result = (unsigned char*)p; + memcpy (p+saltpos, s2ksalt, 8); + memcpy (p+ivpos, iv, use_ocb? 12 : blklen); + memcpy (p+encpos, outbuf, enclen); + xfree (iv); + xfree (outbuf); + return 0; + + leave: + gcry_cipher_close (hd); + xfree (iv); + xfree (outbuf); + return rc; +} + + + +/* Protect the key encoded in canonical format in PLAINKEY. We assume + a valid S-Exp here. With USE_UCB set to -1 the default scheme is + used (ie. either CBC or OCB), set to 0 the old CBC mode is used, + and set to 1 OCB is used. */ +int +agent_protect (const unsigned char *plainkey, const char *passphrase, + unsigned char **result, size_t *resultlen, + unsigned long s2k_count, int use_ocb) +{ + int rc; + const char *parmlist; + int prot_from_idx, prot_to_idx; + const unsigned char *s; + const unsigned char *hash_begin, *hash_end; + const unsigned char *prot_begin, *prot_end, *real_end; + size_t n; + int c, infidx, i; + char timestamp_exp[35]; + unsigned char *protected; + size_t protectedlen; + int depth = 0; + unsigned char *p; + int have_curve = 0; + + if (use_ocb == -1) + use_ocb = !!opt.enable_extended_key_format; + + /* Create an S-expression with the protected-at timestamp. */ + memcpy (timestamp_exp, "(12:protected-at15:", 19); + gnupg_get_isotime (timestamp_exp+19); + timestamp_exp[19+15] = ')'; + + /* Parse original key. */ + s = plainkey; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "private-key")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + depth++; + hash_begin = s; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + + for (infidx=0; protect_info[infidx].algo + && !smatch (&s, n, protect_info[infidx].algo); infidx++) + ; + if (!protect_info[infidx].algo) + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + + /* The parser below is a complete mess: To make it robust for ECC + use we should reorder the s-expression to include only what we + really need and thus guarantee the right order for saving stuff. + This should be done before calling this function and maybe with + the help of the new gcry_sexp_extract_param. */ + parmlist = protect_info[infidx].parmlist; + prot_from_idx = protect_info[infidx].prot_from; + prot_to_idx = protect_info[infidx].prot_to; + prot_begin = prot_end = NULL; + for (i=0; (c=parmlist[i]); i++) + { + if (i == prot_from_idx) + prot_begin = s; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (n != 1 || c != *s) + { + if (n == 5 && !memcmp (s, "curve", 5) + && !i && protect_info[infidx].ecc_hack) + { + /* This is a private ECC key but the first parameter is + the name of the curve. We change the parameter list + here to the one we expect in this case. */ + have_curve = 1; + parmlist = "?qd"; + prot_from_idx = 2; + prot_to_idx = 2; + } + else if (n == 5 && !memcmp (s, "flags", 5) + && i == 1 && have_curve) + { + /* "curve" followed by "flags": Change again. */ + parmlist = "??qd"; + prot_from_idx = 3; + prot_to_idx = 3; + } + else + return gpg_error (GPG_ERR_INV_SEXP); + } + s += n; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s +=n; /* skip value */ + if (*s != ')') + return gpg_error (GPG_ERR_INV_SEXP); + depth--; + if (i == prot_to_idx) + prot_end = s; + s++; + } + if (*s != ')' || !prot_begin || !prot_end ) + return gpg_error (GPG_ERR_INV_SEXP); + depth--; + hash_end = s; + s++; + /* Skip to the end of the S-expression. */ + assert (depth == 1); + rc = sskip (&s, &depth); + if (rc) + return rc; + assert (!depth); + real_end = s-1; + + rc = do_encryption (hash_begin, hash_end - hash_begin + 1, + prot_begin, prot_end - prot_begin + 1, + passphrase, timestamp_exp, sizeof (timestamp_exp), + &protected, &protectedlen, s2k_count, use_ocb); + if (rc) + return rc; + + /* Now create the protected version of the key. Note that the 10 + extra bytes are for the inserted "protected-" string (the + beginning of the plaintext reads: "((11:private-key(" ). The 35 + term is the space for (12:protected-at15:<timestamp>). */ + *resultlen = (10 + + (prot_begin-plainkey) + + protectedlen + + 35 + + (real_end-prot_end)); + *result = p = xtrymalloc (*resultlen); + if (!p) + { + gpg_error_t tmperr = out_of_core (); + xfree (protected); + return tmperr; + } + memcpy (p, "(21:protected-", 14); + p += 14; + memcpy (p, plainkey+4, prot_begin - plainkey - 4); + p += prot_begin - plainkey - 4; + memcpy (p, protected, protectedlen); + p += protectedlen; + + memcpy (p, timestamp_exp, 35); + p += 35; + + memcpy (p, prot_end+1, real_end - prot_end); + p += real_end - prot_end; + assert ( p - *result == *resultlen); + xfree (protected); + + return 0; +} + + + +/* Do the actual decryption and check the return list for consistency. */ +static gpg_error_t +do_decryption (const unsigned char *aad_begin, size_t aad_len, + const unsigned char *aadhole_begin, size_t aadhole_len, + const unsigned char *protected, size_t protectedlen, + const char *passphrase, + const unsigned char *s2ksalt, unsigned long s2kcount, + const unsigned char *iv, size_t ivlen, + int prot_cipher, int prot_cipher_keylen, int is_ocb, + unsigned char **result) +{ + int rc; + int blklen; + gcry_cipher_hd_t hd; + unsigned char *outbuf; + size_t reallen; + + blklen = gcry_cipher_get_algo_blklen (prot_cipher); + if (is_ocb) + { + /* OCB does not require a multiple of the block length but we + * check that it is long enough for the 128 bit tag and that we + * have the 96 bit nonce. */ + if (protectedlen < (4 + 16) || ivlen != 12) + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + } + else + { + if (protectedlen < 4 || (protectedlen%blklen)) + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + } + + rc = gcry_cipher_open (&hd, prot_cipher, + is_ocb? GCRY_CIPHER_MODE_OCB : + GCRY_CIPHER_MODE_CBC, + GCRY_CIPHER_SECURE); + if (rc) + return rc; + + outbuf = gcry_malloc_secure (protectedlen); + if (!outbuf) + rc = out_of_core (); + + /* Hash the passphrase and set the key. */ + if (!rc) + { + unsigned char *key; + + key = gcry_malloc_secure (prot_cipher_keylen); + if (!key) + rc = out_of_core (); + else + { + rc = hash_passphrase (passphrase, GCRY_MD_SHA1, + 3, s2ksalt, s2kcount, key, prot_cipher_keylen); + if (!rc) + rc = gcry_cipher_setkey (hd, key, prot_cipher_keylen); + xfree (key); + } + } + + /* Set the IV/nonce. */ + if (!rc) + { + rc = gcry_cipher_setiv (hd, iv, ivlen); + } + + /* Decrypt. */ + if (!rc) + { + if (is_ocb) + { + rc = gcry_cipher_authenticate (hd, aad_begin, + aadhole_begin - aad_begin); + if (!rc) + rc = gcry_cipher_authenticate + (hd, aadhole_begin + aadhole_len, + aad_len - (aadhole_begin+aadhole_len - aad_begin)); + + if (!rc) + { + gcry_cipher_final (hd); + rc = gcry_cipher_decrypt (hd, outbuf, protectedlen - 16, + protected, protectedlen - 16); + } + if (!rc) + { + rc = gcry_cipher_checktag (hd, protected + protectedlen - 16, 16); + if (gpg_err_code (rc) == GPG_ERR_CHECKSUM) + { + /* Return Bad Passphrase instead of checksum error */ + rc = gpg_error (GPG_ERR_BAD_PASSPHRASE); + } + } + } + else + { + rc = gcry_cipher_decrypt (hd, outbuf, protectedlen, + protected, protectedlen); + } + } + + /* Release cipher handle and check for errors. */ + gcry_cipher_close (hd); + if (rc) + { + xfree (outbuf); + return rc; + } + + /* Do a quick check on the data structure. */ + if (*outbuf != '(' && outbuf[1] != '(') + { + xfree (outbuf); + return gpg_error (GPG_ERR_BAD_PASSPHRASE); + } + + /* Check that we have a consistent S-Exp. */ + reallen = gcry_sexp_canon_len (outbuf, protectedlen, NULL, NULL); + if (!reallen || (reallen + blklen < protectedlen) ) + { + xfree (outbuf); + return gpg_error (GPG_ERR_BAD_PASSPHRASE); + } + *result = outbuf; + return 0; +} + + +/* Merge the parameter list contained in CLEARTEXT with the original + * protect lists PROTECTEDKEY by replacing the list at REPLACEPOS. + * Return the new list in RESULT and the MIC value in the 20 byte + * buffer SHA1HASH; if SHA1HASH is NULL no MIC will be computed. + * CUTOFF and CUTLEN will receive the offset and the length of the + * resulting list which should go into the MIC calculation but then be + * removed. */ +static gpg_error_t +merge_lists (const unsigned char *protectedkey, + size_t replacepos, + const unsigned char *cleartext, + unsigned char *sha1hash, + unsigned char **result, size_t *resultlen, + size_t *cutoff, size_t *cutlen) +{ + size_t n, newlistlen; + unsigned char *newlist, *p; + const unsigned char *s; + const unsigned char *startpos, *endpos; + int i, rc; + + *result = NULL; + *resultlen = 0; + *cutoff = 0; + *cutlen = 0; + + if (replacepos < 26) + return gpg_error (GPG_ERR_BUG); + + /* Estimate the required size of the resulting list. We have a large + safety margin of >20 bytes (FIXME: MIC hash from CLEARTEXT and the + removed "protected-" */ + newlistlen = gcry_sexp_canon_len (protectedkey, 0, NULL, NULL); + if (!newlistlen) + return gpg_error (GPG_ERR_BUG); + n = gcry_sexp_canon_len (cleartext, 0, NULL, NULL); + if (!n) + return gpg_error (GPG_ERR_BUG); + newlistlen += n; + newlist = gcry_malloc_secure (newlistlen); + if (!newlist) + return out_of_core (); + + /* Copy the initial segment */ + strcpy ((char*)newlist, "(11:private-key"); + p = newlist + 15; + memcpy (p, protectedkey+15+10, replacepos-15-10); + p += replacepos-15-10; + + /* Copy the cleartext. */ + s = cleartext; + if (*s != '(' && s[1] != '(') + return gpg_error (GPG_ERR_BUG); /*we already checked this */ + s += 2; + startpos = s; + while ( *s == '(' ) + { + s++; + n = snext (&s); + if (!n) + goto invalid_sexp; + s += n; + n = snext (&s); + if (!n) + goto invalid_sexp; + s += n; + if ( *s != ')' ) + goto invalid_sexp; + s++; + } + if ( *s != ')' ) + goto invalid_sexp; + endpos = s; + s++; + + /* Intermezzo: Get the MIC if requested. */ + if (sha1hash) + { + if (*s != '(') + goto invalid_sexp; + s++; + n = snext (&s); + if (!smatch (&s, n, "hash")) + goto invalid_sexp; + n = snext (&s); + if (!smatch (&s, n, "sha1")) + goto invalid_sexp; + n = snext (&s); + if (n != 20) + goto invalid_sexp; + memcpy (sha1hash, s, 20); + s += n; + if (*s != ')') + goto invalid_sexp; + } + + /* Append the parameter list. */ + memcpy (p, startpos, endpos - startpos); + p += endpos - startpos; + + /* Skip over the protected list element in the original list. */ + s = protectedkey + replacepos; + assert (*s == '('); + s++; + i = 1; + rc = sskip (&s, &i); + if (rc) + goto failure; + /* Record the position of the optional protected-at expression. */ + if (*s == '(') + { + const unsigned char *save_s = s; + s++; + n = snext (&s); + if (smatch (&s, n, "protected-at")) + { + i = 1; + rc = sskip (&s, &i); + if (rc) + goto failure; + *cutlen = s - save_s; + } + s = save_s; + } + startpos = s; + i = 2; /* we are inside this level */ + rc = sskip (&s, &i); + if (rc) + goto failure; + assert (s[-1] == ')'); + endpos = s; /* one behind the end of the list */ + + /* Append the rest. */ + if (*cutlen) + *cutoff = p - newlist; + memcpy (p, startpos, endpos - startpos); + p += endpos - startpos; + + + /* ready */ + *result = newlist; + *resultlen = newlistlen; + return 0; + + failure: + wipememory (newlist, newlistlen); + xfree (newlist); + return rc; + + invalid_sexp: + wipememory (newlist, newlistlen); + xfree (newlist); + return gpg_error (GPG_ERR_INV_SEXP); +} + + + +/* Unprotect the key encoded in canonical format. We assume a valid + S-Exp here. If a protected-at item is available, its value will + be stored at protected_at unless this is NULL. */ +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) +{ + static const struct { + const char *name; /* Name of the protection method. */ + int algo; /* (A zero indicates the "openpgp-native" hack.) */ + int keylen; /* Used key length in bytes. */ + unsigned int is_ocb:1; + } algotable[] = { + { "openpgp-s2k3-sha1-aes-cbc", GCRY_CIPHER_AES128, (128/8)}, + { "openpgp-s2k3-sha1-aes256-cbc", GCRY_CIPHER_AES256, (256/8)}, + { "openpgp-s2k3-ocb-aes", GCRY_CIPHER_AES128, (128/8), 1}, + { "openpgp-native", 0, 0 } + }; + int rc; + const unsigned char *s; + const unsigned char *protect_list; + size_t n; + int infidx, i; + unsigned char sha1hash[20], sha1hash2[20]; + const unsigned char *s2ksalt; + unsigned long s2kcount; + const unsigned char *iv; + int prot_cipher, prot_cipher_keylen; + int is_ocb; + const unsigned char *aad_begin, *aad_end, *aadhole_begin, *aadhole_end; + const unsigned char *prot_begin; + unsigned char *cleartext; + unsigned char *final; + size_t finallen; + size_t cutoff, cutlen; + + if (protected_at) + *protected_at = 0; + + s = protectedkey; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "protected-private-key")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + { + aad_begin = aad_end = s; + aad_end++; + i = 1; + rc = sskip (&aad_end, &i); + if (rc) + return rc; + } + + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + + for (infidx=0; protect_info[infidx].algo + && !smatch (&s, n, protect_info[infidx].algo); infidx++) + ; + if (!protect_info[infidx].algo) + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + + /* See wether we have a protected-at timestamp. */ + protect_list = s; /* Save for later. */ + if (protected_at) + { + while (*s == '(') + { + prot_begin = s; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "protected-at")) + { + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (n != 15) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + memcpy (protected_at, s, 15); + protected_at[15] = 0; + break; + } + s += n; + i = 1; + rc = sskip (&s, &i); + if (rc) + return rc; + } + } + + /* Now find the list with the protected information. Here is an + example for such a list: + (protected openpgp-s2k3-sha1-aes-cbc + ((sha1 <salt> <count>) <Initialization_Vector>) + <encrypted_data>) + */ + s = protect_list; + for (;;) + { + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + prot_begin = s; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "protected")) + break; + s += n; + i = 1; + rc = sskip (&s, &i); + if (rc) + return rc; + } + /* found */ + { + aadhole_begin = aadhole_end = prot_begin; + aadhole_end++; + i = 1; + rc = sskip (&aadhole_end, &i); + if (rc) + return rc; + } + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + + /* Lookup the protection algo. */ + prot_cipher = 0; /* (avoid gcc warning) */ + prot_cipher_keylen = 0; /* (avoid gcc warning) */ + is_ocb = 0; + for (i=0; i < DIM (algotable); i++) + if (smatch (&s, n, algotable[i].name)) + { + prot_cipher = algotable[i].algo; + prot_cipher_keylen = algotable[i].keylen; + is_ocb = algotable[i].is_ocb; + break; + } + if (i == DIM (algotable)) + return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION); + + if (!prot_cipher) /* This is "openpgp-native". */ + { + gcry_sexp_t s_prot_begin; + + rc = gcry_sexp_sscan (&s_prot_begin, NULL, + prot_begin, + gcry_sexp_canon_len (prot_begin, 0,NULL,NULL)); + if (rc) + return rc; + + rc = convert_from_openpgp_native (ctrl, s_prot_begin, passphrase, &final); + gcry_sexp_release (s_prot_begin); + if (!rc) + { + *result = final; + *resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL); + } + return rc; + } + + if (*s != '(' || s[1] != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s += 2; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "sha1")) + return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION); + n = snext (&s); + if (n != 8) + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + s2ksalt = s; + s += n; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + /* We expect a list close as next, so we can simply use strtoul() + here. We might want to check that we only have digits - but this + is nothing we should worry about */ + if (s[n] != ')' ) + return gpg_error (GPG_ERR_INV_SEXP); + + /* Old versions of gpg-agent used the funny floating point number in + a byte encoding as specified by OpenPGP. However this is not + needed and thus we now store it as a plain unsigned integer. We + can easily distinguish the old format by looking at its value: + Less than 256 is an old-style encoded number; other values are + plain integers. In any case we check that they are at least + 65536 because we never used a lower value in the past and we + should have a lower limit. */ + s2kcount = strtoul ((const char*)s, NULL, 10); + if (!s2kcount) + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + if (s2kcount < 256) + s2kcount = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6); + if (s2kcount < 65536) + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + + s += n; + s++; /* skip list end */ + + n = snext (&s); + if (is_ocb) + { + if (n != 12) /* Wrong size of the nonce. */ + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + } + else + { + if (n != 16) /* Wrong blocksize for IV (we support only 128 bit). */ + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + } + iv = s; + s += n; + if (*s != ')' ) + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + + cleartext = NULL; /* Avoid cc warning. */ + rc = do_decryption (aad_begin, aad_end - aad_begin, + aadhole_begin, aadhole_end - aadhole_begin, + s, n, + passphrase, s2ksalt, s2kcount, + iv, is_ocb? 12:16, + prot_cipher, prot_cipher_keylen, is_ocb, + &cleartext); + if (rc) + return rc; + + rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext, + is_ocb? NULL : sha1hash, + &final, &finallen, &cutoff, &cutlen); + /* Albeit cleartext has been allocated in secure memory and thus + xfree will wipe it out, we do an extra wipe just in case + somethings goes badly wrong. */ + wipememory (cleartext, n); + xfree (cleartext); + if (rc) + return rc; + + if (!is_ocb) + { + rc = calculate_mic (final, sha1hash2); + if (!rc && memcmp (sha1hash, sha1hash2, 20)) + rc = gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + if (rc) + { + wipememory (final, finallen); + xfree (final); + return rc; + } + } + + /* Now remove the part which is included in the MIC but should not + go into the final thing. */ + if (cutlen) + { + memmove (final+cutoff, final+cutoff+cutlen, finallen-cutoff-cutlen); + finallen -= cutlen; + } + + *result = final; + *resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL); + return 0; +} + + +/* Check the type of the private key, this is one of the constants: + PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the + value 0), PRIVATE_KEY_CLEAR for an unprotected private key. + PRIVATE_KEY_PROTECTED for an protected private key or + PRIVATE_KEY_SHADOWED for a sub key where the secret parts are + stored elsewhere. Finally PRIVATE_KEY_OPENPGP_NONE may be returned + is the key is still in the openpgp-native format but without + protection. */ +int +agent_private_key_type (const unsigned char *privatekey) +{ + const unsigned char *s; + size_t n; + int i; + + s = privatekey; + if (*s != '(') + return PRIVATE_KEY_UNKNOWN; + s++; + n = snext (&s); + if (!n) + return PRIVATE_KEY_UNKNOWN; + if (smatch (&s, n, "protected-private-key")) + { + /* We need to check whether this is openpgp-native protected + with the protection method "none". In that case we return a + different key type so that the caller knows that there is no + need to ask for a passphrase. */ + if (*s != '(') + return PRIVATE_KEY_PROTECTED; /* Unknown sexp - assume protected. */ + s++; + n = snext (&s); + if (!n) + return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ + s += n; /* Skip over the algo */ + + /* Find the (protected ...) list. */ + for (;;) + { + if (*s != '(') + return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ + s++; + n = snext (&s); + if (!n) + return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ + if (smatch (&s, n, "protected")) + break; + s += n; + i = 1; + if (sskip (&s, &i)) + return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ + } + /* Found - Is this openpgp-native? */ + n = snext (&s); + if (!n) + return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ + if (smatch (&s, n, "openpgp-native")) /* Yes. */ + { + if (*s != '(') + return PRIVATE_KEY_UNKNOWN; /* Unknown sexp. */ + s++; + n = snext (&s); + if (!n) + return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ + s += n; /* Skip over "openpgp-private-key". */ + /* Find the (protection ...) list. */ + for (;;) + { + if (*s != '(') + return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ + s++; + n = snext (&s); + if (!n) + return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ + if (smatch (&s, n, "protection")) + break; + s += n; + i = 1; + if (sskip (&s, &i)) + return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ + } + /* Found - Is the mode "none"? */ + n = snext (&s); + if (!n) + return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ + if (smatch (&s, n, "none")) + return PRIVATE_KEY_OPENPGP_NONE; /* Yes. */ + } + + return PRIVATE_KEY_PROTECTED; + } + if (smatch (&s, n, "shadowed-private-key")) + return PRIVATE_KEY_SHADOWED; + if (smatch (&s, n, "private-key")) + return PRIVATE_KEY_CLEAR; + return PRIVATE_KEY_UNKNOWN; +} + + + +/* Transform a passphrase into a suitable key of length KEYLEN and + store this key in the caller provided buffer KEY. The caller must + provide an HASHALGO, a valid S2KMODE (see rfc-2440) and depending on + that mode an S2KSALT of 8 random bytes and an S2KCOUNT. + + Returns an error code on failure. */ +static int +hash_passphrase (const char *passphrase, int hashalgo, + int s2kmode, + const unsigned char *s2ksalt, + unsigned long s2kcount, + unsigned char *key, size_t keylen) +{ + /* The key derive function does not support a zero length string for + the passphrase in the S2K modes. Return a better suited error + code than GPG_ERR_INV_DATA. */ + if (!passphrase || !*passphrase) + return gpg_error (GPG_ERR_NO_PASSPHRASE); + return gcry_kdf_derive (passphrase, strlen (passphrase), + s2kmode == 3? GCRY_KDF_ITERSALTED_S2K : + s2kmode == 1? GCRY_KDF_SALTED_S2K : + s2kmode == 0? GCRY_KDF_SIMPLE_S2K : GCRY_KDF_NONE, + hashalgo, s2ksalt, 8, s2kcount, + keylen, key); +} + + +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) +{ + return hash_passphrase (passphrase, hashalgo, s2kmode, s2ksalt, + S2K_DECODE_COUNT (s2kcount), + key, keylen); +} + + + + +/* Create an canonical encoded S-expression with the shadow info from + a card's SERIALNO and the IDSTRING. */ +unsigned char * +make_shadow_info (const char *serialno, const char *idstring) +{ + const char *s; + char *info, *p; + char numbuf[20]; + size_t n; + + for (s=serialno, n=0; *s && s[1]; s += 2) + n++; + + info = p = xtrymalloc (1 + sizeof numbuf + n + + sizeof numbuf + strlen (idstring) + 1 + 1); + if (!info) + return NULL; + *p++ = '('; + p = stpcpy (p, smklen (numbuf, sizeof numbuf, n, NULL)); + for (s=serialno; *s && s[1]; s += 2) + *(unsigned char *)p++ = xtoi_2 (s); + p = stpcpy (p, smklen (numbuf, sizeof numbuf, strlen (idstring), NULL)); + p = stpcpy (p, idstring); + *p++ = ')'; + *p = 0; + return (unsigned char *)info; +} + + + +/* Create a shadow key from a public key. We use the shadow protocol + "t1-v1" and insert the S-expressionn SHADOW_INFO. The resulting + S-expression is returned in an allocated buffer RESULT will point + to. The input parameters are expected to be valid canonicalized + S-expressions */ +int +agent_shadow_key (const unsigned char *pubkey, + const unsigned char *shadow_info, + unsigned char **result) +{ + const unsigned char *s; + const unsigned char *point; + size_t n; + int depth = 0; + char *p; + size_t pubkey_len = gcry_sexp_canon_len (pubkey, 0, NULL,NULL); + size_t shadow_info_len = gcry_sexp_canon_len (shadow_info, 0, NULL,NULL); + + if (!pubkey_len || !shadow_info_len) + return gpg_error (GPG_ERR_INV_VALUE); + s = pubkey; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "public-key")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; /* skip over the algorithm name */ + + while (*s != ')') + { + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s +=n; /* skip value */ + if (*s != ')') + return gpg_error (GPG_ERR_INV_SEXP); + depth--; + s++; + } + point = s; /* insert right before the point */ + depth--; + s++; + assert (depth == 1); + + /* Calculate required length by taking in account: the "shadowed-" + prefix, the "shadowed", "t1-v1" as well as some parenthesis */ + n = 12 + pubkey_len + 1 + 3+8 + 2+5 + shadow_info_len + 1; + *result = xtrymalloc (n); + p = (char*)*result; + if (!p) + return out_of_core (); + p = stpcpy (p, "(20:shadowed-private-key"); + /* (10:public-key ...)*/ + memcpy (p, pubkey+14, point - (pubkey+14)); + p += point - (pubkey+14); + p = stpcpy (p, "(8:shadowed5:t1-v1"); + memcpy (p, shadow_info, shadow_info_len); + p += shadow_info_len; + *p++ = ')'; + memcpy (p, point, pubkey_len - (point - pubkey)); + p += pubkey_len - (point - pubkey); + + return 0; +} + +/* Parse a canonical encoded shadowed key and return a pointer to the + inner list with the shadow_info */ +gpg_error_t +agent_get_shadow_info (const unsigned char *shadowkey, + unsigned char const **shadow_info) +{ + const unsigned char *s; + size_t n; + int depth = 0; + + s = shadowkey; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "shadowed-private-key")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; /* skip over the algorithm name */ + + for (;;) + { + if (*s == ')') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "shadowed")) + break; + s += n; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s +=n; /* skip value */ + if (*s != ')') + return gpg_error (GPG_ERR_INV_SEXP); + depth--; + s++; + } + /* Found the shadowed list, S points to the protocol */ + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "t1-v1")) + { + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + *shadow_info = s; + } + else + return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); + return 0; +} + + +/* Parse the canonical encoded SHADOW_INFO S-expression. On success + the hex encoded serial number is returned as a malloced strings at + R_HEXSN and the Id string as a malloced string at R_IDSTR. On + error an error code is returned and NULL is stored at the result + parameters addresses. If the serial number or the ID string is not + required, NULL may be passed for them. */ +gpg_error_t +parse_shadow_info (const unsigned char *shadow_info, + char **r_hexsn, char **r_idstr, int *r_pinlen) +{ + const unsigned char *s; + size_t n; + + if (r_hexsn) + *r_hexsn = NULL; + if (r_idstr) + *r_idstr = NULL; + if (r_pinlen) + *r_pinlen = 0; + + s = shadow_info; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + + if (r_hexsn) + { + *r_hexsn = bin2hex (s, n, NULL); + if (!*r_hexsn) + return gpg_error_from_syserror (); + } + s += n; + + n = snext (&s); + if (!n) + { + if (r_hexsn) + { + xfree (*r_hexsn); + *r_hexsn = NULL; + } + return gpg_error (GPG_ERR_INV_SEXP); + } + + if (r_idstr) + { + *r_idstr = xtrymalloc (n+1); + if (!*r_idstr) + { + if (r_hexsn) + { + xfree (*r_hexsn); + *r_hexsn = NULL; + } + return gpg_error_from_syserror (); + } + memcpy (*r_idstr, s, n); + (*r_idstr)[n] = 0; + trim_spaces (*r_idstr); + } + + /* Parse the optional PINLEN. */ + n = snext (&s); + if (!n) + return 0; + + if (r_pinlen) + { + char *tmpstr = xtrymalloc (n+1); + if (!tmpstr) + { + if (r_hexsn) + { + xfree (*r_hexsn); + *r_hexsn = NULL; + } + if (r_idstr) + { + xfree (*r_idstr); + *r_idstr = NULL; + } + return gpg_error_from_syserror (); + } + memcpy (tmpstr, s, n); + tmpstr[n] = 0; + + *r_pinlen = (int)strtol (tmpstr, NULL, 10); + xfree (tmpstr); + } + + return 0; +} diff --git a/agent/t-protect.c b/agent/t-protect.c new file mode 100644 index 0000000..88b5525 --- /dev/null +++ b/agent/t-protect.c @@ -0,0 +1,351 @@ +/* t-protect.c - Module tests for protect.c + * Copyright (C) 2005 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "agent.h" + + +#define pass() do { ; } while(0) +#define fail() do { fprintf (stderr, "%s:%d: test failed\n",\ + __FILE__,__LINE__); \ + exit (1); \ + } while(0) + + +static void +test_agent_protect (void) +{ + /* Protect the key encoded in canonical format in PLAINKEY. We assume + a valid S-Exp here. */ + + unsigned int i; + int ret; + struct key_spec + { + const char *string; + }; + /* Valid RSA key. */ + struct key_spec key_rsa_valid = + { + "\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28\x33\x3A\x72\x73" + "\x61\x28\x31\x3A\x6E\x31\x32\x39\x3A\x00\xB6\xB5\x09\x59\x6A\x9E\xCA\xBC\x93\x92" + "\x12\xF8\x91\xE6\x56\xA6\x26\xBA\x07\xDA\x85\x21\xA9\xCA\xD4\xC0\x8E\x64\x0C\x04" + "\x05\x2F\xBB\x87\xF4\x24\xEF\x1A\x02\x75\xA4\x8A\x92\x99\xAC\x9D\xB6\x9A\xBE\x3D" + "\x01\x24\xE6\xC7\x56\xB1\xF7\xDF\xB9\xB8\x42\xD6\x25\x1A\xEA\x6E\xE8\x53\x90\x49" + "\x5C\xAD\xA7\x3D\x67\x15\x37\xFC\xE5\x85\x0A\x93\x2F\x32\xBA\xB6\x0A\xB1\xAC\x1F" + "\x85\x2C\x1F\x83\xC6\x25\xE7\xA7\xD7\x0C\xDA\x9E\xF1\x6D\x5C\x8E\x47\x73\x9D\x77" + "\xDF\x59\x26\x1A\xBE\x84\x54\x80\x7F\xF4\x41\xE1\x43\xFB\xD3\x7F\x85\x45\x29\x28" + "\x31\x3A\x65\x33\x3A\x01\x00\x01\x29\x28\x31\x3A\x64\x31\x32\x38\x3A\x07\x7A\xD3" + "\xDE\x28\x42\x45\xF4\x80\x6A\x1B\x82\xB7\x9E\x61\x6F\xBD\xE8\x21\xC8\x2D\x69\x1A" + "\x65\x66\x5E\x57\xB5\xFA\xD3\xF3\x4E\x67\xF4\x01\xE7\xBD\x2E\x28\x69\x9E\x89\xD9" + "\xC4\x96\xCF\x82\x19\x45\xAE\x83\xAC\x7A\x12\x31\x17\x6A\x19\x6B\xA6\x02\x7E\x77" + "\xD8\x57\x89\x05\x5D\x50\x40\x4A\x7A\x2A\x95\xB1\x51\x2F\x91\xF1\x90\xBB\xAE\xF7" + "\x30\xED\x55\x0D\x22\x7D\x51\x2F\x89\xC0\xCD\xB3\x1A\xC0\x6F\xA9\xA1\x95\x03\xDD" + "\xF6\xB6\x6D\x0B\x42\xB9\x69\x1B\xFD\x61\x40\xEC\x17\x20\xFF\xC4\x8A\xE0\x0C\x34" + "\x79\x6D\xC8\x99\xE5\x29\x28\x31\x3A\x70\x36\x35\x3A\x00\xD5\x86\xC7\x8E\x5F\x1B" + "\x4B\xF2\xE7\xCD\x7A\x04\xCA\x09\x19\x11\x70\x6F\x19\x78\x8B\x93\xE4\x4E\xE2\x0A" + "\xAF\x46\x2E\x83\x63\xE9\x8A\x72\x25\x3E\xD8\x45\xCC\xBF\x24\x81\xBB\x35\x1E\x85" + "\x57\xC8\x5B\xCF\xFF\x0D\xAB\xDB\xFF\x8E\x26\xA7\x9A\x09\x38\x09\x6F\x27\x29\x28" + "\x31\x3A\x71\x36\x35\x3A\x00\xDB\x0C\xDF\x60\xF2\x6F\x2A\x29\x6C\x88\xD6\xBF\x9F" + "\x8E\x5B\xE4\x5C\x0D\xDD\x71\x3C\x96\xCC\x73\xEB\xCB\x48\xB0\x61\x74\x09\x43\xF2" + "\x1D\x2A\x93\xD6\xE4\x2A\x72\x11\xE7\xF0\x2A\x95\xDC\xED\x6C\x39\x0A\x67\xAD\x21" + "\xEC\xF7\x39\xAE\x8A\x0C\xA4\x6F\xF2\xEB\xB3\x29\x28\x31\x3A\x75\x36\x34\x3A\x33" + "\x14\x91\x95\xF1\x69\x12\xDB\x20\xA4\x8D\x02\x0D\xBC\x3B\x9E\x38\x81\xB3\x9D\x72" + "\x2B\xF7\x93\x78\xF6\x34\x0F\x43\x14\x8A\x6E\x9F\xC5\xF5\x3E\x28\x53\xB7\x38\x7B" + "\xA4\x44\x3B\xA5\x3A\x52\xFC\xA8\x17\x3D\xE6\xE8\x5B\x42\xF9\x78\x3D\x4A\x78\x17" + "\xD0\x68\x0B\x29\x29\x29\x00" + }; + /* This RSA key is missing the last closing brace. */ + struct key_spec key_rsa_bogus_0 = + { + "\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28\x33\x3A\x72\x73" + "\x61\x28\x31\x3A\x6E\x31\x32\x39\x3A\x00\xB6\xB5\x09\x59\x6A\x9E\xCA\xBC\x93\x92" + "\x12\xF8\x91\xE6\x56\xA6\x26\xBA\x07\xDA\x85\x21\xA9\xCA\xD4\xC0\x8E\x64\x0C\x04" + "\x05\x2F\xBB\x87\xF4\x24\xEF\x1A\x02\x75\xA4\x8A\x92\x99\xAC\x9D\xB6\x9A\xBE\x3D" + "\x01\x24\xE6\xC7\x56\xB1\xF7\xDF\xB9\xB8\x42\xD6\x25\x1A\xEA\x6E\xE8\x53\x90\x49" + "\x5C\xAD\xA7\x3D\x67\x15\x37\xFC\xE5\x85\x0A\x93\x2F\x32\xBA\xB6\x0A\xB1\xAC\x1F" + "\x85\x2C\x1F\x83\xC6\x25\xE7\xA7\xD7\x0C\xDA\x9E\xF1\x6D\x5C\x8E\x47\x73\x9D\x77" + "\xDF\x59\x26\x1A\xBE\x84\x54\x80\x7F\xF4\x41\xE1\x43\xFB\xD3\x7F\x85\x45\x29\x28" + "\x31\x3A\x65\x33\x3A\x01\x00\x01\x29\x28\x31\x3A\x64\x31\x32\x38\x3A\x07\x7A\xD3" + "\xDE\x28\x42\x45\xF4\x80\x6A\x1B\x82\xB7\x9E\x61\x6F\xBD\xE8\x21\xC8\x2D\x69\x1A" + "\x65\x66\x5E\x57\xB5\xFA\xD3\xF3\x4E\x67\xF4\x01\xE7\xBD\x2E\x28\x69\x9E\x89\xD9" + "\xC4\x96\xCF\x82\x19\x45\xAE\x83\xAC\x7A\x12\x31\x17\x6A\x19\x6B\xA6\x02\x7E\x77" + "\xD8\x57\x89\x05\x5D\x50\x40\x4A\x7A\x2A\x95\xB1\x51\x2F\x91\xF1\x90\xBB\xAE\xF7" + "\x30\xED\x55\x0D\x22\x7D\x51\x2F\x89\xC0\xCD\xB3\x1A\xC0\x6F\xA9\xA1\x95\x03\xDD" + "\xF6\xB6\x6D\x0B\x42\xB9\x69\x1B\xFD\x61\x40\xEC\x17\x20\xFF\xC4\x8A\xE0\x0C\x34" + "\x79\x6D\xC8\x99\xE5\x29\x28\x31\x3A\x70\x36\x35\x3A\x00\xD5\x86\xC7\x8E\x5F\x1B" + "\x4B\xF2\xE7\xCD\x7A\x04\xCA\x09\x19\x11\x70\x6F\x19\x78\x8B\x93\xE4\x4E\xE2\x0A" + "\xAF\x46\x2E\x83\x63\xE9\x8A\x72\x25\x3E\xD8\x45\xCC\xBF\x24\x81\xBB\x35\x1E\x85" + "\x57\xC8\x5B\xCF\xFF\x0D\xAB\xDB\xFF\x8E\x26\xA7\x9A\x09\x38\x09\x6F\x27\x29\x28" + "\x31\x3A\x71\x36\x35\x3A\x00\xDB\x0C\xDF\x60\xF2\x6F\x2A\x29\x6C\x88\xD6\xBF\x9F" + "\x8E\x5B\xE4\x5C\x0D\xDD\x71\x3C\x96\xCC\x73\xEB\xCB\x48\xB0\x61\x74\x09\x43\xF2" + "\x1D\x2A\x93\xD6\xE4\x2A\x72\x11\xE7\xF0\x2A\x95\xDC\xED\x6C\x39\x0A\x67\xAD\x21" + "\xEC\xF7\x39\xAE\x8A\x0C\xA4\x6F\xF2\xEB\xB3\x29\x28\x31\x3A\x75\x36\x34\x3A\x33" + "\x14\x91\x95\xF1\x69\x12\xDB\x20\xA4\x8D\x02\x0D\xBC\x3B\x9E\x38\x81\xB3\x9D\x72" + "\x2B\xF7\x93\x78\xF6\x34\x0F\x43\x14\x8A\x6E\x9F\xC5\xF5\x3E\x28\x53\xB7\x38\x7B" + "\xA4\x44\x3B\xA5\x3A\x52\xFC\xA8\x17\x3D\xE6\xE8\x5B\x42\xF9\x78\x3D\x4A\x78\x17" + "\xD0\x68\x0B\x29\x29\x00" + }; + /* This RSA key is the 'e' value. */ + struct key_spec key_rsa_bogus_1 = + { + "\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28\x33\x3A\x72\x73" + "\x61\x28\x31\x3A\x6E\x31\x32\x39\x3A\x00\xA8\x80\xB6\x71\xF4\x95\x9F\x49\x84\xED" + "\xC1\x1D\x5F\xFF\xED\x14\x7B\x9C\x6A\x62\x0B\x7B\xE2\x3E\x41\x48\x49\x85\xF5\x64" + "\x50\x04\x9D\x30\xFC\x84\x1F\x01\xC3\xC3\x15\x03\x48\x6D\xFE\x59\x0B\xB0\xD0\x3E" + "\x68\x8A\x05\x7A\x62\xB0\xB9\x6E\xC5\xD2\xA8\xEE\x0C\x6B\xDE\x5E\x3D\x8E\xE8\x8F" + "\xB3\xAE\x86\x99\x7E\xDE\x2B\xC2\x4D\x60\x51\xDB\xB1\x2C\xD0\x38\xEC\x88\x62\x3E" + "\xA9\xDD\x11\x53\x04\x17\xE4\xF2\x07\x50\xDC\x44\xED\x14\xF5\x0B\xAB\x9C\xBC\x24" + "\xC6\xCB\xAD\x0F\x05\x25\x94\xE2\x73\xEB\x14\xD5\xEE\x5E\x18\xF0\x40\x31\x29\x28" + "\x31\x3A\x64\x31\x32\x38\x3A\x40\xD0\x55\x9D\x2A\xA7\xBC\xBF\xE2\x3E\x33\x98\x71" + "\x7B\x37\x3D\xB8\x38\x57\xA1\x43\xEA\x90\x81\x42\xCA\x23\xE1\xBF\x9C\xA8\xBC\xC5" + "\x9B\xF8\x9D\x77\x71\xCD\xD3\x85\x8B\x20\x3A\x92\xE9\xBC\x79\xF3\xF7\xF5\x6D\x15" + "\xA3\x58\x3F\xC2\xEB\xED\x72\xD4\xE0\xCF\xEC\xB3\xEC\xEB\x09\xEA\x1E\x72\x6A\xBA" + "\x95\x82\x2C\x7E\x30\x95\x66\x3F\xA8\x2D\x40\x0F\x7A\x12\x4E\xF0\x71\x0F\x97\xDB" + "\x81\xE4\x39\x6D\x24\x58\xFA\xAB\x3A\x36\x73\x63\x01\x77\x42\xC7\x9A\xEA\x87\xDA" + "\x93\x8F\x6C\x64\xAD\x9E\xF0\xCA\xA2\x89\xA4\x0E\xB3\x25\x73\x29\x28\x31\x3A\x70" + "\x36\x35\x3A\x00\xC3\xF7\x37\x3F\x9D\x93\xEC\xC7\x5E\x4C\xB5\x73\x29\x62\x35\x80" + "\xC6\x7C\x1B\x1E\x68\x5F\x92\x56\x77\x0A\xE2\x8E\x95\x74\x87\xA5\x2F\x83\x2D\xF7" + "\xA1\xC2\x78\x54\x18\x6E\xDE\x35\xF0\x9F\x7A\xCA\x80\x5C\x83\x5C\x44\xAD\x8B\xE7" + "\x5B\xE2\x63\x7D\x6A\xC7\x98\x97\x29\x28\x31\x3A\x71\x36\x35\x3A\x00\xDC\x1F\xB1" + "\xB3\xD8\x13\xE0\x09\x19\xFD\x1C\x58\xA1\x2B\x02\xB4\xC8\xF2\x1C\xE7\xF9\xC6\x3B" + "\x68\xB9\x72\x43\x86\xEF\xA9\x94\x68\x02\xEF\x7D\x77\xE0\x0A\xD1\xD7\x48\xFD\xCD" + "\x98\xDA\x13\x8A\x76\x48\xD4\x0F\x63\x28\xFA\x01\x1B\xF3\xC7\x15\xB8\x53\x22\x7E" + "\x77\x29\x28\x31\x3A\x75\x36\x35\x3A\x00\xB3\xBB\x4D\xEE\x5A\xAF\xD0\xF2\x56\x8A" + "\x10\x2D\x6F\x4B\x2D\x76\x49\x9B\xE9\xA8\x60\x5D\x9E\x7E\x50\x86\xF1\xA1\x0F\x28" + "\x9B\x7B\xE8\xDD\x1F\x87\x4E\x79\x7B\x50\x12\xA7\xB4\x8B\x52\x38\xEC\x7C\xBB\xB9" + "\x55\x87\x11\x1C\x74\xE7\x7F\xA0\xBA\xE3\x34\x5D\x61\xBF\x29\x29\x29\x00" + }; + + struct key_spec key_ecdsa_valid = + { + "\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28" + "\x35\x3A\x65\x63\x64\x73\x61\x28\x35\x3A\x63\x75\x72\x76\x65\x31" + "\x30\x3A\x4E\x49\x53\x54\x20\x50\x2D\x32\x35\x36\x29\x28\x31\x3A" + "\x71\x36\x35\x3A\x04\x64\x5A\x12\x6F\x86\x7C\x43\x87\x2B\x7C\xAF" + "\x77\xFE\xD8\x22\x31\xEA\xE6\x89\x9F\xAA\xEA\x63\x26\xBC\x49\xED" + "\x85\xC6\xD2\xC9\x8B\x38\xD2\x78\x75\xE6\x1C\x27\x57\x01\xC5\xA1" + "\xE3\xF9\x1F\xBE\xCF\xC1\x72\x73\xFE\xA4\x58\xB6\x6A\x92\x7D\x33" + "\x1D\x02\xC9\xCB\x12\x29\x28\x31\x3A\x64\x33\x33\x3A\x00\x81\x2D" + "\x69\x9A\x5F\x5B\x6F\x2C\x99\x61\x36\x15\x6B\x44\xD8\x06\xC1\x54" + "\xC1\x4C\xFB\x70\x6A\xB6\x64\x81\x78\xF3\x94\x2F\x30\x5D\x29\x29" + "\x28\x37\x3A\x63\x6F\x6D\x6D\x65\x6E\x74\x32\x32\x3A\x2F\x68\x6F" + "\x6D\x65\x2F\x77\x6B\x2F\x2E\x73\x73\x68\x2F\x69\x64\x5F\x65\x63" + "\x64\x73\x61\x29\x29" + }; + + + struct + { + const char *key; + const char *passphrase; + int no_result_expected; + int compare_results; + unsigned char *result_expected; + size_t resultlen_expected; + int ret_expected; + unsigned char *result; + size_t resultlen; + } specs[] = + { + /* Invalid S-Expressions */ + /* - non-NULL */ + { "", + "passphrase", 1, 0, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 }, + /* - NULL; disabled, this segfaults */ + //{ NULL, + // "passphrase", 1, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 }, + + /* Valid and invalid keys. */ + { key_rsa_valid.string, + "passphrase", 0, 0, NULL, 0, 0, NULL, 0 }, + { key_rsa_bogus_0.string, + "passphrase", 0, 0, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 }, + { key_rsa_bogus_1.string, + "passphrase", 0, 0, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 }, + + { key_ecdsa_valid.string, + "passphrase", 0, 0, NULL, 0, 0, NULL, 0 }, + + /* FIXME: add more test data. */ + }; + + for (i = 0; i < DIM (specs); i++) + { + ret = agent_protect ((const unsigned char*)specs[i].key, + specs[i].passphrase, + &specs[i].result, &specs[i].resultlen, 0, -1); + if (gpg_err_code (ret) != specs[i].ret_expected) + { + printf ("agent_protect(%d) returned '%i/%s'; expected '%i/%s'\n", + i, ret, gpg_strerror (ret), + specs[i].ret_expected, gpg_strerror (specs[i].ret_expected)); + abort (); + } + + if (specs[i].no_result_expected) + { + assert (! specs[i].result); + assert (! specs[i].resultlen); + } + else + { + if (specs[i].compare_results) + { + assert (specs[i].resultlen == specs[i].resultlen_expected); + if (specs[i].result_expected) + assert (! memcmp (specs[i].result, specs[i].result_expected, + specs[i].resultlen)); + else + assert (! specs[i].result); + } + xfree (specs[i].result); + } + } +} + + +static void +test_agent_unprotect (void) +{ + /* Unprotect the key encoded in canonical format. We assume a valid + S-Exp here. */ +/* int */ +/* agent_unprotect (const unsigned char *protectedkey, const char *passphrase, */ +/* unsigned char **result, size_t *resultlen) */ +} + + +static void +test_agent_private_key_type (void) +{ +/* Check the type of the private key, this is one of the constants: + PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the + value 0), PRIVATE_KEY_CLEAR for an unprotected private key. + PRIVATE_KEY_PROTECTED for an protected private key or + PRIVATE_KEY_SHADOWED for a sub key where the secret parts are stored + elsewhere. */ +/* int */ +/* agent_private_key_type (const unsigned char *privatekey) */ +} + + +static void +test_make_shadow_info (void) +{ +#if 0 + static struct + { + const char *snstr; + const char *idstr; + const char *expected; + } data[] = { + { "", "", NULL }, + + }; + int i; + unsigned char *result; + + for (i=0; i < DIM(data); i++) + { + result = make_shadow_info (data[i].snstr, data[i].idstr); + if (!result && !data[i].expected) + pass (); + else if (!result && data[i].expected) + fail (); + else if (!data[i].expected) + fail (); + /* fixme: Need to compare the result but also need to check + proper S-expression syntax. */ + } +#endif +} + + + +static void +test_agent_shadow_key (void) +{ +/* Create a shadow key from a public key. We use the shadow protocol + "t1-v1" and insert the S-expressionn SHADOW_INFO. The resulting + S-expression is returned in an allocated buffer RESULT will point + to. The input parameters are expected to be valid canonicalized + S-expressions */ +/* int */ +/* agent_shadow_key (const unsigned char *pubkey, */ +/* const unsigned char *shadow_info, */ +/* unsigned char **result) */ +} + + +static void +test_agent_get_shadow_info (void) +{ +/* Parse a canonical encoded shadowed key and return a pointer to the + inner list with the shadow_info */ +/* int */ +/* agent_get_shadow_info (const unsigned char *shadowkey, */ +/* unsigned char const **shadow_info) */ +} + + +static void +test_agent_protect_shared_secret (void) +{ + +} + + + + +int +main (int argc, char **argv) +{ + (void)argv; + + opt.verbose = argc - 1; /* We can do "./t-protect -v -v" */ + gcry_control (GCRYCTL_DISABLE_SECMEM); + + test_agent_protect (); + test_agent_unprotect (); + test_agent_private_key_type (); + test_make_shadow_info (); + test_agent_shadow_key (); + test_agent_get_shadow_info (); + test_agent_protect_shared_secret (); + + return 0; +} + +/* Stub function. */ +gpg_error_t +convert_from_openpgp_native (gcry_sexp_t s_pgp, const char *passphrase, + unsigned char **r_key) +{ + (void)s_pgp; + (void)passphrase; + (void)r_key; + return gpg_error (GPG_ERR_BUG); +} diff --git a/agent/trans.c b/agent/trans.c new file mode 100644 index 0000000..ff1a34e --- /dev/null +++ b/agent/trans.c @@ -0,0 +1,41 @@ +/* trans.c - translatable strings + * Copyright (C) 2001 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 <https://www.gnu.org/licenses/>. + */ + +/* To avoid any problems with the gettext implementation (there used + to be some vulnerabilities in the last years and the use of + external files is a minor security problem in itself), we use our + own simple translation stuff */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "agent.h" + +const char * +trans (const char *text) +{ + return text; +} diff --git a/agent/trustlist.c b/agent/trustlist.c new file mode 100644 index 0000000..086d8ae --- /dev/null +++ b/agent/trustlist.c @@ -0,0 +1,852 @@ +/* trustlist.c - Maintain the list of trusted keys + * Copyright (C) 2002, 2004, 2006, 2007, 2009, + * 2012 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <unistd.h> +#include <sys/stat.h> +#include <npth.h> + +#include "agent.h" +#include <assuan.h> /* fixme: need a way to avoid assuan calls here */ +#include "../common/i18n.h" + + +/* A structure to store the information from the trust file. */ +struct trustitem_s +{ + struct + { + int disabled:1; /* This entry is disabled. */ + int for_pgp:1; /* Set by '*' or 'P' as first flag. */ + int for_smime:1; /* Set by '*' or 'S' as first flag. */ + int relax:1; /* Relax checking of root certificate + constraints. */ + int cm:1; /* Use chain model for validation. */ + } flags; + unsigned char fpr[20]; /* The binary fingerprint. */ +}; +typedef struct trustitem_s trustitem_t; + +/* Malloced table and its allocated size with all trust items. */ +static trustitem_t *trusttable; +static size_t trusttablesize; +/* A mutex used to protect the table. */ +static npth_mutex_t trusttable_lock; + + +static const char headerblurb[] = +"# This is the list of trusted keys. Comment lines, like this one, as\n" +"# well as empty lines are ignored. Lines have a length limit but this\n" +"# is not a serious limitation as the format of the entries is fixed and\n" +"# checked by gpg-agent. A non-comment line starts with optional white\n" +"# space, followed by the SHA-1 fingerpint in hex, followed by a flag\n" +"# which may be one of 'P', 'S' or '*' and optionally followed by a list of\n" +"# other flags. The fingerprint may be prefixed with a '!' to mark the\n" +"# key as not trusted. You should give the gpg-agent a HUP or run the\n" +"# command \"gpgconf --reload gpg-agent\" after changing this file.\n" +"\n\n" +"# Include the default trust list\n" +"include-default\n" +"\n"; + + +/* This function must be called once to initialize this module. This + has to be done before a second thread is spawned. We can't do the + static initialization because Pth emulation code might not be able + to do a static init; in particular, it is not possible for W32. */ +void +initialize_module_trustlist (void) +{ + static int initialized; + int err; + + if (!initialized) + { + err = npth_mutex_init (&trusttable_lock, NULL); + if (err) + log_fatal ("failed to init mutex in %s: %s\n", __FILE__,strerror (err)); + initialized = 1; + } +} + + + + +static void +lock_trusttable (void) +{ + int err; + + err = npth_mutex_lock (&trusttable_lock); + if (err) + log_fatal ("failed to acquire mutex in %s: %s\n", __FILE__, strerror (err)); +} + + +static void +unlock_trusttable (void) +{ + int err; + + err = npth_mutex_unlock (&trusttable_lock); + if (err) + log_fatal ("failed to release mutex in %s: %s\n", __FILE__, strerror (err)); +} + + +/* Clear the trusttable. The caller needs to make sure that the + trusttable is locked. */ +static inline void +clear_trusttable (void) +{ + xfree (trusttable); + trusttable = NULL; + trusttablesize = 0; +} + + +/* Return the name of the system trustlist. Caller must free. */ +static char * +make_sys_trustlist_name (void) +{ + if (opt.sys_trustlist_name + && (strchr (opt.sys_trustlist_name, '/') + || strchr (opt.sys_trustlist_name, '\\') + || (*opt.sys_trustlist_name == '~' + && opt.sys_trustlist_name[1] == '/'))) + return make_absfilename (opt.sys_trustlist_name, NULL); + else + return make_filename (gnupg_sysconfdir (), + (opt.sys_trustlist_name ? + opt.sys_trustlist_name : "trustlist.txt"), + NULL); +} + + +static gpg_error_t +read_one_trustfile (const char *fname, int systrust, + trustitem_t **addr_of_table, + size_t *addr_of_tablesize, + int *addr_of_tableidx) +{ + gpg_error_t err = 0; + estream_t fp; + int n, c; + char *p, line[256]; + trustitem_t *table, *ti; + int tableidx; + size_t tablesize; + int lnr = 0; + + table = *addr_of_table; + tablesize = *addr_of_tablesize; + tableidx = *addr_of_tableidx; + + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err)); + goto leave; + } + + while (es_fgets (line, DIM(line)-1, fp)) + { + lnr++; + + n = strlen (line); + if (!n || line[n-1] != '\n') + { + /* Eat until end of line. */ + while ( (c=es_getc (fp)) != EOF && c != '\n') + ; + err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG + : GPG_ERR_INCOMPLETE_LINE); + log_error (_("file '%s', line %d: %s\n"), + fname, lnr, gpg_strerror (err)); + continue; + } + line[--n] = 0; /* Chop the LF. */ + if (n && line[n-1] == '\r') + line[--n] = 0; /* Chop an optional CR. */ + + /* Allow for empty lines and spaces */ + for (p=line; spacep (p); p++) + ; + if (!*p || *p == '#') + continue; + + if (!strncmp (p, "include-default", 15) + && (!p[15] || spacep (p+15))) + { + char *etcname; + gpg_error_t err2; + gpg_err_code_t ec; + + if (systrust) + { + log_error (_("statement \"%s\" ignored in '%s', line %d\n"), + "include-default", fname, lnr); + continue; + } + /* fixme: Should check for trailing garbage. */ + + etcname = make_sys_trustlist_name (); + if ( !strcmp (etcname, fname) ) /* Same file. */ + log_info (_("statement \"%s\" ignored in '%s', line %d\n"), + "include-default", fname, lnr); + else if ((ec=gnupg_access (etcname, F_OK)) && ec == GPG_ERR_ENOENT) + { + /* A non existent system trustlist is not an error. + Just print a note. */ + log_info (_("system trustlist '%s' not available\n"), etcname); + } + else + { + err2 = read_one_trustfile (etcname, 1, + &table, &tablesize, &tableidx); + if (err2) + err = err2; + } + xfree (etcname); + + continue; + } + + if (tableidx == tablesize) /* Need more space. */ + { + trustitem_t *tmp; + size_t tmplen; + + tmplen = tablesize + 20; + tmp = xtryrealloc (table, tmplen * sizeof *table); + if (!tmp) + { + err = gpg_error_from_syserror (); + goto leave; + } + table = tmp; + tablesize = tmplen; + } + + ti = table + tableidx; + + memset (&ti->flags, 0, sizeof ti->flags); + if (*p == '!') + { + ti->flags.disabled = 1; + p++; + while (spacep (p)) + p++; + } + + n = hexcolon2bin (p, ti->fpr, 20); + if (n < 0) + { + log_error (_("bad fingerprint in '%s', line %d\n"), fname, lnr); + err = gpg_error (GPG_ERR_BAD_DATA); + continue; + } + p += n; + for (; spacep (p); p++) + ; + + /* Process the first flag which needs to be the first for + backward compatibility. */ + if (!*p || *p == '*' ) + { + ti->flags.for_smime = 1; + ti->flags.for_pgp = 1; + } + else if ( *p == 'P' || *p == 'p') + { + ti->flags.for_pgp = 1; + } + else if ( *p == 'S' || *p == 's') + { + ti->flags.for_smime = 1; + } + else + { + log_error (_("invalid keyflag in '%s', line %d\n"), fname, lnr); + err = gpg_error (GPG_ERR_BAD_DATA); + continue; + } + p++; + if ( *p && !spacep (p) ) + { + log_error (_("invalid keyflag in '%s', line %d\n"), fname, lnr); + err = gpg_error (GPG_ERR_BAD_DATA); + continue; + } + + /* Now check for more key-value pairs of the form NAME[=VALUE]. */ + while (*p) + { + for (; spacep (p); p++) + ; + if (!*p) + break; + n = strcspn (p, "= \t"); + if (p[n] == '=') + { + log_error ("assigning a value to a flag is not yet supported; " + "in '%s', line %d\n", fname, lnr); + err = gpg_error (GPG_ERR_BAD_DATA); + p++; + } + else if (n == 5 && !memcmp (p, "relax", 5)) + ti->flags.relax = 1; + else if (n == 2 && !memcmp (p, "cm", 2)) + ti->flags.cm = 1; + else + log_error ("flag '%.*s' in '%s', line %d ignored\n", + n, p, fname, lnr); + p += n; + } + tableidx++; + } + if ( !err && !es_feof (fp) ) + { + err = gpg_error_from_syserror (); + log_error (_("error reading '%s', line %d: %s\n"), + fname, lnr, gpg_strerror (err)); + } + + leave: + es_fclose (fp); + *addr_of_table = table; + *addr_of_tablesize = tablesize; + *addr_of_tableidx = tableidx; + return err; +} + + +/* Read the trust files and update the global table on success. The + trusttable is assumed to be locked. */ +static gpg_error_t +read_trustfiles (void) +{ + gpg_error_t err; + trustitem_t *table, *ti; + int tableidx; + size_t tablesize; + char *fname; + int systrust = 0; + gpg_err_code_t ec; + + tablesize = 20; + table = xtrycalloc (tablesize, sizeof *table); + if (!table) + return gpg_error_from_syserror (); + tableidx = 0; + + if (opt.no_user_trustlist) + fname = NULL; + else + { + fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + xfree (table); + return err; + } + } + + if (!fname || (ec = gnupg_access (fname, F_OK))) + { + if (!fname) + ; /* --no-user-trustlist active. */ + else if ( ec == GPG_ERR_ENOENT ) + ; /* Silently ignore a non-existing trustfile. */ + else + { + err = gpg_error (ec); + log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err)); + } + xfree (fname); + fname = make_sys_trustlist_name (); + systrust = 1; + } + err = read_one_trustfile (fname, systrust, &table, &tablesize, &tableidx); + xfree (fname); + + if (err) + { + xfree (table); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + /* Take a missing trustlist as an empty one. */ + clear_trusttable (); + err = 0; + } + return err; + } + + /* Fixme: we should drop duplicates and sort the table. */ + ti = xtryrealloc (table, (tableidx?tableidx:1) * sizeof *table); + if (!ti) + { + err = gpg_error_from_syserror (); + xfree (table); + return err; + } + + /* Replace the trusttable. */ + xfree (trusttable); + trusttable = ti; + trusttablesize = tableidx; + return 0; +} + + +/* Check whether the given fpr is in our trustdb. We expect FPR to be + an all uppercase hexstring of 40 characters. If ALREADY_LOCKED is + true the function assumes that the trusttable is already locked. */ +static gpg_error_t +istrusted_internal (ctrl_t ctrl, const char *fpr, int *r_disabled, + int already_locked) +{ + gpg_error_t err = 0; + int locked = already_locked; + trustitem_t *ti; + size_t len; + unsigned char fprbin[20]; + + if (r_disabled) + *r_disabled = 0; + + if ( hexcolon2bin (fpr, fprbin, 20) < 0 ) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + if (!already_locked) + { + lock_trusttable (); + locked = 1; + } + + if (!trusttable) + { + err = read_trustfiles (); + if (err) + { + log_error (_("error reading list of trusted root certificates\n")); + goto leave; + } + } + + if (trusttable) + { + for (ti=trusttable, len = trusttablesize; len; ti++, len--) + if (!memcmp (ti->fpr, fprbin, 20)) + { + if (ti->flags.disabled && r_disabled) + *r_disabled = 1; + + /* Print status messages only if we have not been called + in a locked state. */ + if (already_locked) + ; + else if (ti->flags.relax) + { + unlock_trusttable (); + locked = 0; + err = agent_write_status (ctrl, "TRUSTLISTFLAG", "relax", NULL); + } + else if (ti->flags.cm) + { + unlock_trusttable (); + locked = 0; + err = agent_write_status (ctrl, "TRUSTLISTFLAG", "cm", NULL); + } + + if (!err) + err = ti->flags.disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0; + goto leave; + } + } + err = gpg_error (GPG_ERR_NOT_TRUSTED); + + leave: + if (locked && !already_locked) + unlock_trusttable (); + return err; +} + + +/* Check whether the given fpr is in our trustdb. We expect FPR to be + an all uppercase hexstring of 40 characters. */ +gpg_error_t +agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled) +{ + return istrusted_internal (ctrl, fpr, r_disabled, 0); +} + + +/* Write all trust entries to FP. */ +gpg_error_t +agent_listtrusted (void *assuan_context) +{ + trustitem_t *ti; + char key[51]; + gpg_error_t err; + size_t len; + + lock_trusttable (); + if (!trusttable) + { + err = read_trustfiles (); + if (err) + { + unlock_trusttable (); + log_error (_("error reading list of trusted root certificates\n")); + return err; + } + } + + if (trusttable) + { + for (ti=trusttable, len = trusttablesize; len; ti++, len--) + { + if (ti->flags.disabled) + continue; + bin2hex (ti->fpr, 20, key); + key[40] = ' '; + key[41] = ((ti->flags.for_smime && ti->flags.for_pgp)? '*' + : ti->flags.for_smime? 'S': ti->flags.for_pgp? 'P':' '); + key[42] = '\n'; + assuan_send_data (assuan_context, key, 43); + assuan_send_data (assuan_context, NULL, 0); /* flush */ + } + } + + unlock_trusttable (); + return 0; +} + + +/* Create a copy of string with colons inserted after each two bytes. + Caller needs to release the string. In case of a memory failure, + NULL is returned. */ +static char * +insert_colons (const char *string) +{ + char *buffer, *p; + size_t n = strlen (string); + size_t nnew = n + (n+1)/2; + + p = buffer = xtrymalloc ( nnew + 1 ); + if (!buffer) + return NULL; + while (*string) + { + *p++ = *string++; + if (*string) + { + *p++ = *string++; + if (*string) + *p++ = ':'; + } + } + *p = 0; + assert (strlen (buffer) <= nnew); + + return buffer; +} + + +/* To pretty print DNs in the Pinentry, we replace slashes by + REPLSTRING. The caller needs to free the returned string. NULL is + returned on error with ERRNO set. */ +static char * +reformat_name (const char *name, const char *replstring) +{ + const char *s; + char *newname; + char *d; + size_t count; + size_t replstringlen = strlen (replstring); + + /* If the name does not start with a slash it is not a preformatted + DN and thus we don't bother to reformat it. */ + if (*name != '/') + return xtrystrdup (name); + + /* Count the names. Note that a slash contained in a DN part is + expected to be C style escaped and thus the slashes we see here + are the actual part delimiters. */ + for (s=name+1, count=0; *s; s++) + if (*s == '/') + count++; + newname = xtrymalloc (strlen (name) + count*replstringlen + 1); + if (!newname) + return NULL; + for (s=name+1, d=newname; *s; s++) + if (*s == '/') + d = stpcpy (d, replstring); + else + *d++ = *s; + *d = 0; + return newname; +} + + +/* Insert the given fpr into our trustdb. We expect FPR to be an all + uppercase hexstring of 40 characters. FLAG is either 'P' or 'C'. + This function does first check whether that key has already been + put into the trustdb and returns success in this case. Before a + FPR actually gets inserted, the user is asked by means of the + Pinentry whether this is actual what he wants to do. */ +gpg_error_t +agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) +{ + gpg_error_t err = 0; + gpg_err_code_t ec; + char *desc; + char *fname; + estream_t fp; + char *fprformatted; + char *nameformatted; + int is_disabled; + int yes_i_trust; + + /* Check whether we are at all allowed to modify the trustlist. + This is useful so that the trustlist may be a symlink to a global + trustlist with only admin privileges to modify it. Of course + this is not a secure way of denying access, but it avoids the + usual clicking on an Okay button most users are used to. */ + fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL); + if (!fname) + return gpg_error_from_syserror (); + + if ((ec = access (fname, W_OK)) && ec != GPG_ERR_ENOENT) + { + xfree (fname); + return gpg_error (GPG_ERR_EPERM); + } + xfree (fname); + + if (!agent_istrusted (ctrl, fpr, &is_disabled)) + { + return 0; /* We already got this fingerprint. Silently return + success. */ + } + + /* This feature must explicitly been enabled. */ + if (!opt.allow_mark_trusted) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + if (is_disabled) + { + /* There is an disabled entry in the trustlist. Return an error + so that the user won't be asked again for that one. Changing + this flag with the integrated marktrusted feature is and will + not be made possible. */ + return gpg_error (GPG_ERR_NOT_TRUSTED); + } + + + /* Insert a new one. */ + nameformatted = reformat_name (name, "%0A "); + if (!nameformatted) + return gpg_error_from_syserror (); + + /* First a general question whether this is trusted. */ + desc = xtryasprintf ( + /* TRANSLATORS: This prompt is shown by the Pinentry + and has one special property: A "%%0A" is used by + Pinentry to insert a line break. The double + percent sign is actually needed because it is also + a printf format string. If you need to insert a + plain % sign, you need to encode it as "%%25". The + "%s" gets replaced by the name as stored in the + certificate. */ + L_("Do you ultimately trust%%0A" + " \"%s\"%%0A" + "to correctly certify user certificates?"), + nameformatted); + if (!desc) + { + xfree (nameformatted); + return out_of_core (); + } + err = agent_get_confirmation (ctrl, desc, L_("Yes"), L_("No"), 1); + xfree (desc); + if (!err) + yes_i_trust = 1; + else if (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED) + yes_i_trust = 0; + else + { + xfree (nameformatted); + return err; + } + + + fprformatted = insert_colons (fpr); + if (!fprformatted) + { + xfree (nameformatted); + return out_of_core (); + } + + /* If the user trusts this certificate he has to verify the + fingerprint of course. */ + if (yes_i_trust) + { + desc = xtryasprintf + ( + /* TRANSLATORS: This prompt is shown by the Pinentry and has + one special property: A "%%0A" is used by Pinentry to + insert a line break. The double percent sign is actually + needed because it is also a printf format string. If you + need to insert a plain % sign, you need to encode it as + "%%25". The second "%s" gets replaced by a hexdecimal + fingerprint string whereas the first one receives the name + as stored in the certificate. */ + L_("Please verify that the certificate identified as:%%0A" + " \"%s\"%%0A" + "has the fingerprint:%%0A" + " %s"), nameformatted, fprformatted); + if (!desc) + { + xfree (fprformatted); + xfree (nameformatted); + return out_of_core (); + } + + /* TRANSLATORS: "Correct" is the label of a button and intended + to be hit if the fingerprint matches the one of the CA. The + other button is "the default "Cancel" of the Pinentry. */ + err = agent_get_confirmation (ctrl, desc, L_("Correct"), L_("Wrong"), 1); + xfree (desc); + if (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED) + yes_i_trust = 0; + else if (err) + { + xfree (fprformatted); + xfree (nameformatted); + return err; + } + } + + + /* Now check again to avoid duplicates. We take the lock to make + sure that nobody else plays with our file and force a reread. */ + lock_trusttable (); + clear_trusttable (); + if (!istrusted_internal (ctrl, fpr, &is_disabled, 1) || is_disabled) + { + unlock_trusttable (); + xfree (fprformatted); + xfree (nameformatted); + return is_disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0; + } + + fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + unlock_trusttable (); + xfree (fprformatted); + xfree (nameformatted); + return err; + } + if ((ec = access (fname, F_OK)) && ec == GPG_ERR_ENOENT) + { + fp = es_fopen (fname, "wx,mode=-rw-r"); + if (!fp) + { + err = gpg_error (ec); + log_error ("can't create '%s': %s\n", fname, gpg_strerror (err)); + xfree (fname); + unlock_trusttable (); + xfree (fprformatted); + xfree (nameformatted); + return err; + } + es_fputs (headerblurb, fp); + es_fclose (fp); + } + fp = es_fopen (fname, "a+,mode=-rw-r"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("can't open '%s': %s\n", fname, gpg_strerror (err)); + xfree (fname); + unlock_trusttable (); + xfree (fprformatted); + xfree (nameformatted); + return err; + } + + /* Append the key. */ + es_fputs ("\n# ", fp); + xfree (nameformatted); + nameformatted = reformat_name (name, "\n# "); + if (!nameformatted || strchr (name, '\n')) + { + /* Note that there should never be a LF in NAME but we better + play safe and print a sanitized version in this case. */ + es_write_sanitized (fp, name, strlen (name), NULL, NULL); + } + else + es_fputs (nameformatted, fp); + es_fprintf (fp, "\n%s%s %c%s\n", yes_i_trust?"":"!", fprformatted, flag, + flag == 'S'? " relax":""); + if (es_ferror (fp)) + err = gpg_error_from_syserror (); + + if (es_fclose (fp)) + err = gpg_error_from_syserror (); + + clear_trusttable (); + xfree (fname); + unlock_trusttable (); + xfree (fprformatted); + xfree (nameformatted); + if (!err) + bump_key_eventcounter (); + return err; +} + + +/* This function may be called to force reloading of the + trustlist. */ +void +agent_reload_trustlist (void) +{ + /* All we need to do is to delete the trusttable. At the next + access it will get re-read. */ + lock_trusttable (); + clear_trusttable (); + unlock_trusttable (); + bump_key_eventcounter (); +} |