diff options
author | Anas Nashif <anas.nashif@intel.com> | 2013-03-27 09:15:23 -0700 |
---|---|---|
committer | Anas Nashif <anas.nashif@intel.com> | 2013-03-27 09:15:23 -0700 |
commit | 6576640b55777bd811a12a188b9b1f3c63653799 (patch) | |
tree | 7dab5ece3a5bf7ed238e8b0824194ce01b61121e /sm | |
download | gpg2-6576640b55777bd811a12a188b9b1f3c63653799.tar.gz gpg2-6576640b55777bd811a12a188b9b1f3c63653799.tar.bz2 gpg2-6576640b55777bd811a12a188b9b1f3c63653799.zip |
Imported Upstream version 2.0.19upstream/2.0.19
Diffstat (limited to 'sm')
-rw-r--r-- | sm/ChangeLog-2011 | 2705 | ||||
-rw-r--r-- | sm/Makefile.am | 71 | ||||
-rw-r--r-- | sm/Makefile.in | 711 | ||||
-rw-r--r-- | sm/base64.c | 733 | ||||
-rw-r--r-- | sm/call-agent.c | 1069 | ||||
-rw-r--r-- | sm/call-dirmngr.c | 1126 | ||||
-rw-r--r-- | sm/certchain.c | 2044 | ||||
-rw-r--r-- | sm/certcheck.c | 439 | ||||
-rw-r--r-- | sm/certdump.c | 974 | ||||
-rw-r--r-- | sm/certlist.c | 555 | ||||
-rw-r--r-- | sm/certreqgen-ui.c | 415 | ||||
-rw-r--r-- | sm/certreqgen.c | 884 | ||||
-rw-r--r-- | sm/decrypt.c | 586 | ||||
-rw-r--r-- | sm/delete.c | 182 | ||||
-rw-r--r-- | sm/encrypt.c | 513 | ||||
-rw-r--r-- | sm/export.c | 749 | ||||
-rw-r--r-- | sm/fingerprint.c | 347 | ||||
-rw-r--r-- | sm/gpgsm.c | 2181 | ||||
-rw-r--r-- | sm/gpgsm.h | 423 | ||||
-rw-r--r-- | sm/import.c | 807 | ||||
-rw-r--r-- | sm/keydb.c | 1535 | ||||
-rw-r--r-- | sm/keydb.h | 86 | ||||
-rw-r--r-- | sm/keylist.c | 1562 | ||||
-rw-r--r-- | sm/misc.c | 89 | ||||
-rw-r--r-- | sm/qualified.c | 321 | ||||
-rw-r--r-- | sm/server.c | 1464 | ||||
-rw-r--r-- | sm/sign.c | 780 | ||||
-rw-r--r-- | sm/verify.c | 660 |
28 files changed, 24011 insertions, 0 deletions
diff --git a/sm/ChangeLog-2011 b/sm/ChangeLog-2011 new file mode 100644 index 0000000..4efea96 --- /dev/null +++ b/sm/ChangeLog-2011 @@ -0,0 +1,2705 @@ +2011-12-02 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-08-04 Werner Koch <wk@g10code.com> + + * keydb.c (keydb_add_resource): Remove set but unused var + CREATED_FNAME. + * gpgsm.c (main): Remove set but used var FNAME. + +2011-07-21 Werner Koch <wk@g10code.com> + + * call-dirmngr.c (get_cached_cert, get_cached_cert_data_cb): New. + (gpgsm_dirmngr_isvalid): Try to get the only-valid-if-cert-valid + certificate from the dirmngr first. + +2010-09-16 Werner Koch <wk@g10code.com> + + * certchain.c (gpgsm_walk_cert_chain): Use GPG_ERR_MISSING_ISSUER_CERT. + (do_validate_chain): Ditto. + (gpgsm_basic_cert_check): Ditto. + * call-agent.c (learn_cb): Take care of new + GPG_ERR_MISSING_ISSUER_CERT. + * import.c (check_and_store): Ditto. + (check_and_store): Ditto. + +2010-05-12 Werner Koch <wk@g10code.com> + + * Makefile.am (gpgsm_LDADD): Include NETLIBS which is required for + Solaris. + +2010-03-12 Werner Koch <wk@g10code.com> + + * server.c (cmd_passwd): New. From trunk. + (register_commands): Register it. + +2010-02-11 Marcus Brinkmann <marcus@g10code.de> + + From trunk 2009-09-23, 2009-11-02, 2009-11-04, 2009-11-05, 2009-11-25, + 2009-12-08: + + * call-agent.c (membuf_data_cb, default_inq_cb) + (inq_ciphertext_cb, scd_serialno_status_cb) + (scd_keypairinfo_status_cb, istrusted_status_cb) + (learn_status_cb, learn_cb, keyinfo_status_cb): Return gpg_error_t. + * gpgsm.c (main): Update to new assuan API. + * server.c: Include "gpgsm.h" before <assuan.h> due to check for + GPG_ERR_SOURCE_DEFAULT and assuan.h now including gpg-error.h. + * server.c (reset_notify, input_notify, output_notify): Update to + new assuan interface. + (option_handler, cmd_recipient, cmd_signer, cmd_encrypt) + (cmd_decrypt, cmd_verify, cmd_sign, cmd_import, cmd_export) + (cmd_delkeys, cmd_message, cmd_listkeys, cmd_dumpkeys) + (cmd_listsecretkeys, cmd_dumpsecretkeys, cmd_genkey) + (cmd_getauditlog, cmd_getinfo): Return gpg_error_t instead of int. + (register_commands): Use assuan_handler_t. Same for member HANDLER + in table. Add NULL arg to assuan_register_command. Add help arg to + assuan_register_command. Provide help strings for all commands. + (gpgsm_server): Allocate assuan context before starting server. + Use assuan_fd_t and assuan_fdopen on fds. + * call-dirmngr.c (prepare_dirmngr): Check for CTX and error before + setting LDAPSERVER. + (start_dirmngr_ext): Allocate assuan context before starting + server. Update use ofassuan_pipe_connect and assuan_socket_connect. + Convert posix fd to assuan fd. + (inq_certificate, isvalid_status_cb, lookup_cb, lookup_status_cb) + (run_command_cb, run_command_inq_cb, run_command_status_cb): + Return gpg_error_t instead of int. + +2009-12-10 Werner Koch <wk@g10code.com> + + * gpgsm.c: Add option --ignore-cert-extension. + * gpgsm.h (opt): Add field IGNORED_CERT_EXTENSIONS. + * certchain.c (unknown_criticals): Handle ignored extensions, + +2009-12-03 Werner Koch <wk@g10code.com> + + From trunk: + + * verify.c (gpgsm_verify): Add audit info on hash algorithms. + * sign.c (gpgsm_sign): Add audit log calls. + (hash_data): Return an error indicator. + * decrypt.c (gpgsm_decrypt): Add audit log calls. + + * gpgsm.c: New option --html-audit-log. + + * certreqgen.c (proc_parameters): Change fallback key length to + 2048. + * gpgsm.c (main) <aGpgConfList>: Add key "default_pubkey_algo". + +2009-12-03 Werner Koch <wk@g10code.com> + + * gpgsm.c (set_debug): Allow for numerical debug levels. Print + active debug flags. + +2009-10-16 Werner Koch <wk@g10code.com> + + * gpgsm.c (DEFAULT_INCLUDE_CERTS): New. + (default_include_certs): Init to -2. + +2009-08-06 Werner Koch <wk@g10code.com> + + * sign.c (gpgsm_sign): Print INV_SNDR for a bad default key. + + * server.c (cmd_signer): Remove unneeded case for -1. Send + INV_SGNR. Use new map function. + (cmd_recipient): Use new map function. + * gpgsm.c (do_add_recipient): Use new map function for INV_RECP. + (main): Ditto. Also send INV_SGNR. + +2009-07-30 Werner Koch <wk@g10code.com> + + * call-agent.c (learn_cb): Do not store as ephemeral. + +2009-07-29 Marcus Brinkmann <marcus@g10code.com> + + * keylist.c (print_capabilities): Print a trailing colon. + +2009-07-23 Werner Koch <wk@g10code.com> + + * certchain.c (is_cert_still_valid): Emit AUDIT_CRL_CHECK. + +2009-07-07 Werner Koch <wk@g10code.com> + + * server.c (command_has_option): New. + (cmd_getinfo): Add subcommand "cmd_has_option". + (cmd_import): Implement option --re-import. + * import.c (gpgsm_import): Add arg reimport_mode. + (reimport_one): New. + + * gpgsm.h: Include session-env.h. + (opt): Add field SESSION_ENV. Remove obsolete fields. + * server.c (option_handler): Rewrite setting of option fields. + Replace strdup by xtrystrdup. + * gpgsm.c (set_opt_session_env): New. + (main): Use it for oDisplay, oTTYname, oTTYtype and oXauthority. + * call-agent.c (start_agent): Adjust start_new_gpg_agent for + changed args. + * misc.c (setup_pinentry_env): Use new session_env stuff. + +2009-07-02 Werner Koch <wk@g10code.com> + + * certreqgen-ui.c (gpgsm_gencertreq_tty): Allow using a key from a + card. + * call-agent.c (gpgsm_agent_scd_serialno) + (scd_serialno_status_cb, store_serialno): New. + (scd_keypairinfo_status_cb, gpgsm_agent_scd_keypairinfo): New. + +2009-07-01 Werner Koch <wk@g10code.com> + + * certreqgen-ui.c (check_keygrip): New. + (gpgsm_gencertreq_tty): Allow using an existing key. + + * gpgsm.c (open_es_fread): New. + (main) <aKeygen>: Implement --batch mode. + +2009-06-24 Werner Koch <wk@g10code.com> + + * call-dirmngr.c (pattern_from_strlist): Remove dead assignment of N. + * sign.c (gpgsm_sign): Remove dead assignment. + * certreqgen.c (create_request): Assign GPG_ERR_BUG to RC. + Reported by Fabian Keil. + +2009-05-27 Werner Koch <wk@g10code.com> + + * encrypt.c (encrypt_dek): Make use of make_canon_sexp. + +2009-05-18 Werner Koch <wk@g10code.com> + + * server.c (option_handler): New option "no-encrypt-to". + (cmd_encrypt): Make use of it. + + * gpgsm.c: Remove not implemented --verify-files. + +2009-04-02 Werner Koch <wk@g10code.com> + + * keylist.c (list_cert_std): Print card serial number. + +2009-04-01 Werner Koch <wk@g10code.com> + + * export.c (popen_protect_tool): Add command line option + --agent-program and pass flag bit 6. + * import.c (popen_protect_tool): Ditto. + +2009-03-26 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): s/def_digest_string/forced_digest_algo/ and + activate the --digest-algo option. + * gpgsm.h (struct opt): s/def_digest_algo/forced_digest_algo/. + * sign.c (gpgsm_sign): Implement --digest-algo. + + * sign.c (MAX_DIGEST_LEN): Change to 64. + + * call-agent.c (gpgsm_agent_marktrusted): Format the issuer name. + +2009-03-25 Werner Koch <wk@g10code.com> + + * decrypt.c (gpgsm_decrypt): Print ENC_TO and NO_SECKEY stati. + Fixes bug#1020. + * fingerprint.c (gpgsm_get_short_fingerprint): Add arg R_HIGH and + change all callers. + +2009-03-23 Werner Koch <wk@g10code.com> + + * delete.c (delete_one): Also delete ephemeral certificates if + specified uniquely. + +2009-03-20 Werner Koch <wk@g10code.com> + + * keylist.c (list_internal_keys): Set released cert to NULL. + + * call-agent.c (learn_status_cb): New. + (gpgsm_agent_learn): Use it. + (learn_cb): Send a progress for every certificate. + +2009-03-18 Werner Koch <wk@g10code.com> + + * gpgsm.h (struct opt): Move field WITH_EPHEMERAL_KEYS to struct + server_control_s. + * gpgsm.c (main): Change accordingly. + * keylist.c (list_internal_keys): Ditto. + * server.c (option_handler): Add "with-ephemeral-keys". + +2009-03-12 Werner Koch <wk@g10code.com> + + * certdump.c (gpgsm_dump_time): Remove. + * certdump.c, verify.c, certchain.c + * gpgsm.c: s/gpgsm_dump_time/dump_isotime/. + +2009-03-06 Werner Koch <wk@g10code.com> + + * call-agent.c (gpgsm_agent_keyinfo, keyinfo_status_cb): New. + * keylist.c (list_cert_colon): Print card S/N. + + * keylist.c (list_internal_keys): Always list ephemeral keys if + specified by keygrip or fingerprint. + (list_cert_raw): Always show ephemeral flag. + * export.c (gpgsm_export): Export ephemeral keys if specified by + keygrip. + +2009-02-09 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Change default cipher back to 3DES. + +2009-01-12 Werner Koch <wk@g10code.com> + + * keylist.c (print_utf8_extn_raw): Cast printf precision argument. + +2009-01-08 Werner Koch <wk@g10code.com> + + * fingerprint.c (gpgsm_get_keygrip_hexstring): Add error detection. + +2008-12-10 Werner Koch <wk@g10code.com> + + * gpgsm.c (our_cipher_test_algo): Use the GCRY constants as we now + require 1.4. + (our_md_test_algo): Ditto. Add SHA224. + (main) <aGpgConfList>: Update default cipher algo. + +2008-12-09 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Call i18n_init before init_common_subsystems. + +2008-12-05 Werner Koch <wk@g10code.com> + + * certreqgen.c (create_request): Provide a custom prompt for the + signing. + + * certdump.c (gpgsm_format_keydesc): Remove debug output. + (gpgsm_format_keydesc): Remove saving of errno as xfree is + supposed not to change it. Use the new percent_plus_escape + function which also fixes the issue that we did not escaped a + percent in the past. + +2008-11-18 Werner Koch <wk@g10code.com> + + * gpgsm.c (make_libversion): New. + (my_strusage): Use new function. + (build_lib_list): Remove. + +2008-11-13 Werner Koch <wk@g10code.com> + + * gpgsm.c: Remove all unused options. Use ARGPARSE macros. + +2008-10-28 Werner Koch <wk@g10code.com> + + * certdump.c (gpgsm_format_keydesc): Use xtryasprintf and xfree. + (gpgsm_es_print_name): Factor code out to ... + (gpgsm_es_print_name2): New function. + (gpgsm_format_name2, format_name_writer): Use estream so that it + works on all platforms. + (format_name_writer): Fix reallocation bug. + +2008-10-23 Werner Koch <wk@g10code.com> + + * import.c (popen_protect_tool): Add arg CTRL and assure that the + agent is running. Pass a value for CTRL from all caller. + * export.c (popen_protect_tool): Ditto. + +2008-10-21 Werner Koch <wk@g10code.com> + + * call-dirmngr.c (inq_certificate_parm_s): Add field CTRL. + (gpgsm_dirmngr_isvalid): Supply a value for that field. + (inq_certificate): Add inquiry ISTRUSTED. + + * call-agent.c (gpgsm_agent_istrusted): Add new optional arg + HEXFPR. Changed all callers. + +2008-10-20 Werner Koch <wk@g10code.com> + + * keydb.c (keydb_locate_writable): Mark unused arg. + (keydb_search_kid): Ditto. + (keydb_clear_some_cert_flags): Ditto. + * server.c (cmd_encrypt): Ditto. + (cmd_decrypt, cmd_verify, cmd_import, cmd_genkey): Ditto. + * call-agent.c (gpgsm_scd_pksign): Ditto. + * call-dirmngr.c (release_dirmngr, release_dirmngr2) + (run_command_cb): Ditto. + * certlist.c (gpgsm_add_cert_to_certlist): Ditto. + * certchain.c (find_up_dirmngr): Ditto. + * keylist.c (print_key_data): Ditto. + (list_cert_raw, list_cert_std): Ditto. + * qualified.c (gpgsm_is_in_qualified_list): Ditto. + + * gpgsm.c (set_binary) [!W32]: Mark unused arg. + +2008-10-17 Werner Koch <wk@g10code.com> + + * call-dirmngr.c (start_dirmngr, start_dirmngr2): Reset the lock + flag on error. + (release_dirmngr, release_dirmngr2): Replace asserts by error messages. + (gpgsm_dirmngr_lookup): Replace assert by fatal error message. + +2008-10-13 Werner Koch <wk@g10code.com> + + * gpgsm.c: Add alias --delete-keys. + +2008-09-30 Werner Koch <wk@g10code.com> + + * server.c (cmd_getinfo): New subcommand agent-check. + * call-agent.c (gpgsm_agent_send_nop): New. + +2008-09-29 Werner Koch <wk@g10code.com> + + * certcheck.c (MY_GCRY_PK_ECDSA): Remove. Change users to + GCRY_PK_ECDSA. + * gpgsm.c (MY_GCRY_PK_ECDSA): Ditto. + * sign.c (MY_GCRY_MD_SHA224): Remove change users to GCRY_MD_SHA224. + +2008-09-04 Werner Koch <wk@g10code.com> + + * certdump.c (gpgsm_format_keydesc): Work around a mingw32 bug. + +2008-09-03 Werner Koch <wk@g10code.com> + + * sign.c (MY_GCRY_MD_SHA224): New, so that we don't need libgcrypt + 1.2. + +2008-08-13 Werner Koch <wk@g10code.com> + + * keylist.c (list_cert_colon): Print 'f' for validated certs. + +2008-08-08 Marcus Brinkmann <marcus@g10code.de> + + * gpgsm.h (struct server_control_s): Remove member dirmngr_seen. + * call-dirmngr.c (dirmngr2_ctx, dirmngr_ctx_locked) + (dirmngr2_ctx_locked): New global variables. + (prepare_dirmngr): Don't check dirmngr_seen anymore. + (start_dirmngr): Move bunch of code to ... + (start_dirmngr_ext): ... this new function. + (release_dirmngr, start_dirmngr2, release_dirmngr2): New + functions. + (gpgsm_dirmngr_isvalid): Call release_dirmngr. + (gpgsm_dirmngr_lookup): Call release_dirmngr. If dirmngr_ctx is + locked, use dirmngr2_locked. + (gpgsm_dirmngr_run_command): Call release_dirmngr. + +2008-06-25 Werner Koch <wk@g10code.com> + + * sign.c (gpgsm_sign): Revamp the hash algorithm selection. + * gpgsm.h (struct certlist_s): Add field HASH_ALGO and HASH_ALGO_OID. + + * qualified.c (gpgsm_qualified_consent): Fix double free. + + * gpgsm.c (main): Change default cipher algo to AES. + + * keylist.c (print_utf8_extn_raw, print_utf8_extn): New. + (list_cert_raw, list_cert_std): Print the TeleSec restriction + extension. + +2008-06-23 Werner Koch <wk@g10code.com> + + * encrypt.c (encode_session_key): Replace xmalloc by xtrymalloc. + Use bin2hex instead of open coding the conversion. + (encrypt_dek): Init S_DATA. + +2008-06-13 Marcus Brinkmann <marcus@ulysses.g10code.com> + + * call-dirmngr.c (prepare_dirmngr): Fix error code to ignore. + +2008-06-12 Marcus Brinkmann <marcus@g10code.de> + + * gpgsm.h (struct keyserver_spec): New struct. + (opt): Add member keyserver. + * gpgsm.c (keyserver_list_free, parse_keyserver_line): New functions. + (main): Implement --keyserver option. + * call-dirmngr.c (prepare_dirmngr): Send LDAPSERVER commands. + +2008-05-20 Werner Koch <wk@g10code.com> + + * gpgsm.c (main) <aExportSecretKeyP12>: Pass FP and not stdout to + the export function. Reported by Marc Mutz. + +2008-05-06 Werner Koch <wk@g10code.com> + + * keylist.c (list_external_keys): Ignore NOT FOUND error code. + This is bug#907. + +2008-04-23 Werner Koch <wk@g10code.com> + + * certchain.c (find_up): Make correct C89 code. Declare variable + at the top of the block. Reported by Alain Guibert. + +2008-04-09 Werner Koch <wk@g10code.com> + + * verify.c (gpgsm_verify): Print the message hash values on error. + +2008-03-31 Werner Koch <wk@g10code.com> + + * call-dirmngr.c (start_dirmngr): Use log_info instead of + log_error when falling back to start dirmngr. + +2008-03-20 Werner Koch <wk@g10code.com> + + * certlist.c (gpgsm_add_to_certlist): Always save the first + subject and issuer. Initialize issuer with issuer and not with + subject. + (same_subject_issuer): Set issuer2 to issuer and not to subject. + +2008-03-17 Werner Koch <wk@g10code.com> + + * certdump.c (my_funopen_hook_size_t): New. + (format_name_writer): Use it. + +2008-03-13 Werner Koch <wk@g10code.com> + + * certdump.c (gpgsm_fpr_and_name_for_status): Fix signed/unsigned + char issue. + (gpgsm_format_keydesc): Remove superfluous test. Add expire date + to the prompt. + +2008-02-18 Werner Koch <wk@g10code.com> + + * certchain.c (gpgsm_is_root_cert): Factor code out to ... + (is_root_cert): New. Extend test for self-issued certificates + signed by other CAs. + (do_validate_chain, gpgsm_basic_cert_check) + (gpgsm_walk_cert_chain): Use it here. + + * gpgsm.c: Add option --no-common-certs-import. + + * certchain.c (find_up_dirmngr, find_up, do_validate_chain) + (check_cert_policy): Be more silent with --quiet. + + * gpgsm.c: Add option --disable-dirmngr. + * gpgsm.h (opt): Add field DISABLE_DIRMNGR. + * call-dirmngr.c (start_dirmngr): Implement option. + +2008-02-14 Werner Koch <wk@g10code.com> + + * server.c (option_handler): Add option allow-pinentry-notify. + (gpgsm_proxy_pinentry_notify): New. + * call-agent.c (default_inq_cb): New. + (gpgsm_agent_pksign, gpgsm_scd_pksign, gpgsm_agent_readkey) + (gpgsm_agent_istrusted, gpgsm_agent_marktrusted) + (gpgsm_agent_passwd, gpgsm_agent_get_confirmation): Call it. + (struct cipher_parm_s, struct genkey_parm_s): Add field CTRL. + (inq_ciphertext_cb): Test keyword and fallback to default_inq_cb. + (inq_genkey_parms): Ditto. + (start_agent): Tell agent to send us the pinentry notifications. + +2008-02-13 Werner Koch <wk@g10code.com> + + * call-dirmngr.c (gpgsm_dirmngr_lookup): Add arg CACHE_ONLY. + * keylist.c (list_external_keys): Pass false for new arg. + * certchain.c (find_up_dirmngr): New. + (find_up): Also try to read from the dirmngr cache. + (find_up, find_up_external, gpgsm_walk_cert_chain) + (gpgsm_basic_cert_check, allowed_ca): Add arg CTRL and changed all + callers. + * call-agent.c (struct learn_parm_s): Add field CTRL. + (gpgsm_agent_learn): Set it. + +2008-02-11 Werner Koch <wk@g10code.com> + + * server.c (cmd_getinfo): New. + (gpgsm_server): Register GETINFO. + +2008-01-29 Marcus Brinkmann <marcus@g10code.de> + + * keylist.c (list_internal_keys): New variable lastcert. Use it + to suppress duplicates which immediately follow each other. + +2008-01-27 Werner Koch <wk@g10code.com> + + * import.c (popen_protect_tool): Set bit 7 in the flags for + gnupg_spawn_process so that under W32 no window appears. + * export.c (popen_protect_tool): Ditto. + +2007-12-13 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Add option --extra-digest-algo. + * gpgsm.h (struct): Add EXTRA_DIGEST_ALGO. + * verify.c (gpgsm_verify): Use it. Use the hash algorithm from + the signature value. + +2007-12-11 Werner Koch <wk@g10code.com> + + * certchain.c (do_validate_chain): Log AUDIT_ROOT_TRUSTED. + + * server.c (cmd_sign, cmd_decrypt, cmd_encrypt): Start audit log. + (cmd_recipient): Start audit session. + + * gpgsm.c (main): Revamp creation of the audit log. + + * gpgsm.h (struct server_control_s): Add AGENT_SEEN and DIRMNGR_SEEN. + * call-agent.c (start_agent): Record an audit event. + * call-dirmngr.c (start_dirmngr): Ditto. Add new arg CTRL and pass + it from all callers. + (prepare_dirmngr): New helper for start_dirmngr. + + * encrypt.c (gpgsm_encrypt): Add calls to audit_log. + +2007-12-03 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Call gnupg_reopen_std. + +h2007-11-22 Werner Koch <wk@g10code.com> + + * server.c (cmd_getauditlog): New. + (register_commands): Register GETAUDITLOG. + +2007-11-19 Werner Koch <wk@g10code.com> + + * server.c (cmd_recipient, cmd_signer): Add error reason 11. + + * gpgsm.c (main): Print a warning if --audit-log is used. + +2007-11-15 Werner Koch <wk@g10code.com> + + * gpgsm.h (struct): Add XAUTHORITY and PINENTRY_USER_DATA. + * misc.c (setup_pinentry_env): Add XAUTHORITY and PINENTRY_USER_DATA. + * gpgsm.c (main): New option --xauthority. + * call-agent.c (start_agent): Adjust for changed start_new_gpg_agent. + * server.c (option_handler): Ad the new options. + +2007-11-07 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): New option --audit-log. + * server.c (option_handler): New option enable-audit-log. + (start_audit_session): New. + (cmd_verify): Create audit context. + (gpgsm_server): Release the context. + + * gpgsm.h (struct server_control_s): Add member AUDIT, include + audit.h. + * certdump.c (gpgsm_format_sn_issuer): New. + * verify.c (hash_data): Return an error code. + (gpgsm_verify): Add calls to audit_log. + + * gpgsm.c (get_status_string): Remove. + * gpgsm.h: Include status.h instead of errors.h. + +2007-10-19 Werner Koch <wk@g10code.com> + + * qualified.c (gpgsm_qualified_consent): Use i18N-swicth functions. + (gpgsm_not_qualified_warning): Ditto. + * certdump.c (gpgsm_format_keydesc): Ditto. + +2007-09-14 Werner Koch <wk@g10code.com> + + * gpgsm.c (build_lib_list): New. + (my_strusage): Print lib info. + +2007-08-24 Werner Koch <wk@g10code.com> + + * Makefile.am (common_libs): Swap libkeybox and jnlib. + +2007-08-23 Werner Koch <wk@g10code.com> + + * certlist.c (gpgsm_certs_identical_p): New. + (gpgsm_add_to_certlist): Ignore duplicate certificates in + ambigious name detection. + (gpgsm_find_cert): Ditto. + * export.c (gpgsm_p12_export): Ditto. + +2007-08-22 Werner Koch <wk@g10code.com> + + * certreqgen.c (create_request): Replace open coding by bin2hex. + + * certreqgen-ui.c (gpgsm_gencertreq_tty): Use es_fopenmem. + +2007-08-21 Werner Koch <wk@g10code.com> + + * import.c (parse_p12): Use gnupg_tmpfile. + * export.c (export_p12): Ditto. + +2007-08-20 Werner Koch <wk@g10code.com> + + * certreqgen.c (read_parameters): Change FP to an estream_t. + (gpgsm_genkey): Replace in_fd and in_stream by a estream_t. + * server.c (cmd_genkey): Adjust for that. + * certreqgen-ui.c (gpgsm_gencertreq_tty): Use es_open_memstream + instead of a temporary file. + +2007-08-14 Werner Koch <wk@g10code.com> + + * call-dirmngr.c (start_dirmngr): Use dirmngr_socket_name. change + the way infostr is xstrdupped. + + * gpgsm.c (main) [W32]: Make --prefer-system-dirmngr a dummy under + Windows. + +2007-08-13 Werner Koch <wk@g10code.com> + + * gpgsm.c (do_add_recipient): Add RECP_REQUIRED and make error + message depend on that. + (main): Add avriable RECP_REQUIRED, set ift for encryption + commands and pass it to do_add_recipient. + (our_pk_test_algo, our_cipher_test_algo, our_md_test_algo): Implement. + +2007-08-09 Werner Koch <wk@g10code.com> + + * gpgsm.c (main) [W32]: Enable CRL check by default. + (main): Update the default control structure after reading the + options. + (gpgsm_parse_validation_model, parse_validation_model): New. + (main): New option --validation-model. + * certchain.c (gpgsm_validate_chain): Implement this option. + * server.c (option_handler): Ditto. + + * certchain.c (is_cert_still_valid): Reformatted. Add arg + FORCE_OCSP. Changed callers to set this flag when using the chain + model. + +2007-08-08 Werner Koch <wk@g10code.com> + + * certdump.c (gpgsm_print_serial): Fixed brown paper bag style bugs + which prefixed the output with a 3A and cut it off at a 00. + + * keylist.c (list_cert_raw): Print the certificate ID first and + rename "Serial number" to "S/N". + (list_cert_std): Ditto. + +2007-08-07 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Allow a string for --faked-system-time. + +2007-08-06 Werner Koch <wk@g10code.com> + + Implementation of the chain model. + + * gpgsm.h (struct rootca_flags_s): Define new members VALID and + CHAIN_MODEL. + * call-agent.c (gpgsm_agent_istrusted): Mark ROOTCA_FLAGS valid. + (istrusted_status_cb): Set CHAIN_MODEL. + * certchain.c (gpgsm_validate_chain): Replace LM alias by LISTMODE + and FP by LISTFP. + (gpgsm_validate_chain): Factor some code out to ... + (check_validity_period, ask_marktrusted): .. new. + (check_validity_cm_basic, check_validity_cm_main): New. + (do_validate_chain): New with all code from gpgsm_validate_chain. + New arg ROOTCA_FLAGS. + (gpgsm_validate_chain): Provide ROOTCA_FLAGS and fallback to chain + model. Add RETFLAGS arg and changed all callers to pass NULL. Add + CHECKTIME arg and changed all callers to pass a nil value. + (has_validity_model_chain): New. + * verify.c (gpgsm_verify): Check for chain model and return as + part of the trust status. + + * gpgsm.h (VALIDATE_FLAG_NO_DIRMNGR): New. + (VALIDATE_FLAG_NO_DIRMNGR): New. + * call-dirmngr.c (gpgsm_dirmngr_isvalid): Use constant here. + +2007-08-03 Werner Koch <wk@g10code.com> + + * keylist.c (list_cert_colon): Avoid duplicate listing of kludge + uids. + + * verify.c (gpgsm_verify): Make STATUS_VERIFY return the hash and + pk algo. + * certcheck.c (gpgsm_check_cms_signature): Add arg R_PKALGO. + +2007-08-02 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Factored GC_OPT_FLAGS out to gc-opt-flags.h. + +2007-07-17 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Implement --default-key. + (main) <gpgconf-list>: Declare --default-key and --encrypt-to. + +2007-07-16 Werner Koch <wk@g10code.com> + + * server.c (cmd_message): Use gnupg_fd_t to avoid dependecy on + newer assuan versions. + +2007-07-12 Werner Koch <wk@g10code.com> + + * gpgsm.c (check_special_filename): Use translate_sys2libc_fd_int + when passing an int value. + * server.c (cmd_encrypt, cmd_decrypt, cmd_verify, cmd_import) + (cmd_export, cmd_message, cmd_genkey): Translate file descriptors. + +2007-07-05 Werner Koch <wk@g10code.com> + + * Makefile.am (common_libs): Changed order of libs. + +2007-07-04 Werner Koch <wk@g10code.com> + + * certchain.c (check_cert_policy): Remove extra checks for + GPG_ERR_NO_VALUE. They are not needed since libksba 1.0.1. + * keylist.c (print_capabilities, list_cert_raw, list_cert_std): Ditto. + * certlist.c (cert_usage_p, cert_usage_p): Ditto. + +2007-06-26 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Call gnupg_rl_initialize. + * Makefile.am (gpgsm_LDADD): Add LIBREADLINE and libgpgrl.a. + +2007-06-25 Werner Koch <wk@g10code.com> + + * gpgsm.c (check_special_filename): Use translate_sys2libc_fd and + add new arg FOR_WRITE. Change callers to pass new arg. + +2007-06-24 Werner Koch <wk@g10code.com> + + * gpgsm.c (open_es_fwrite): Avoid the dup by using the new + es_fdopen_nc(). + +2007-06-21 Werner Koch <wk@g10code.com> + + * certreqgen-ui.c: New. + * gpgsm.c (main): Let --gen-key call it. + * certreqgen.c (gpgsm_genkey): Add optional IN_STREAM arg and + adjusted caller. + + * gpgsm.h (ctrl_t): Remove. It is now declared in ../common/util.h. + + * call-agent.c (start_agent): Factored almost all code out to + ../common/asshelp.c. + +2007-06-20 Werner Koch <wk@g10code.com> + + * call-agent.c (start_agent) [W32]: Start the agent on the fly. + +2007-06-18 Marcus Brinkmann <marcus@g10code.de> + + * gpgsm.c (main): Percent escape output of --gpgconf-list. + +2007-06-14 Werner Koch <wk@g10code.com> + + * call-agent.c (start_agent): Use gnupg_module_name. + * call-dirmngr.c (start_dirmngr): Ditto. + * export.c (export_p12): Ditto. + * import.c (parse_p12): Ditto. + * gpgsm.c (run_protect_tool): Ditto. + +2007-06-12 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Replace some calls by init_common_subsystems. + (main): Use gnupg_datadir. + * qualified.c (read_list): Use gnupg-datadir. + +2007-06-11 Werner Koch <wk@g10code.com> + + * Makefile.am (common_libs): Use libcommaonstd macr. + + * gpgsm.c (main) [W32]: Call pth_init. + +2007-06-06 Werner Koch <wk@g10code.com> + + * qualified.c (gpgsm_not_qualified_warning) [!ENABLE_NLS]: Do not + define orig_codeset. + * certdump.c (gpgsm_format_keydesc) [!ENABLE_NLS]: Do not define + orig_codeset. + (format_name_writer): Define only if funopen et al is available. + + * gpgsm.c (i18n_init): Remove. + +2007-05-29 Werner Koch <wk@g10code.com> + + * export.c (gpgsm_p12_export): Print passphrase encoding info only + in PEM mode. + +2007-05-18 Marcus Brinkmann <marcus@g10code.de> + + * qualified.c (gpgsm_qualified_consent, + gpgsm_not_qualified_warning): Free ORIG_CODESET on error. + * certdump.c (gpgsm_format_keydesc): Likewise. + +2007-05-07 Werner Koch <wk@g10code.com> + + * certcheck.c (MY_GCRY_PK_ECDSA): New. + +2007-04-20 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Parameterize failed versions check messages. + +2007-04-19 Werner Koch <wk@g10code.com> + + * certcheck.c (do_encode_md): Add arg PKEY. Add support for DSA2 + and all ECDSA sizes. + (get_dsa_qbits): New. + (pk_algo_from_sexp): A key will never contain ecdsa as algorithm, + so remove that. + +2007-04-18 Werner Koch <wk@g10code.com> + + * certcheck.c (do_encode_md): Support 160 bit ECDSA. + +2007-04-13 Werner Koch <wk@g10code.com> + + * call-agent.c (start_agent): Don't use log_error when using the + fallback hack to start the agent. This is bug 782. + +2007-03-20 Werner Koch <wk@g10code.com> + + * fingerprint.c (gpgsm_get_fingerprint): Add caching. + (gpgsm_get_fingerprint_string): Use bin2hexcolon(). + (gpgsm_get_fingerprint_hexstring): Use bin2hex and allocate only + as much memory as required. + (gpgsm_get_keygrip_hexstring): Use bin2hex. + + * certchain.c (gpgsm_validate_chain): Keep track of the + certificate chain and reset the ephemeral flags. + * keydb.c (keydb_set_cert_flags): New args EPHEMERAL and MASK. + Changed caller to use a mask of ~0. Return a proper error code if + the certificate is not available. + + * gpgsm.c: Add option --p12-charset. + * gpgsm.h (struct opt): Add p12_charset. + * export.c (popen_protect_tool): Use new option. + +2007-03-19 Werner Koch <wk@g10code.com> + + Changes to let export and key listing use estream to help systems + without funopen. + + * keylist.c: Use estream in place of stdio functions. + * gpgsm.c (open_es_fwrite): New. + (main): Use it for the list commands. + * server.c (data_line_cookie_functions): New. + (data_line_cookie_write, data_line_cookie_close): New. + (do_listkeys): Use estream. + + * certdump.c (gpgsm_print_serial): Changed to use estream. + (gpgsm_print_time): Ditto. + (pretty_es_print_sexp): New. + (gpgsm_es_print_name): New. + (print_dn_part): New arg STREAM. Changed all callers. + (print_dn_parts): Ditto. + * certchain.c (gpgsm_validate_chain): Changed FP to type + estream_t. + (do_list, unknown_criticals, allowed_ca, check_cert_policy) + (is_cert_still_valid): Ditto. + + * export.c (gpgsm_export): New arg STREAM. + (do_putc, do_fputs): New. + (print_short_info): Allow printing to optional STREAM. + * server.c (cmd_export): Use stream. + * base64.c (do_putc, do_fputs): New. + (base64_writer_cb, base64_finish_write): Let them cope with an + alternate output function. + (plain_writer_cb): New. + (gpgsm_create_writer): New arg STREAM and call plain_writer_cb for + binary output to an estream. Changed call callers. + +2007-01-31 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Let --gen-key print a more informative error + message. + +2007-01-25 Werner Koch <wk@g10code.com> + + * Makefile.am (gpgsm_LDADD): Add LIBICONV. Noted by Billy Halsey. + +2007-01-05 Werner Koch <wk@g10code.com> + + * certchain.c (unknown_criticals): Add subjectAltName. + +2006-12-21 Werner Koch <wk@g10code.com> + + * gpgsm.c: Comment mtrace feature. + +2006-12-21 Marcus Brinkmann <marcus@g10code.de> + + * certchain.c (gpgsm_basic_cert_check): Release SUBJECT. + + * encrypt.c (encrypt_dek): Release S_CIPH. + +2006-12-20 Marcus Brinkmann <marcus@g10code.de> + + * server.c (gpgsm_server): Release CTRL->server_local. + + * base64.c: Add new members READER and WRITER in union U2. + (gpgsm_create_reader): Initialise CTX->u2.reader. + (gpgsm_destroy_reader): Invoke ksba_reader_release. Return early + if CTX is NULL. + (gpgsm_create_writer): Initialise CTX->u2.writer. + (gpgsm_destroy_writer): Invoke ksba_writer_release. Return early + if CTX is NULL. + +2006-12-18 Marcus Brinkmann <marcus@g10code.de> + + * fingerprint.c (gpgsm_get_fingerprint): Close MD. + +2006-11-24 Werner Koch <wk@g10code.com> + + * certdump.c (parse_dn_part): Take '#' as a special character only + at the beginning of a string. + +2006-11-21 Werner Koch <wk@g10code.com> + + * certdump.c (my_funopen_hook_ret_t): New. + (format_name_writer): Use it for the return value. + +2006-11-14 Werner Koch <wk@g10code.com> + + * server.c (skip_options): Skip leading spaces. + (has_option): Honor "--". + (cmd_export): Add option --data to do an inline export. Skip all + options. + + * certdump.c (gpgsm_fpr_and_name_for_status): New. + * verify.c (gpgsm_verify): Use it to print correct status messages. + +2006-11-11 Werner Koch <wk@g10code.com> + + * server.c (skip_options): New. + +2006-10-24 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (AM_CFLAGS): Add $(LIBASSUAN_CFLAGS). + +2006-10-23 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Remap common cipher algo names to their OIDs. + (main): New command --gpgconf-test. + +2006-10-20 Werner Koch <wk@g10code.com> + + * keydb.c (classify_user_id): Parse keygrip for the '&' identifier. + +2006-10-18 Werner Koch <wk@g10code.com> + + * keylist.c (list_cert_raw): Also test for GPG_ERR_NO_VALUE when + testing for GPG_ERR_NO_DATA. + * certlist.c (cert_usage_p, gpgsm_find_cert): Ditto. + * certchain.c (check_cert_policy): Ditto. + + * keylist.c (list_cert_std, list_cert_raw): Print "none" for no + chain length available. + +2006-10-17 Werner Koch <wk@g10code.com> + + * gpgsm.c: No need for pth.h. + (main): or to init it. It used to be hack for W32. + + * sign.c (gpgsm_get_default_cert): Changed to return only + certificates usable for signing. + +2006-10-16 Werner Koch <wk@g10code.com> + + * certchain.c (already_asked_marktrusted) + (set_already_asked_marktrusted): New. + (gpgsm_validate_chain) <not trusted>: Keep track of certificates + we already asked for. + +2006-10-11 Werner Koch <wk@g10code.com> + + * certreqgen.c (proc_parameters, create_request): Allow for + creation directly from a card. + * call-agent.c (gpgsm_agent_readkey): New arg FROMCARD. + (gpgsm_scd_pksign): New. + +2006-10-06 Werner Koch <wk@g10code.com> + + * Makefile.am (AM_CFLAGS): Use PTH version of libassuan. + (gpgsm_LDADD): Ditto. + +2006-10-05 Werner Koch <wk@g10code.com> + + * certcheck.c (do_encode_md): Check that the has algo is valid. + +2006-10-02 Marcus Brinkmann <marcus@g10code.de> + + * server.c (register_commands): New commands DUMPKEYS and + DUMPSECRETKEYS. + (cmd_dumpkeys, cmd_dumpsecretkeys): New functions. + (option_handler): Support with-key-data option. + +2006-09-26 Werner Koch <wk@g10code.com> + + * certchain.c (gpgsm_validate_chain): More changes for the relax + feature. Use certificate reference counting instead of the old + explicit tests. Added a missing free. + +2006-09-25 Werner Koch <wk@g10code.com> + + * gpgsm.h (struct rootca_flags_s): New. + * call-agent.c (istrusted_status_cb): New. + (gpgsm_agent_istrusted): New arg ROOTCA_FLAGS. + * keylist.c (list_cert_colon): Use dummy for new arg. + * certchain.c (gpgsm_validate_chain): Make use of the relax flag + for root certificates. + (unknown_criticals): Ignore a GPG_ERR_NO_VALUE. + +2006-09-20 Werner Koch <wk@g10code.com> + + * gpgsm.c: Add alias command --dump-cert. + + * Makefile.am: Changes to allow parallel make runs. + +2006-09-18 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Use this to import standard certificates. + * keydb.c (keydb_add_resource): New arg AUTO_CREATED. + +2006-09-14 Werner Koch <wk@g10code.com> + + Replaced all call gpg_error_from_errno(errno) by + gpg_error_from_syserror(). + +2006-09-13 Werner Koch <wk@g10code.com> + + * keylist.c (list_internal_keys): Print marker line to FP and not + to stdout. + + * gpgsm.c (main): All list key list commands now make ose of + --output. Cleaned up calls to list modes. New command + --dump-chain. Renamed --list-sigs to --list-chain and added an + alias for the old one. + + * server.c (cmd_message): Changed to use assuan_command_parse_fd. + (option_handler): New option list-to-output. + (do_listkeys): Use it. + +2006-09-06 Werner Koch <wk@g10code.com> + + * gpgsm.h (OUT_OF_CORE): Removed and changed all callers to + out_of_core. + (CTRL): Removed and changed everywhere to ctrl_t. + (CERTLIST): Ditto. + + Replaced all Assuan error codes by libgpg-error codes. Removed + all map_to_assuan_status and map_assuan_err. + + * gpgsm.c (main): Call assuan_set_assuan_err_source to have Assuan + switch to gpg-error codes. + * server.c (set_error): Adjusted. + +2006-08-29 Werner Koch <wk@g10code.com> + + * call-agent.c (gpgsm_agent_pkdecrypt): Allow decryption using + complete S-expressions as implemented by the current gpg-agent. + + * gpgsm.c (main): Implement --output for encrypt, decrypt, sign + and export. + +2006-07-03 Werner Koch <wk@g10code.com> + + * certreqgen.c (proc_parameters): Print the component label of a + faulty DN. + +2006-06-26 Werner Koch <wk@g10code.com> + + * certdump.c (gpgsm_cert_log_name): New. + * certchain.c (is_cert_still_valid): Log the name of the certificate. + +2006-06-20 Werner Koch <wk@g10code.com> + + * gpgsm.c (gpgsm_init_default_ctrl): Take care of the command line + option --include-certs. + + * keylist.c (list_cert_raw): Print the certid. + +2006-05-23 Werner Koch <wk@g10code.com> + + * keydb.c (hextobyte): Deleted as it is now defined in jnlib. + + * Makefile.am (gpgsm_LDADD): Include ZLIBS. + +2006-05-19 Marcus Brinkmann <marcus@g10code.de> + + * keydb.c (keydb_insert_cert): Do not lock here, but only check if + it is locked. + (keydb_store_cert): Lock here. + + * keydb.h (keydb_delete): Accept new argument UNLOCK. + * keydb.c (keydb_delete): Likewise. Only unlock if this is set. + * delete.c (delete_one): Add new argument to invocation of + keydb_delete. + +2006-05-15 Werner Koch <wk@g10code.com> + + * keylist.c (print_names_raw): Sanitize URI. + +2006-03-21 Werner Koch <wk@g10code.com> + + * certchain.c (get_regtp_ca_info): New. + (allowed_ca): Use it. + +2006-03-20 Werner Koch <wk@g10code.com> + + * qualified.c (gpgsm_is_in_qualified_list): New optional arg COUNTRY. + +2006-02-17 Werner Koch <wk@g10code.com> + + * call-dirmngr.c (start_dirmngr): Print name of dirmngr to be started. + +2005-11-23 Werner Koch <wk@g10code.com> + + * gpgsm.h: New member QUALSIG_APPROVAL. + * sign.c (gpgsm_sign): Print a warning if a certificate is not + qualified. + * qualified.c (gpgsm_qualified_consent): Include a note that this + is not approved software. + (gpgsm_not_qualified_warning): New. + * gpgsm.c (main): Prepared to print a note whether the software + has been approved. + +2005-11-13 Werner Koch <wk@g10code.com> + + * call-agent.c (gpgsm_agent_get_confirmation): New. + + * keylist.c (list_cert_std): Print qualified status. + * qualified.c: New. + * certchain.c (gpgsm_validate_chain): Check for qualified + certificates. + + * certchain.c (gpgsm_basic_cert_check): Release keydb handle when + no-chain-validation is used. + +2005-11-11 Werner Koch <wk@g10code.com> + + * keylist.c (print_capabilities): Print is_qualified status. + +2005-10-28 Werner Koch <wk@g10code.com> + + * certdump.c (pretty_print_sexp): New. + (gpgsm_print_name2): Use it here. This allows proper printing of + DNS names as used with server certificates. + +2005-10-10 Werner Koch <wk@g10code.com> + + * keylist.c: Add pkaAdress OID as reference. + +2005-10-08 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (gpgsm_LDADD): Add ../gl/libgnu.a after + ../common/libcommon.a. + +2005-09-13 Werner Koch <wk@g10code.com> + + * verify.c (gpgsm_verify): Print a note if the unknown algorithm + is MD2. + * sign.c (gpgsm_sign): Ditto. + * certcheck.c (gpgsm_check_cert_sig): Ditto. + +2005-09-08 Werner Koch <wk@g10code.com> + + * export.c (popen_protect_tool): Add option --have-cert. We + probably lost this option with 1.9.14 due to restructuring of + export.c. + +2005-07-21 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): New options --no-log-file and --debug-none. + + * certreqgen.c (get_parameter, get_parameter_value): Add SEQ arg + to allow enumeration. Changed all callers. + (create_request): Process DNS and URI parameters. + +2005-07-20 Werner Koch <wk@g10code.com> + + * keylist.c (email_kludge): Reworked. + + * certdump.c (gpgsm_print_serial, gpgsm_dump_serial): Cast printf + arg to unsigned. + * call-dirmngr.c (gpgsm_dirmngr_run_command): Ditto + +2005-07-19 Werner Koch <wk@g10code.com> + + * fingerprint.c (gpgsm_get_certid): Cast printf arg to unsigned. + Bug accidently introduced while solving the #$%^& gcc + signed/unsigned char* warnings. + +2005-06-15 Werner Koch <wk@g10code.com> + + * delete.c (delete_one): Changed FPR to unsigned. + * encrypt.c (encrypt_dek): Made ENCVAL unsigned. + (gpgsm_encrypt): Ditto. + * sign.c (gpgsm_sign): Made SIGVAL unsigned. + * base64.c (base64_reader_cb): Need to use some casting to get + around signed/unsigned char* warnings. + * certcheck.c (gpgsm_check_cms_signature): Ditto. + (gpgsm_create_cms_signature): Changed arg R_SIGVAL to unsigned char*. + (do_encode_md): Made NFRAME a size_t. + * certdump.c (gpgsm_print_serial): Fixed signed/unsigned warning. + (gpgsm_dump_serial): Ditto. + (gpgsm_format_serial): Ditto. + (gpgsm_dump_string): Ditto. + (gpgsm_dump_cert): Ditto. + (parse_dn_part): Ditto. + (gpgsm_print_name2): Ditto. + * keylist.c (email_kludge): Ditto. + * certreqgen.c (proc_parameters, create_request): Ditto. + (create_request): Ditto. + * call-agent.c (gpgsm_agent_pksign): Made arg R_BUF unsigned. + (struct cipher_parm_s): Made CIPHERTEXT unsigned. + (struct genkey_parm_s): Ditto. + * server.c (strcpy_escaped_plus): Made arg S signed char*. + * fingerprint.c (gpgsm_get_fingerprint): Made ARRAY unsigned. + (gpgsm_get_keygrip): Ditto. + * keydb.c (keydb_insert_cert): Made DIGEST unsigned. + (keydb_update_cert): Ditto. + (classify_user_id): Apply cast to signed/unsigned assignment. + (hextobyte): Ditto. + +2005-06-01 Werner Koch <wk@g10code.com> + + * misc.c: Include setenv.h. + +2005-04-21 Werner Koch <wk@g10code.com> + + * gpgsm.c: New options --{enable,disable}-trusted-cert-crl-check. + * certchain.c (gpgsm_validate_chain): Make use of it. + + * certchain.c (gpgsm_validate_chain): Check revocations even for + expired certificates. This is required because on signature + verification an expired key is fine whereas a revoked one is not. + +2005-04-20 Werner Koch <wk@g10code.com> + + * Makefile.am (AM_CFLAGS): Add PTH_CFLAGS as noted by several folks. + +2005-04-19 Werner Koch <wk@g10code.com> + + * certchain.c (check_cert_policy): Print the diagnostic for a open + failure of policies.txt only in verbose mode or when it is not + ENOENT. + +2005-04-17 Werner Koch <wk@g10code.com> + + * call-dirmngr.c (inq_certificate): Add new inquire SENDCERT_SKI. + * certlist.c (gpgsm_find_cert): Add new arg KEYID and implement + this filter. Changed all callers. + + * certchain.c (find_up_search_by_keyid): New helper. + (find_up): Also try using the AKI.keyIdentifier. + (find_up_external): Ditto. + +2005-04-15 Werner Koch <wk@g10code.com> + + * keylist.c (list_cert_raw): Print the subjectKeyIdentifier as + well as the keyIdentifier part of the authorityKeyIdentifier. + +2005-03-31 Werner Koch <wk@g10code.com> + + * call-dirmngr.c (start_dirmngr): Use PATHSEP_C instead of ':'. + * call-agent.c (start_agent): Ditto. + +2005-03-17 Werner Koch <wk@g10code.com> + + * certcheck.c: Fixed use of DBG_CRYPTO and DBG_X509. + + * certchain.c (gpgsm_basic_cert_check): Dump certificates after a + failed gcry_pk_verify. + (find_up): Do an external lookup also for an authorityKeyIdentifier + lookup. Factored external lookup code out to .. + (find_up_external): .. new. + +2005-03-03 Werner Koch <wk@g10code.com> + + * Makefile.am (gpgsm_LDADD): Added PTH_LIBS. Noted by Kazu Yamamoto. + +2005-01-13 Werner Koch <wk@g10code.com> + + * certreqgen.c (proc_parameters): Cast printf arg. + +2004-12-22 Werner Koch <wk@g10code.com> + + * gpgsm.c (set_binary): New. + (main, open_read, open_fwrite): Use it. + +2004-12-21 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Use default_homedir(). + (main) [W32]: Default to disabled CRL checks. + +2004-12-20 Werner Koch <wk@g10code.com> + + * call-agent.c (start_agent): Before starting a pipe server start + to connect to a server on the standard socket. Use PATHSEP + * call-dirmngr.c (start_dirmngr): Use PATHSEP. + + * import.c: Include unistd.h for dup and close. + +2004-12-18 Werner Koch <wk@g10code.com> + + * gpgsm.h (map_assuan_err): Define in terms of + map_assuan_err_with_source. + * call-agent.c (start_agent): Pass error source to + send_pinentry_environment. + +2004-12-17 Werner Koch <wk@g10code.com> + + * call-dirmngr.c (isvalid_status_cb, lookup_status_cb) + (run_command_status_cb): Return cancel status if gpgsm_status + returned an error. + + * server.c (gpgsm_status, gpgsm_status2) + (gpgsm_status_with_err_code): Return an error code. + (gpgsm_status2): Always call va_end(). + +2004-12-15 Werner Koch <wk@g10code.com> + + * call-dirmngr.c (lookup_status_cb): Send progress messages + upstream. + (isvalid_status_cb): Ditto. + (gpgsm_dirmngr_isvalid): Put CTRL into status CB parameters. + (gpgsm_dirmngr_run_command, run_command_status_cb): Pass CTRL to + status callback and handle PROGRESS. + + * misc.c (setup_pinentry_env) [W32]: Don't use it. + + * gpgsm.c (main) [W32]: Init Pth because we need it for the socket + operations and to resolve libassuan symbols. + (run_protect_tool) [W32]: Disable it. + + * Makefile.am (gpgsm_LDADD): Move LIBASSUAN_LIBS more to the end. + +2004-12-07 Werner Koch <wk@g10code.com> + + * Makefile.am (gpgsm_LDADD): Put libassuan before jnlib because + under W32 we need the w32 pth code from jnlib. + + * misc.c (setup_pinentry_env) [W32]: Disabled. + +2004-12-06 Werner Koch <wk@g10code.com> + + * gpgsm.c (run_protect_tool) [_WIN32]: Disabled. + + * import.c (popen_protect_tool): Simplified by making use of + gnupg_spawn_process. + (parse_p12): Likewise, using gnupg_wait_process. + * export.c (popen_protect_tool): Ditto. + (export_p12): Ditto. + + * keydb.c: Don't define DIRSEP_S here. + +2004-12-02 Werner Koch <wk@g10code.com> + + * certchain.c (gpgsm_basic_cert_check): Dump certs with bad + signature for debugging. + (gpgsm_validate_chain): Ditto. + +2004-11-29 Werner Koch <wk@g10code.com> + + * gpgsm.c (set_debug): Changed to use a globals DEBUG_LEVEL and + DEBUG_VALUE. + (main): Made DEBUG_LEVEL global and introduced DEBUG_VALUE. This + now allows to add debug flags on top of a debug-level setting. + +2004-11-23 Werner Koch <wk@g10code.com> + + * gpgsm.c: New option --prefer-system-dirmngr. + * call-dirmngr.c (start_dirmngr): Implement this option. + +2004-10-22 Werner Koch <wk@g10code.com> + + * certreqgen.c (gpgsm_genkey): Remove the NEW from the certificate + request PEM header. This is according to the Sphinx standard. + +2004-10-08 Moritz Schulte <moritz@g10code.com> + + * certchain.c (gpgsm_validate_chain): Do not use keydb_new() in + case the no_chain_validation-return-short-cut is used (fixes + memory leak). + +2004-10-04 Werner Koch <wk@g10code.com> + + * misc.c (setup_pinentry_env): Try hard to set a default for GPG_TTY. + +2004-09-30 Werner Koch <wk@g10code.com> + + * gpgsm.c (i18n_init): Always use LC_ALL. + + * certdump.c (gpgsm_format_name): Factored code out to .. + (gpgsm_format_name2): .. new. + (gpgsm_print_name): Factored code out to .. + (gpgsm_print_name2): .. new. + (print_dn_part): New arg TRANSLATE. Changed all callers. + (print_dn_parts): Ditto. + (gpgsm_format_keydesc): Do not translate the SUBJECT; we require + it to stay UTF-8 but we still want to filter out bad control + characters. + + * Makefile.am: Adjusted for gettext 0.14. + + * keylist.c (list_cert_colon): Make sure that the expired flag has + a higher precedence than the invalid flag. + +2004-09-29 Werner Koch <wk@g10code.com> + + * import.c (parse_p12): Write an error status line for bad + passphrases. Add new arg CTRL and changed caller. + * export.c (export_p12): Likewise. + +2004-09-14 Werner Koch <wk@g10code.com> + + * certchain.c (gpgsm_validate_chain): Give expired certificates a + higher error precedence and don't bother to check any CRL in that + case. + +2004-08-24 Werner Koch <wk@g10code.de> + + * certlist.c: Fixed typo in ocsp OID. + +2004-08-18 Werner Koch <wk@g10code.de> + + * certlist.c (gpgsm_cert_use_ocsp_p): New. + (cert_usage_p): Support it here. + * call-dirmngr.c (gpgsm_dirmngr_isvalid): Use it here. + +2004-08-17 Marcus Brinkmann <marcus@g10code.de> + + * import.c: Fix typo in last change. + +2004-08-17 Werner Koch <wk@g10code.de> + + * import.c (check_and_store): Do a full validation if + --with-validation is set. + + * certchain.c (gpgsm_basic_cert_check): Print more detailed error + messages. + + * certcheck.c (do_encode_md): Partly support DSA. Add new arg + PKALGO. Changed all callers to pass it. + (pk_algo_from_sexp): New. + +2004-08-16 Werner Koch <wk@g10code.de> + + * gpgsm.c: New option --fixed-passphrase. + * import.c (popen_protect_tool): Pass it to the protect-tool. + + * server.c (cmd_encrypt): Use DEFAULT_RECPLIST and not recplist + for encrypt-to keys. + +2004-08-06 Werner Koch <wk@g10code.com> + + * gpgsm.c: New option --with-ephemeral-keys. + * keylist.c (list_internal_keys): Set it here. + (list_cert_raw): And indicate those keys. Changed all our callers + to pass the new arg HD through. + +2004-07-23 Werner Koch <wk@g10code.de> + + * certreqgen.c (proc_parameters): Do not allow key length below + 1024. + +2004-07-22 Werner Koch <wk@g10code.de> + + * keylist.c (list_cert_raw): Print the keygrip. + +2004-07-20 Werner Koch <wk@gnupg.org> + + * certchain.c (gpgsm_validate_chain): The trust check didn't + worked anymore, probably due to the changes at 2003-03-04. Fixed. + +2004-06-06 Werner Koch <wk@gnupg.org> + + * certreqgen.c (get_parameter_uint, create_request): Create + an extension for key usage when requested. + +2004-05-12 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): Install emergency_cleanup also as an atexit + handler. + + * verify.c (gpgsm_verify): Removed the separate error code + handling for KSBA. We use shared error codes anyway. + + * export.c (export_p12): Removed debugging code. + + * encrypt.c (gpgsm_encrypt): Put the session key in to secure memory. + +2004-05-11 Werner Koch <wk@gnupg.org> + + * sign.c (gpgsm_sign): Include the error source in the final error + message. + * decrypt.c (gpgsm_decrypt): Ditto. + + * fingerprint.c (gpgsm_get_key_algo_info): New. + * sign.c (gpgsm_sign): Don't assume RSA in the status line. + * keylist.c (list_cert_colon): Really print the algorithm and key + length. + (list_cert_raw, list_cert_std): Ditto. + (list_cert_colon): Reorganized to be able to tell whether a root + certificate is trusted. + + * gpgsm.c: New option --debug-allow-core-dump. + + * gpgsm.h (opt): Add member CONFIG_FILENAME. + * gpgsm.c (main): Use it here instead of the local var. + + * server.c (gpgsm_server): Print some additional information with + the hello in verbose mode. + +2004-04-30 Werner Koch <wk@gnupg.org> + + * import.c (check_and_store): Do not update the stats for hidden + imports of issuer certs. + (popen_protect_tool): Request statusmessages from the protect-tool. + (parse_p12): Detect status messages. Add new arg STATS and update them. + (print_imported_summary): Include secret key stats. + +2004-04-28 Werner Koch <wk@gnupg.org> + + * gpgsm.c: New command --keydb-clear-some-cert-flags. + * keydb.c (keydb_clear_some_cert_flags): New. + (keydb_update_keyblock, keydb_set_flags): Change error code + CONFLICT to NOT_LOCKED. + +2004-04-26 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main) <gpgconf>: Do not use /dev/null as default config + filename. + + * call-agent.c (gpgsm_agent_pksign, gpgsm_agent_pkdecrypt) + (gpgsm_agent_genkey, gpgsm_agent_istrusted) + (gpgsm_agent_marktrusted, gpgsm_agent_havekey) + (gpgsm_agent_passwd): Add new arg CTRL and changed all callers. + (start_agent): New arg CTRL. Send progress item when starting a + new agent. + * sign.c (gpgsm_get_default_cert, get_default_signer): New arg + CTRL to be passed down to the agent function. + * decrypt.c (prepare_decryption): Ditto. + * certreqgen.c (proc_parameters, read_parameters): Ditto. + * certcheck.c (gpgsm_create_cms_signature): Ditto. + +2004-04-23 Werner Koch <wk@gnupg.org> + + * keydb.c (keydb_add_resource): Try to compress the file on init. + + * keylist.c (oidtranstbl): New. OIDs collected from several sources. + (print_name_raw, print_names_raw, list_cert_raw): New. + (gpgsm_list_keys): Check the dump mode and pass it down as + necessary. + +2004-04-22 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): New commands --dump-keys, --dump-external-keys, + --dump-secret-keys. + +2004-04-13 Werner Koch <wk@gnupg.org> + + * misc.c (setup_pinentry_env): New. + * import.c (popen_protect_tool): Call it. + * export.c (popen_protect_tool): Call it. + +2004-04-08 Werner Koch <wk@gnupg.org> + + * decrypt.c (gpgsm_decrypt): Return GPG_ERR_NO_DATA if it is not a + encrypted message. + +2004-04-07 Werner Koch <wk@gnupg.org> + + * gpgsm.c: New option --force-crl-refresh. + * call-dirmngr.c (gpgsm_dirmngr_isvalid): Pass option to dirmngr. + +2004-04-05 Werner Koch <wk@gnupg.org> + + * server.c (get_status_string): Add STATUS_NEWSIG. + * verify.c (gpgsm_verify): Print STATUS_NEWSIG for each signature. + + * certchain.c (gpgsm_validate_chain) <gpgsm_cert_use_cer_p>: Do + not just warn if a cert is not suitable; bail out immediately. + +2004-04-01 Werner Koch <wk@gnupg.org> + + * call-dirmngr.c (isvalid_status_cb): New. + (unhexify_fpr): New. Taken from ../g10/call-agent.c + (gpgsm_dirmngr_isvalid): Add new arg CTRL, changed caller to pass + it thru. Detect need to check the respondert cert and do that. + * certchain.c (gpgsm_validate_chain): Add new arg FLAGS. Changed + all callers. + +2004-03-24 Werner Koch <wk@gnupg.org> + + * sign.c (gpgsm_sign): Include a short list of capabilities. + +2004-03-17 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main) <gpgconf>: Fixed default value quoting. + +2004-03-16 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): Implemented --gpgconf-list. + +2004-03-15 Werner Koch <wk@gnupg.org> + + * keylist.c (list_cert_colon): Hack to set the expired flag. + +2004-03-09 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): Correctly intitialze USE_OCSP flag. + + * keydb.c (keydb_delete): s/GPG_ERR_CONFLICT/GPG_ERR_NOT_LOCKED/ + +2004-03-04 Werner Koch <wk@gnupg.org> + + * call-dirmngr.c (gpgsm_dirmngr_isvalid): New arg ISSUER_CERT. + + * certchain.c (is_cert_still_valid): New. Code moved from ... + (gpgsm_validate_chain): ... here because we now need to check at + two places and at a later stage, so that we can pass the issuer + cert down to the dirmngr. + +2004-03-03 Werner Koch <wk@gnupg.org> + + * call-agent.c (start_agent): Replaced pinentry setup code by a + call to a new common function. + + * certdump.c (gpgsm_format_keydesc): Make sure the string is + returned as utf-8. + + * export.c (gpgsm_export): Make sure that we don't export more + than one certificate. + +2004-03-02 Werner Koch <wk@gnupg.org> + + * export.c (create_duptable, destroy_duptable) + (insert_duptable): New. + (gpgsm_export): Avoid duplicates. + +2004-02-26 Werner Koch <wk@gnupg.org> + + * certchain.c (compare_certs): New. + (gpgsm_validate_chain): Fixed infinite certificate checks after + bad signatures. + +2004-02-24 Werner Koch <wk@gnupg.org> + + * keylist.c (list_cert_colon): Print the fingerprint as the + cert-id for root certificates. + +2004-02-21 Werner Koch <wk@gnupg.org> + + * keylist.c (list_internal_keys): Return error codes. + (list_external_keys, gpgsm_list_keys): Ditto. + * server.c (do_listkeys): Ditto. + + * gpgsm.c (main): Display a key description for --passwd. + * call-agent.c (gpgsm_agent_passwd): New arg DESC. + +2004-02-20 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): New option --debug-ignore-expiration. + * certchain.c (gpgsm_validate_chain): Use it here. + + * certlist.c (cert_usage_p): Apply extKeyUsage. + +2004-02-19 Werner Koch <wk@gnupg.org> + + * export.c (export_p12, popen_protect_tool) + (gpgsm_p12_export): New. + * gpgsm.c (main): New command --export-secret-key-p12. + +2004-02-18 Werner Koch <wk@gnupg.org> + + * gpgsm.c (set_debug): Set the new --debug-level flags. + (main): New option --gpgconf-list. + (main): Do not setup -u and -r keys when not required. + (main): Setup the used character set. + + * keydb.c (keydb_add_resource): Print a hint to start the + gpg-agent. + +2004-02-17 Werner Koch <wk@gnupg.org> + + * gpgsm.c: Fixed value parsing for --with-validation. + * call-agent.c (start_agent): Ignore an empty GPG_AGENT_INFO. + * call-dirmngr.c (start_dirmngr): Likewise for DIRMNGR_INFO. + + * gpgsm.c: New option --with-md5-fingerprint. + * keylist.c (list_cert_std): Print MD5 fpr. + + * gpgsm.c: New options --with-validation. + * server.c (option_handler): New option "with-validation". + * keylist.c (list_cert_std, list_internal_keys): New args CTRL and + WITH_VALIDATION. Changed callers to set it. + (list_external_cb, list_external_keys): Pass CTRL to the callback. + (list_cert_colon): Add arg CTRL. Check validation if requested. + * certchain.c (unknown_criticals, allowed_ca, check_cert_policy) + (gpgsm_validate_chain): New args LISTMODE and FP. + (do_list): New helper for info output. + (find_up): New arg FIND_NEXT. + (gpgsm_validate_chain): After a bad signature try again with other + CA certificates. + + * import.c (print_imported_status): New arg NEW_CERT. Print + additional STATUS_IMPORT_OK becuase that is what gpgme expects. + (check_and_store): Always call above function after import. + * server.c (get_status_string): Added STATUS_IMPORT_OK. + +2004-02-13 Werner Koch <wk@gnupg.org> + + * certcheck.c (gpgsm_create_cms_signature): Format a description + for use by the pinentry. + * decrypt.c (gpgsm_decrypt): Ditto. Free HEXKEYGRIP. + * certdump.c (format_name_cookie, format_name_writer) + (gpgsm_format_name): New. + (gpgsm_format_serial): New. + (gpgsm_format_keydesc): New. + * call-agent.c (gpgsm_agent_pksign): New arg DESC. + (gpgsm_agent_pkdecrypt): Ditto. + + * encrypt.c (init_dek): Check for too weak algorithms. + + * import.c (parse_p12, popen_protect_tool): New. + + * base64.c (gpgsm_create_reader): New arg ALLOW_MULTI_PEM. + Changed all callers. + (base64_reader_cb): Handle it here. + (gpgsm_reader_eof_seen): New. + (base64_reader_cb): Set a flag for EOF. + (simple_reader_cb): Ditto. + +2004-02-12 Werner Koch <wk@gnupg.org> + + * gpgsm.h, gpgsm.c: New option --protect-tool-program. + * gpgsm.c (run_protect_tool): Use it. + +2004-02-11 Werner Koch <wk@gnupg.org> + + * Makefile.am (AM_CPPFLAGS): Pass directory constants via -D; this + will allow to override directory names at make time. + +2004-02-02 Werner Koch <wk@gnupg.org> + + * import.c (check_and_store): Import certificates even with + missing issuer's cert. Fixed an "depending on the verbose + setting" bug. + + * certchain.c (gpgsm_validate_chain): Mark revoked certs in the + keybox. + + * keylist.c (list_cert_colon): New arg VALIDITY; use it to print a + revoked flag. + (list_internal_keys): Retrieve validity flag. + (list_external_cb): Pass 0 as validity flag. + * keydb.c (keydb_get_flags, keydb_set_flags): New. + (keydb_set_cert_flags): New. + (lock_all): Return a proper error code. + (keydb_lock): New. + (keydb_delete): Don't lock but check that it has been locked. + (keydb_update_keyblock): Ditto. + * delete.c (delete_one): Take a lock. + +2004-01-30 Werner Koch <wk@gnupg.org> + + * certchain.c (check_cert_policy): Fixed read error checking. + (check_cert_policy): With no critical policies issue only a + warning if the policy file does not exists. + + * sign.c (add_certificate_list): Decrement N for the first cert. + +2004-01-29 Werner Koch <wk@gnupg.org> + + * certdump.c (parse_dn_part): Map common OIDs to human readable + labels. Make sure that a value won't get truncated if it includes + a Nul. + +2004-01-28 Werner Koch <wk@gnupg.org> + + * certchain.c (gpgsm_validate_chain): Changed the message printed + for an untrusted root certificate. + +2004-01-27 Werner Koch <wk@gnupg.org> + + * certdump.c (parse_dn_part): Pretty print the nameDistinguisher OID. + (print_dn_part): Do not delimit multiple RDN by " + ". Handle + multi-valued RDNs in a special way, i.e. in the order specified by + the certificate. + (print_dn_parts): Simplified. + +2004-01-16 Werner Koch <wk@gnupg.org> + + * sign.c (gpgsm_sign): Print an error message on all failures. + * decrypt.c (gpgsm_decrypt): Ditto. + +2003-12-17 Werner Koch <wk@gnupg.org> + + * server.c (gpgsm_server): Add arg DEFAULT_RECPLIST. + (cmd_encrypt): Add all enrypt-to marked certs to the list. + * encrypt.c (gpgsm_encrypt): Check that real recipients are + available. + * gpgsm.c (main): Make the --encrypt-to and --no-encrypt-to + options work. Pass the list of recients to gpgsm_server. + * gpgsm.h (certlist_s): Add field IS_ENCRYPT_TO. + (opt): Add NO_ENCRYPT_TO. + * certlist.c (gpgsm_add_to_certlist): New arg IS_ENCRYPT_TO. + Changed all callers and ignore duplicate entries. + (is_cert_in_certlist): New. + (gpgsm_add_cert_to_certlist): New. + + * certdump.c (gpgsm_print_serial): Cleaned up cast use in strtoul. + (gpgsm_dump_serial): Ditto. + + * decrypt.c (gpgsm_decrypt): Replaced ERR by RC. + +2003-12-16 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): Set the prefixes for assuan logging. + + * sign.c (gpgsm_sign): Add validation checks for the default + certificate. + + * gpgsm.c: Add -k as alias for --list-keys and -K for + --list-secret-keys. + +2003-12-15 Werner Koch <wk@gnupg.org> + + * encrypt.c (init_dek): Use gry_create_nonce for the IV; there is + not need for real strong random here and it even better protect + the random bits used for the key. + +2003-12-01 Werner Koch <wk@gnupg.org> + + * gpgsm.c, gpgsm.h: New options --{enable,disable}-ocsp. + (gpgsm_init_default_ctrl): Set USE_OCSP to the default value. + * certchain.c (gpgsm_validate_chain): Handle USE_OCSP. + * call-dirmngr.c (gpgsm_dirmngr_isvalid): Add arg USE_OCSP and + proceed accordingly. + +2003-11-19 Werner Koch <wk@gnupg.org> + + * verify.c (gpgsm_verify): Use "0" instead of an empty string for + the VALIDSIG status. + +2003-11-18 Werner Koch <wk@gnupg.org> + + * verify.c (gpgsm_verify): Fixed for changes API of gcry_md_info. + + * certchain.c (unknown_criticals): Fixed an error code test. + +2003-11-12 Werner Koch <wk@gnupg.org> + + Adjusted for API changes in Libksba. + +2003-10-31 Werner Koch <wk@gnupg.org> + + * certchain.c (gpgsm_validate_chain): Changed to use ksba_isotime_t. + * verify.c (strtimestamp_r, gpgsm_verify): Ditto. + * sign.c (gpgsm_sign): Ditto. + * keylist.c (print_time, list_cert_std, list_cert_colon): Ditto. + * certdump.c (gpgsm_print_time, gpgsm_dump_time, gpgsm_dump_cert): + Ditto. + +2003-10-25 Werner Koch <wk@gnupg.org> + + * certreqgen.c (read_parameters): Fixed faulty of !spacep(). + +2003-08-20 Marcus Brinkmann <marcus@g10code.de> + + * encrypt.c (encode_session_key): Allocate enough space. Cast key + byte to unsigned char to prevent sign extension. + (encrypt_dek): Check return value before error. + +2003-08-14 Timo Schulz <twoaday@freakmail.de> + + * encrypt.c (encode_session_key): Use new Libgcrypt interface. + +2003-07-31 Werner Koch <wk@gnupg.org> + + * Makefile.am (gpgsm_LDADD): Added INTLLIBS. + +2003-07-29 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): Add secmem features and set the random seed file. + (gpgsm_exit): Update the random seed file and enable debug output. + +2003-07-27 Werner Koch <wk@gnupg.org> + + Adjusted for gcry_mpi_print and gcry_mpi_scan API change. + +2003-06-24 Werner Koch <wk@gnupg.org> + + * server.c (gpgsm_status_with_err_code): New. + * verify.c (gpgsm_verify): Use it here instead of the old + tokenizing version. + + * verify.c (strtimestamp): Renamed to strtimestamp_r + + Adjusted for changes in the libgcrypt API. Some more fixes for the + libgpg-error stuff. + +2003-06-04 Werner Koch <wk@gnupg.org> + + * call-agent.c (init_membuf,put_membuf,get_membuf): Removed. + Include new membuf header and changed used type. + + 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. + + * gpgsm.h: Include gpg-error.h . + * Makefile.am: Link with libgpg-error. + +2003-04-29 Werner Koch <wk@gnupg.org> + + * Makefile.am: Use libassuan. Don't override LDFLAGS anymore. + * server.c (register_commands): Adjust for new Assuan semantics. + +2002-12-03 Werner Koch <wk@gnupg.org> + + * call-agent.c (gpgsm_agent_passwd): New. + * gpgsm.c (main): New command --passwd and --call-protect-tool + (run_protect_tool): New. + +2002-11-25 Werner Koch <wk@gnupg.org> + + * verify.c (gpgsm_verify): Handle content-type attribute. + +2002-11-13 Werner Koch <wk@gnupg.org> + + * call-agent.c (start_agent): Try to use $GPG_TTY instead of + ttyname. Changed ttyname to test stdin becuase it can be assumed + that output redirection is more common that input redirection. + +2002-11-12 Werner Koch <wk@gnupg.org> + + * gpgsm.c: New command --call-dirmngr. + * call-dirmngr.c (gpgsm_dirmngr_run_command) + (run_command_inq_cb,run_command_cb) + (run_command_status_cb): New. + +2002-11-11 Werner Koch <wk@gnupg.org> + + * certcheck.c (gpgsm_check_cms_signature): Don't double free + s_sig but free s_pkey at leave. + +2002-11-10 Werner Koch <wk@gnupg.org> + + * gpgsm.c: Removed duplicate --list-secret-key entry. + +2002-09-19 Werner Koch <wk@gnupg.org> + + * certcheck.c (gpgsm_check_cert_sig): Add cert hash debugging. + + * certchain.c (find_up): Print info when the cert was not found + by the autorithyKeyIdentifier. + +2002-09-03 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): Disable the internal libgcrypt locking. + +2002-08-21 Werner Koch <wk@gnupg.org> + + * import.c (print_imported_summary): Cleaned up. Print new + not_imported value. + (check_and_store): Update non_imported counter. + (print_import_problem): New. + (check_and_store): Print error status message. + * server.c (get_status_string): Added STATUS_IMPORT_PROBLEM. + +2002-08-20 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): Use the log file only in server mode. + + * import.c (print_imported_summary): New. + (check_and_store): Update the counters, take new argument. + (import_one): Factored out core of gpgsm_import. + (gpgsm_import): Print counters. + (gpgsm_import_files): New. + * gpgsm.c (main): Use the new function for import. + +2002-08-19 Werner Koch <wk@gnupg.org> + + * decrypt.c (gpgsm_decrypt): Return a better error status token. + * verify.c (gpgsm_verify): Don't error on messages with no signing + time or no message digest. This is only the case for messages + without any signed attributes. + +2002-08-16 Werner Koch <wk@gnupg.org> + + * certpath.c: Renamed to .. + * certchain.c: this. Renamed all all other usages of "path" in the + context of certificates to "chain". + + * call-agent.c (learn_cb): Special treatment when the issuer + certificate is missing. + +2002-08-10 Werner Koch <wk@gnupg.org> + + * Makefile.am (INCLUDES): Add definition for localedir. + + * keylist.c (list_cert_colon): Print the short fingerprint in the + key ID field. + * fingerprint.c (gpgsm_get_short_fingerprint): New. + * verify.c (gpgsm_verify): Print more verbose info for a good + signature. + +2002-08-09 Werner Koch <wk@gnupg.org> + + * decrypt.c (prepare_decryption): Hack to detected already + unpkcsedone keys. + + * gpgsm.c (emergency_cleanup): New. + (main): Initialize the signal handler. + + * sign.c (gpgsm_sign): Reset the hash context for subsequent + signers and release it at the end. + +2002-08-05 Werner Koch <wk@gnupg.org> + + * server.c (cmd_signer): New command "SIGNER" + (register_commands): Register it. + (cmd_sign): Pass the signer list to gpgsm_sign. + * certlist.c (gpgsm_add_to_certlist): Add SECRET argument, check + for secret key if set and changed all callers. + * sign.c (gpgsm_sign): New argument SIGNERLIST and implemt + multiple signers. + * gpgsm.c (main): Support more than one -u. + + * server.c (cmd_recipient): Return reason code 1 for No_Public_Key + which is actually what gets returned from add_to_certlist. + +2002-07-26 Werner Koch <wk@gnupg.org> + + * certcheck.c (gpgsm_check_cert_sig): Implement proper cleanup. + (gpgsm_check_cms_signature): Ditto. + +2002-07-22 Werner Koch <wk@gnupg.org> + + * keydb.c (keydb_add_resource): Register a lock file. + (lock_all, unlock_all): Implemented. + + * delete.c: New. + * gpgsm.c: Made --delete-key work. + * server.c (cmd_delkeys): New. + (register_commands): New command DELKEYS. + + * decrypt.c (gpgsm_decrypt): Print a convenience note when RC2 is + used and a STATUS_ERROR with the algorithm oid. + +2002-07-03 Werner Koch <wk@gnupg.org> + + * server.c (gpgsm_status2): Insert a blank between all optional + arguments when using assuan. + * server.c (cmd_recipient): No more need for extra blank in constants. + * import.c (print_imported_status): Ditto. + * gpgsm.c (main): Ditto. + +2002-07-02 Werner Koch <wk@gnupg.org> + + * verify.c (gpgsm_verify): Extend the STATUS_BADSIG line with + the fingerprint. + + * certpath.c (check_cert_policy): Don't use log_error to print a + warning. + + * keydb.c (keydb_store_cert): Add optional ar EXISTED and changed + all callers. + * call-agent.c (learn_cb): Print info message only for real imports. + + * import.c (gpgsm_import): Moved duplicated code to ... + (check_and_store): new function. Added magic to import the entire + chain. Print status only for real imports and moved printing code + to .. + (print_imported_status): New. + + * call-dirmngr.c (gpgsm_dirmngr_isvalid): print status of dirmngr + call in very verbose mode. + + * gpgsm.c (main): Use the same error codes for STATUS_INV_RECP as + with the server mode. + +2002-06-29 Werner Koch <wk@gnupg.org> + + * gpgsm.c: New option --auto-issuer-key-retrieve. + * certpath.c (find_up): Try to retrieve an issuer key from an + external source and from the ephemeral key DB. + (find_up_store_certs_cb): New. + + * keydb.c (keydb_set_ephemeral): Does now return the old + state. Call the backend only when required. + + * call-dirmngr.c (start_dirmngr): Use GNUPG_DEFAULT_DIRMNGR. + (lookup_status_cb): Issue status only when CTRL is not NULL. + (gpgsm_dirmngr_lookup): Document that CTRL is optional. + + * call-agent.c (start_agent): Use GNUPG_DEFAULT_AGENT. + +2002-06-28 Werner Koch <wk@gnupg.org> + + * server.c (cmd_recipient): Add more reason codes. + +2002-06-27 Werner Koch <wk@gnupg.org> + + * certpath.c (gpgsm_basic_cert_check): Use + --debug-no-path-validation to also bypass this basic check. + + * gpgsm.c (main): Use GNUPG_DEFAULT_HOMEDIR constant. + + * call-agent.c (start_agent): Create and pass the list of FD to + keep in the child to assuan. + * call-dirmngr.c (start_dirmngr): Ditto. + +2002-06-26 Werner Koch <wk@gnupg.org> + + * import.c (gpgsm_import): Print an STATUS_IMPORTED. + + * gpgsm.c: --debug-no-path-validation does not take an argument. + +2002-06-25 Werner Koch <wk@gnupg.org> + + * certdump.c (print_dn_part): Always print a leading slash, + removed NEED_DELIM arg and changed caller. + + * export.c (gpgsm_export): Print LFs to FP and not stdout. + (print_short_info): Ditto. Make use of gpgsm_print_name. + + * server.c (cmd_export): Use output-fd instead of data lines; this + was actually the specified way. + +2002-06-24 Werner Koch <wk@gnupg.org> + + * gpgsm.c: Removed duped help entry for --list-keys. + + * gpgsm.c, gpgsm.h: New option --debug-no-path-validation. + + * certpath.c (gpgsm_validate_path): Use it here instead of the + debug flag hack. + + * certpath.c (check_cert_policy): Return No_Policy_Match if the + policy file could not be opened. + +2002-06-20 Werner Koch <wk@gnupg.org> + + * certlist.c (gpgsm_add_to_certlist): Fixed locating of a + certificate with the required key usage. + + * gpgsm.c (main): Fixed a segv when using --outfile without an + argument. + + * keylist.c (print_capabilities): Also check for non-repudiation + and data encipherment. + * certlist.c (cert_usage_p): Test for signing and encryption was + swapped. Add a case for certification usage, handle + non-repudiation and data encipherment. + (gpgsm_cert_use_cert_p): New. + (gpgsm_add_to_certlist): Added a CTRL argument and changed all + callers to pass it. + * certpath.c (gpgsm_validate_path): Use it here to print a status + message. Added a CTRL argument and changed all callers to pass it. + * decrypt.c (gpgsm_decrypt): Print a status message for wrong key + usage. + * verify.c (gpgsm_verify): Ditto. + * keydb.c (classify_user_id): Allow a colon delimited fingerprint. + +2002-06-19 Werner Koch <wk@gnupg.org> + + * call-agent.c (learn_cb): Use log_info instead of log_error on + successful import. + + * keydb.c (keydb_set_ephemeral): New. + (keydb_store_cert): New are ephemeral, changed all callers. + * keylist.c (list_external_cb): Store cert as ephemeral. + * export.c (gpgsm_export): Kludge to export epehmeral certificates. + + * gpgsm.c (main): New command --list-external-keys. + +2002-06-17 Werner Koch <wk@gnupg.org> + + * certreqgen.c (read_parameters): Improved error handling. + (gpgsm_genkey): Print error message. + +2002-06-13 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): New option --log-file. + +2002-06-12 Werner Koch <wk@gnupg.org> + + * call-dirmngr.c (lookup_status_cb): New. + (gpgsm_dirmngr_lookup): Use the status CB. Add new arg CTRL and + changed caller to pass it. + + * gpgsm.c (open_fwrite): New. + (main): Allow --output for --verify. + + * sign.c (hash_and_copy_data): New. + (gpgsm_sign): Implemented normal (non-detached) signatures. + * gpgsm.c (main): Ditto. + + * certpath.c (gpgsm_validate_path): Special error handling for + no policy match. + +2002-06-10 Werner Koch <wk@gnupg.org> + + * server.c (get_status_string): Add STATUS_ERROR. + + * certpath.c (gpgsm_validate_path): Tweaked the error checking to + return error codes in a more sensitive way. + * verify.c (gpgsm_verify): Send status TRUST_NEVER also for a bad + CA certificate and when the certificate has been revoked. Issue + TRUST_FULLY even when the cert has expired. Append an error token + to these status lines. Issue the new generic error status when a + cert was not found and when leaving the function. + +2002-06-04 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): New command --list-sigs + * keylist.c (list_cert_std): New. Use it whenever colon mode is + not used. + (list_cert_chain): New. + +2002-05-31 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): Don't print the "go ahead" message for an + invalid command. + +2002-05-23 Werner Koch <wk@gnupg.org> + + * import.c (gpgsm_import): Add error messages. + +2002-05-21 Werner Koch <wk@gnupg.org> + + * keylist.c (list_internal_keys): Renamed from gpgsm_list_keys. + (list_external_keys): New. + (gpgsm_list_keys): Dispatcher for above. + * call-dirmngr.c (lookup_cb,pattern_from_strlist) + (gpgsm_dirmngr_lookup): New. + * server.c (option_handler): Handle new option --list-mode. + (do_listkeys): Handle options and actually use the mode argument. + (get_status_string): New code TRUNCATED. + + * import.c (gpgsm_import): Try to identify the type of input and + handle certs-only messages. + +2002-05-14 Werner Koch <wk@gnupg.org> + + * gpgsm.c: New option --faked-system-time + * sign.c (gpgsm_sign): And use it here. + * certpath.c (gpgsm_validate_path): Ditto. + +2002-05-03 Werner Koch <wk@gnupg.org> + + * certpath.c (gpgsm_validate_path): Added EXPTIME arg and changed + all callers. + * verify.c (gpgsm_verify): Tweaked usage of log_debug and + log_error. Return EXPSIG status and add expiretime to VALIDSIG. + +2002-04-26 Werner Koch <wk@gnupg.org> + + * gpgsm.h (DBG_AGENT,DBG_AGENT_VALUE): Replaced by DBG_ASSUAN_*. + Changed all users. + + * call-agent.c (start_agent): Be more silent without -v. + * call-dirmngr.c (start_dirmngr): Ditto. + +2002-04-25 Werner Koch <wk@gnupg.org> + + * call-agent.c (start_agent): Make copies of old locales and check + for setlocale. + +2002-04-25 Marcus Brinkmann <marcus@g10code.de> + + * call-agent.c (start_agent): Fix error handling logic so the + locale is always correctly reset. + +2002-04-25 Marcus Brinkmann <marcus@g10code.de> + + * server.c (option_handler): Accept display, ttyname, ttytype, + lc_ctype and lc_messages options. + * gpgsm.c (main): Allocate memory for these options. + * gpgsm.h (struct opt): Make corresponding members non-const. + +2002-04-24 Marcus Brinkmann <marcus@g10code.de> + + * gpgsm.h (struct opt): New members display, ttyname, ttytype, + lc_ctype, lc_messages. + * gpgsm.c (enum cmd_and_opt_values): New members oDisplay, + oTTYname, oTTYtype, oLCctype, oLCmessages. + (opts): New entries for these options. + (main): Handle these new options. + * call-agent.c (start_agent): Set the various display and tty + parameter after resetting. + +2002-04-18 Werner Koch <wk@gnupg.org> + + * certreqgen.c (gpgsm_genkey): Write status output on success. + +2002-04-15 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): Check ksba version. + + * certpath.c (find_up): New to use the authorithKeyIdentifier. + Use it in all other functions to locate the signing cert.. + +2002-04-11 Werner Koch <wk@gnupg.org> + + * certlist.c (cert_usable_p): New. + (gpgsm_cert_use_sign_p,gpgsm_cert_use_encrypt_p): New. + (gpgsm_cert_use_verify_p,gpgsm_cert_use_decrypt_p): New. + (gpgsm_add_to_certlist): Check the key usage. + * sign.c (gpgsm_sign): Ditto. + * verify.c (gpgsm_verify): Print a message wehn an unsuitable + certificate was used. + * decrypt.c (gpgsm_decrypt): Ditto + * keylist.c (print_capabilities): Determine values from the cert. + +2002-03-28 Werner Koch <wk@gnupg.org> + + * keylist.c (list_cert_colon): Fixed listing of crt record; the + issuer is not at the right place. Print a chainingID. + * certpath.c (gpgsm_walk_cert_chain): Be a bit more silent on + common errors. + +2002-03-21 Werner Koch <wk@gnupg.org> + + * export.c: New. + * gpgsm.c: Add command --export. + * server.c (cmd_export): New. + +2002-03-13 Werner Koch <wk@gnupg.org> + + * decrypt.c (gpgsm_decrypt): Allow multiple recipients. + +2002-03-12 Werner Koch <wk@gnupg.org> + + * certpath.c (check_cert_policy): Print the policy list. + + * verify.c (gpgsm_verify): Detect certs-only message. + +2002-03-11 Werner Koch <wk@gnupg.org> + + * import.c (gpgsm_import): Print a notice about imported certificates + when in verbose mode. + + * gpgsm.c (main): Print INV_RECP status. + * server.c (cmd_recipient): Ditto. + + * server.c (gpgsm_status2): New. Allows for a list of strings. + (gpgsm_status): Divert to gpgsm_status2. + + * encrypt.c (gpgsm_encrypt): Don't use a default key when no + recipients are given. Print a NO_RECP status. + +2002-03-06 Werner Koch <wk@gnupg.org> + + * server.c (cmd_listkeys, cmd_listsecretkeys): Divert to + (do_listkeys): new. Add pattern parsing. + + * keylist.c (gpgsm_list_keys): Handle selection pattern. + + * gpgsm.c: New command --learn-card + * call-agent.c (learn_cb,gpgsm_agent_learn): New. + + * gpgsm.c (main): Print error messages for non-implemented commands. + + * base64.c (base64_reader_cb): Use case insensitive compare of the + Content-Type string to detect plain base-64. + +2002-03-05 Werner Koch <wk@gnupg.org> + + * gpgsm.c, gpgsm.h: Add local_user. + * sign.c (gpgsm_get_default_cert): New. + (get_default_signer): Use the new function if local_user is not + set otherwise used that value. + * encrypt.c (get_default_recipient): Removed. + (gpgsm_encrypt): Use gpgsm_get_default_cert. + + * verify.c (gpgsm_verify): Better error text for a bad signature + found by comparing the hashs. + +2002-02-27 Werner Koch <wk@gnupg.org> + + * call-dirmngr.c, call-agent.c: Add 2 more arguments to all uses + of assuan_transact. + +2002-02-25 Werner Koch <wk@gnupg.org> + + * server.c (option_handler): Allow to use -2 for "send all certs + except the root cert". + * sign.c (add_certificate_list): Implement it here. + * certpath.c (gpgsm_is_root_cert): New. + +2002-02-19 Werner Koch <wk@gnupg.org> + + * certpath.c (check_cert_policy): New. + (gpgsm_validate_path): And call it from here. + * gpgsm.c (main): New options --policy-file, + --disable-policy-checks and --enable-policy-checks. + * gpgsm.h (opt): Added policy_file, no_policy_checks. + +2002-02-18 Werner Koch <wk@gnupg.org> + + * certpath.c (gpgsm_validate_path): Ask the agent to add the + certificate into the trusted list. + * call-agent.c (gpgsm_agent_marktrusted): New. + +2002-02-07 Werner Koch <wk@gnupg.org> + + * certlist.c (gpgsm_add_to_certlist): Check that the specified + name identifies a certificate unambiguously. + (gpgsm_find_cert): Ditto. + + * server.c (cmd_listkeys): Check that the data stream is available. + (cmd_listsecretkeys): Ditto. + (has_option): New. + (cmd_sign): Fix ambiguousity in option recognition. + + * gpgsm.c (main): Enable --logger-fd. + + * encrypt.c (gpgsm_encrypt): Increased buffer size for better + performance. + + * call-agent.c (gpgsm_agent_pksign): Check the S-Exp received from + the agent. + + * keylist.c (list_cert_colon): Filter out control characters. + +2002-02-06 Werner Koch <wk@gnupg.org> + + * decrypt.c (gpgsm_decrypt): Bail out after an decryption error. + + * server.c (reset_notify): Close input and output FDs. + (cmd_encrypt,cmd_decrypt,cmd_verify,cmd_sign.cmd_import) + (cmd_genkey): Close the FDs and release the recipient list even in + the error case. + +2002-02-01 Marcus Brinkmann <marcus@g10code.de> + + * sign.c (gpgsm_sign): Do not release certificate twice. + +2002-01-29 Werner Koch <wk@gnupg.org> + + * call-agent.c (gpgsm_agent_havekey): New. + * keylist.c (list_cert_colon): New arg HAVE_SECRET, print "crs" + when we know that the secret key is available. + (gpgsm_list_keys): New arg MODE, check whether a secret key is + available. Changed all callers. + * gpgsm.c (main): New command --list-secret-keys. + * server.c (cmd_listsecretkeys): New. + (cmd_listkeys): Return secret keys with "crs" record. + +2002-01-28 Werner Koch <wk@gnupg.org> + + * certreqgen.c (create_request): Store the email address in the req. + +2002-01-25 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): Disable core dumps. + + * sign.c (add_certificate_list): New. + (gpgsm_sign): Add the certificates to the CMS object. + * certpath.c (gpgsm_walk_cert_chain): New. + * gpgsm.h (server_control_s): Add included_certs. + * gpgsm.c: Add option --include-certs. + (gpgsm_init_default_ctrl): New. + (main): Call it. + * server.c (gpgsm_server): Ditto. + (option_handler): Support --include-certs. + +2002-01-23 Werner Koch <wk@gnupg.org> + + * certpath.c (gpgsm_validate_path): Print the DN of a missing issuer. + * certdump.c (gpgsm_dump_string): New. + (print_dn): Replaced by above. + +2002-01-22 Werner Koch <wk@gnupg.org> + + * certpath.c (unknown_criticals): New. + (allowed_ca): New. + (gpgsm_validate_path): Check validity, CA attribute, path length + and unknown critical extensions. + +2002-01-21 Werner Koch <wk@gnupg.org> + + * gpgsm.c: Add option --enable-crl-checks. + + * call-agent.c (start_agent): Implemented socket based access. + * call-dirmngr.c (start_dirmngr): Ditto. + +2002-01-20 Werner Koch <wk@gnupg.org> + + * server.c (option_handler): New. + (gpgsm_server): Register it with assuan. + +2002-01-19 Werner Koch <wk@gnupg.org> + + * server.c (gpgsm_server): Use assuan_deinit_server and setup + assuan logging if enabled. + * call-agent.c (inq_ciphertext_cb): Don't show the session key in + an Assuan log file. + + * gpgsm.c (my_strusage): Take bugreport address from configure.ac + +2002-01-15 Werner Koch <wk@gnupg.org> + + * import.c (gpgsm_import): Just do a basic cert check before + storing it. + * certpath.c (gpgsm_basic_cert_check): New. + + * keydb.c (keydb_store_cert): New. + * import.c (store_cert): Removed and change all caller to use + the new function. + * verify.c (store_cert): Ditto. + + * certlist.c (gpgsm_add_to_certlist): Validate the path + + * certpath.c (gpgsm_validate_path): Check the trust list. + * call-agent.c (gpgsm_agent_istrusted): New. + +2002-01-14 Werner Koch <wk@gnupg.org> + + * call-dirmngr.c (inq_certificate): Changed for new interface semantic. + * certlist.c (gpgsm_find_cert): New. + +2002-01-13 Werner Koch <wk@gnupg.org> + + * fingerprint.c (gpgsm_get_certid): Print the serial and not the + hash after the dot. + +2002-01-11 Werner Koch <wk@gnupg.org> + + * call-dirmngr.c: New. + * certpath.c (gpgsm_validate_path): Check the CRL here. + * fingerprint.c (gpgsm_get_certid): New. + * gpgsm.c: New options --dirmngr-program and --disable-crl-checks. + +2002-01-10 Werner Koch <wk@gnupg.org> + + * base64.c (gpgsm_create_writer): Allow to set the object name + +2002-01-08 Werner Koch <wk@gnupg.org> + + * keydb.c (spacep): Removed because it is now in util.c + + * server.c (cmd_genkey): New. + * certreqgen.c: New. The parameter handling code has been taken + from gnupg/g10/keygen.c version 1.0.6. + * call-agent.c (gpgsm_agent_genkey): New. + +2002-01-02 Werner Koch <wk@gnupg.org> + + * server.c (rc_to_assuan_status): Removed and changed all callers + to use map_to_assuan_status. + +2001-12-20 Werner Koch <wk@gnupg.org> + + * verify.c (gpgsm_verify): Implemented non-detached signature + verification. Add OUT_FP arg, initialize a writer and changed all + callers. + * server.c (cmd_verify): Pass an out_fp if one has been set. + + * base64.c (base64_reader_cb): Try to detect an S/MIME body part. + + * certdump.c (print_sexp): Renamed to gpgsm_dump_serial, made + global. + (print_time): Renamed to gpgsm_dump_time, made global. + (gpgsm_dump_serial): Take a real S-Expression as argument and + print the first item. + * keylist.c (list_cert_colon): Ditto. + * keydb.c (keydb_search_issuer_sn): Ditto. + * decrypt.c (print_integer_sexp): Removed and made callers + use gpgsm_dump_serial. + * verify.c (print_time): Removed, made callers use gpgsm_dump_time. + +2001-12-19 Marcus Brinkmann <marcus@g10code.de> + + * call-agent.c (start_agent): Add new argument to assuan_pipe_connect. + +2001-12-18 Werner Koch <wk@gnupg.org> + + * verify.c (print_integer_sexp): Renamed from print_integer and + print the serial number according to the S-Exp rules. + * decrypt.c (print_integer_sexp): Ditto. + +2001-12-17 Werner Koch <wk@gnupg.org> + + * keylist.c (list_cert_colon): Changed for new return value of + get_serial. + * keydb.c (keydb_search_issuer_sn): Ditto. + * certcheck.c (gpgsm_check_cert_sig): Likewise for other S-Exp + returingin functions. + * fingerprint.c (gpgsm_get_keygrip): Ditto. + * encrypt.c (encrypt_dek): Ditto + * certcheck.c (gpgsm_check_cms_signature): Ditto + * decrypt.c (prepare_decryption): Ditto. + * call-agent.c (gpgsm_agent_pkdecrypt): Removed arg ciphertextlen, + use KsbaSexp type and calculate the length. + + * certdump.c (print_sexp): Remaned from print_integer, changed caller. + + * Makefile.am: Use the LIBGCRYPT and LIBKSBA variables. + + * fingerprint.c (gpgsm_get_keygrip): Use the new + gcry_pk_get_keygrip to calculate the grip - note the algorithm and + therefore the grip values changed. + +2001-12-15 Werner Koch <wk@gnupg.org> + + * certcheck.c (gpgsm_check_cms_signature): Removed the faked-key + kludge. + (gpgsm_create_cms_signature): Removed the commented fake key + code. This makes the function pretty simple. + + * gpgsm.c (main): Renamed the default key database to "keyring.kbx". + + * decrypt.c (gpgsm_decrypt): Write STATUS_DECRYPTION_*. + * sign.c (gpgsm_sign): Write a STATUS_SIG_CREATED. + +2001-12-14 Werner Koch <wk@gnupg.org> + + * keylist.c (list_cert_colon): Kludge to show an email address + encoded in the subject's DN. + + * verify.c (gpgsm_verify): Add hash debug helpers + * sign.c (gpgsm_sign): Ditto. + + * base64.c (base64_reader_cb): Reset the linelen when we need to + skip the line and adjusted test; I somehow forgot about DeMorgan. + + * server.c (cmd_encrypt,cmd_decrypt,cmd_sign,cmd_verify) + (cmd_import): Close the FDs on success. + (close_message_fd): New. + (input_notify): Setting autodetect_encoding to 0 after initializing + it to 0 is pretty pointless. Easy to fix. + + * gpgsm.c (main): New option --debug-wait n, so that it is + possible to attach gdb when used in server mode. + + * sign.c (get_default_signer): Use keydb_classify_name here. + +2001-12-14 Marcus Brinkmann <marcus@g10code.de> + + * call-agent.c (LINELENGTH): Removed. + (gpgsm_agent_pksign): Use ASSUAN_LINELENGTH, not LINELENGTH. + (gpgsm_agent_pkdecrypt): Likewise. + +2001-12-13 Werner Koch <wk@gnupg.org> + + * keylist.c (list_cert_colon): Print alternative names of subject + and a few other values. + +2001-12-12 Werner Koch <wk@gnupg.org> + + * gpgsm.c (main): New options --assume-{armor,base64,binary}. + * base64.c (base64_reader_cb): Fixed non-autodetection mode. + +2001-12-04 Werner Koch <wk@gnupg.org> + + * call-agent.c (read_from_agent): Check for inquire responses. + (request_reply): Handle them using a new callback arg, changed all + callers. + (gpgsm_agent_pkdecrypt): New. + +2001-11-27 Werner Koch <wk@gnupg.org> + + * base64.c: New. Changed all other functions to use this instead + of direct creation of ksba_reader/writer. + * gpgsm.c (main): Set ctrl.auto_encoding unless --no-armor is used. + +2001-11-26 Werner Koch <wk@gnupg.org> + + * gpgsm.c: New option --agent-program + * call-agent.c (start_agent): Allow to override the default path + to the agent. + + * keydb.c (keydb_add_resource): Create keybox + + * keylist.c (gpgsm_list_keys): Fixed non-server keylisting. + + * server.c (rc_to_assuan_status): New. Use it for all commands. + + + Copyright 2001, 2002, 2003, 2004, 2005, 2006, + 2007, 2008, 2009 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. diff --git a/sm/Makefile.am b/sm/Makefile.am new file mode 100644 index 0000000..d945d71 --- /dev/null +++ b/sm/Makefile.am @@ -0,0 +1,71 @@ +# Copyright (C) 2001, 2002, 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 <http://www.gnu.org/licenses/>. + +## Process this file with automake to produce Makefile.in + + +bin_PROGRAMS = gpgsm + +EXTRA_DIST = ChangeLog-2011 + +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) + +AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/common -I$(top_srcdir)/intl +include $(top_srcdir)/am/cmacros.am + + +gpgsm_SOURCES = \ + gpgsm.c gpgsm.h \ + misc.c \ + keydb.c keydb.h \ + server.c \ + call-agent.c \ + call-dirmngr.c \ + fingerprint.c \ + base64.c \ + certlist.c \ + certdump.c \ + certcheck.c \ + certchain.c \ + keylist.c \ + verify.c \ + sign.c \ + encrypt.c \ + decrypt.c \ + import.c \ + export.c \ + delete.c \ + certreqgen.c \ + certreqgen-ui.c \ + qualified.c + + +common_libs = $(libcommon) ../kbx/libkeybox.a ../jnlib/libjnlib.a \ + ../gl/libgnu.a + +gpgsm_LDADD = $(common_libs) ../common/libgpgrl.a $(NETLIBS) \ + $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) \ + $(GPG_ERROR_LIBS) $(LIBREADLINE) $(LIBINTL) $(ZLIBS) $(LIBICONV) + +# Make sure that all libs are build before we use them. This is +# important for things like make -j2. +$(PROGRAMS): $(common_libs) + + + + + diff --git a/sm/Makefile.in b/sm/Makefile.in new file mode 100644 index 0000000..b1cd1da --- /dev/null +++ b/sm/Makefile.in @@ -0,0 +1,711 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 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, 2002, 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 <http://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 <http://www.gnu.org/licenses/>. + +VPATH = @srcdir@ +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 = gpgsm$(EXEEXT) +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in \ + $(top_srcdir)/am/cmacros.am +@HAVE_DOSISH_SYSTEM_FALSE@am__append_1 = -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@\"" + + +# 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_2 = -DGNUPG_DEFAULT_AGENT="\"@GNUPG_AGENT_PGM@\"" +@GNUPG_PINENTRY_PGM_TRUE@am__append_3 = -DGNUPG_DEFAULT_PINENTRY="\"@GNUPG_PINENTRY_PGM@\"" +@GNUPG_SCDAEMON_PGM_TRUE@am__append_4 = -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\"" +@GNUPG_DIRMNGR_PGM_TRUE@am__append_5 = -DGNUPG_DEFAULT_DIRMNGR="\"@GNUPG_DIRMNGR_PGM@\"" +@GNUPG_PROTECT_TOOL_PGM_TRUE@am__append_6 = -DGNUPG_DEFAULT_PROTECT_TOOL="\"@GNUPG_PROTECT_TOOL_PGM@\"" +subdir = sm +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/gl/m4/absolute-header.m4 \ + $(top_srcdir)/gl/m4/alloca.m4 $(top_srcdir)/gl/m4/allocsa.m4 \ + $(top_srcdir)/gl/m4/eealloc.m4 \ + $(top_srcdir)/gl/m4/gnulib-comp.m4 \ + $(top_srcdir)/gl/m4/gnulib-tool.m4 \ + $(top_srcdir)/gl/m4/mkdtemp.m4 $(top_srcdir)/gl/m4/setenv.m4 \ + $(top_srcdir)/gl/m4/stdint.m4 $(top_srcdir)/gl/m4/strpbrk.m4 \ + $(top_srcdir)/gl/m4/unistd_h.m4 $(top_srcdir)/m4/autobuild.m4 \ + $(top_srcdir)/m4/codeset.m4 $(top_srcdir)/m4/estream.m4 \ + $(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/gnupg-pth.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/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \ + $(top_srcdir)/m4/longdouble.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \ + $(top_srcdir)/m4/readline.m4 $(top_srcdir)/m4/size_max.m4 \ + $(top_srcdir)/m4/socklen.m4 $(top_srcdir)/m4/sys_socket_h.m4 \ + $(top_srcdir)/m4/tar-ustar.m4 $(top_srcdir)/m4/xsize.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/scripts/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_gpgsm_OBJECTS = gpgsm.$(OBJEXT) misc.$(OBJEXT) keydb.$(OBJEXT) \ + server.$(OBJEXT) call-agent.$(OBJEXT) call-dirmngr.$(OBJEXT) \ + fingerprint.$(OBJEXT) base64.$(OBJEXT) certlist.$(OBJEXT) \ + certdump.$(OBJEXT) certcheck.$(OBJEXT) certchain.$(OBJEXT) \ + keylist.$(OBJEXT) verify.$(OBJEXT) sign.$(OBJEXT) \ + encrypt.$(OBJEXT) decrypt.$(OBJEXT) import.$(OBJEXT) \ + export.$(OBJEXT) delete.$(OBJEXT) certreqgen.$(OBJEXT) \ + certreqgen-ui.$(OBJEXT) qualified.$(OBJEXT) +gpgsm_OBJECTS = $(am_gpgsm_OBJECTS) +am__DEPENDENCIES_1 = +gpgsm_DEPENDENCIES = $(common_libs) ../common/libgpgrl.a \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/scripts/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(gpgsm_SOURCES) +DIST_SOURCES = $(gpgsm_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ABSOLUTE_STDINT_H = @ABSOLUTE_STDINT_H@ +ACLOCAL = @ACLOCAL@ +ADNSLIBS = @ADNSLIBS@ +ALLOCA = @ALLOCA@ +ALLOCA_H = @ALLOCA_H@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BITSIZEOF_PTRDIFF_T = @BITSIZEOF_PTRDIFF_T@ +BITSIZEOF_SIG_ATOMIC_T = @BITSIZEOF_SIG_ATOMIC_T@ +BITSIZEOF_SIZE_T = @BITSIZEOF_SIZE_T@ +BITSIZEOF_WCHAR_T = @BITSIZEOF_WCHAR_T@ +BITSIZEOF_WINT_T = @BITSIZEOF_WINT_T@ +BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@ +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@ +EXEEXT = @EXEEXT@ +FAQPROG = @FAQPROG@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUPG_AGENT_PGM = @GNUPG_AGENT_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_CURL = @GPGKEYS_CURL@ +GPGKEYS_FINGER = @GPGKEYS_FINGER@ +GPGKEYS_HKP = @GPGKEYS_HKP@ +GPGKEYS_KDNS = @GPGKEYS_KDNS@ +GPGKEYS_LDAP = @GPGKEYS_LDAP@ +GPGKEYS_MAILTO = @GPGKEYS_MAILTO@ +GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@ +GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@ +GPG_ERROR_LIBS = @GPG_ERROR_LIBS@ +GREP = @GREP@ +HAVE_INTTYPES_H = @HAVE_INTTYPES_H@ +HAVE_LONG_LONG_INT = @HAVE_LONG_LONG_INT@ +HAVE_SIGNED_SIG_ATOMIC_T = @HAVE_SIGNED_SIG_ATOMIC_T@ +HAVE_SIGNED_WCHAR_T = @HAVE_SIGNED_WCHAR_T@ +HAVE_SIGNED_WINT_T = @HAVE_SIGNED_WINT_T@ +HAVE_STDINT_H = @HAVE_STDINT_H@ +HAVE_SYS_BITYPES_H = @HAVE_SYS_BITYPES_H@ +HAVE_SYS_INTTYPES_H = @HAVE_SYS_INTTYPES_H@ +HAVE_SYS_TYPES_H = @HAVE_SYS_TYPES_H@ +HAVE_UNSIGNED_LONG_LONG_INT = @HAVE_UNSIGNED_LONG_LONG_INT@ +HAVE_WCHAR_H = @HAVE_WCHAR_H@ +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@ +LDAPLIBS = @LDAPLIBS@ +LDAP_CPPFLAGS = @LDAP_CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@ +LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@ +LIBASSUAN_LIBS = @LIBASSUAN_LIBS@ +LIBCURL = @LIBCURL@ +LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBGNU_LIBDEPS = @LIBGNU_LIBDEPS@ +LIBGNU_LTLIBDEPS = @LIBGNU_LTLIBDEPS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBOBJS = @LIBOBJS@ +LIBREADLINE = @LIBREADLINE@ +LIBS = @LIBS@ +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@ +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@ +POSUB = @POSUB@ +PTH_CFLAGS = @PTH_CFLAGS@ +PTH_CONFIG = @PTH_CONFIG@ +PTH_LIBS = @PTH_LIBS@ +PTRDIFF_T_SUFFIX = @PTRDIFF_T_SUFFIX@ +RANLIB = @RANLIB@ +SENDMAIL = @SENDMAIL@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SHRED = @SHRED@ +SIG_ATOMIC_T_SUFFIX = @SIG_ATOMIC_T_SUFFIX@ +SIZE_T_SUFFIX = @SIZE_T_SUFFIX@ +STDINT_H = @STDINT_H@ +STRIP = @STRIP@ +SYS_SOCKET_H = @SYS_SOCKET_H@ +TAR = @TAR@ +UNISTD_H = @UNISTD_H@ +USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +W32SOCKLIBS = @W32SOCKLIBS@ +WCHAR_T_SUFFIX = @WCHAR_T_SUFFIX@ +WINDRES = @WINDRES@ +WINT_T_SUFFIX = @WINT_T_SUFFIX@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +ZLIBS = @ZLIBS@ +_libcurl_config = @_libcurl_config@ +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@ +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 +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) +AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/common \ + -I$(top_srcdir)/intl -DLOCALEDIR=\"$(localedir)\" \ + $(am__append_1) $(am__append_2) $(am__append_3) \ + $(am__append_4) $(am__append_5) $(am__append_6) + +# Convenience macros +libcommon = ../common/libcommon.a +libcommonpth = ../common/libcommonpth.a +gpgsm_SOURCES = \ + gpgsm.c gpgsm.h \ + misc.c \ + keydb.c keydb.h \ + server.c \ + call-agent.c \ + call-dirmngr.c \ + fingerprint.c \ + base64.c \ + certlist.c \ + certdump.c \ + certcheck.c \ + certchain.c \ + keylist.c \ + verify.c \ + sign.c \ + encrypt.c \ + decrypt.c \ + import.c \ + export.c \ + delete.c \ + certreqgen.c \ + certreqgen-ui.c \ + qualified.c + +common_libs = $(libcommon) ../kbx/libkeybox.a ../jnlib/libjnlib.a \ + ../gl/libgnu.a + +gpgsm_LDADD = $(common_libs) ../common/libgpgrl.a $(NETLIBS) \ + $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) \ + $(GPG_ERROR_LIBS) $(LIBREADLINE) $(LIBINTL) $(ZLIBS) $(LIBICONV) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(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 sm/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu sm/Makefile +.PRECIOUS: 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__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(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): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + 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) +gpgsm$(EXEEXT): $(gpgsm_OBJECTS) $(gpgsm_DEPENDENCIES) + @rm -f gpgsm$(EXEEXT) + $(LINK) $(gpgsm_OBJECTS) $(gpgsm_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base64.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/call-agent.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/call-dirmngr.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certchain.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certcheck.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certdump.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certlist.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certreqgen-ui.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certreqgen.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/decrypt.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/delete.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/encrypt.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/export.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fingerprint.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgsm.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/import.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keydb.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keylist.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/qualified.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sign.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/verify.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + 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 +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + 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" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(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 +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; 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: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +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 mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -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-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 -rf ./$(DEPDIR) + -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 + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags 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-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 uninstall \ + uninstall-am uninstall-binPROGRAMS + + +# Make sure that all libs are build before we use them. This is +# important for things like make -j2. +$(PROGRAMS): $(common_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/sm/base64.c b/sm/base64.c new file mode 100644 index 0000000..b0c8dc8 --- /dev/null +++ b/sm/base64.c @@ -0,0 +1,733 @@ +/* base64.c + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" + + +#include <ksba.h> + +#include "i18n.h" + +#ifdef HAVE_DOSISH_SYSTEM + #define LF "\r\n" +#else + #define LF "\n" +#endif + +/* data used by the reader callbacks */ +struct reader_cb_parm_s { + FILE *fp; + + unsigned char line[1024]; + int linelen; + int readpos; + int have_lf; + unsigned long line_counter; + + int allow_multi_pem; /* Allow processing of multiple PEM objects. */ + int autodetect; /* Try to detect the input encoding. */ + int assume_pem; /* Assume input encoding is PEM. */ + int assume_base64; /* Assume input is base64 encoded. */ + + int identified; + int is_pem; + int is_base64; + int stop_seen; + int might_be_smime; + + int eof_seen; + + struct { + int idx; + unsigned char val; + int stop_seen; + } base64; +}; + +/* data used by the writer callbacks */ +struct writer_cb_parm_s { + FILE *fp; /* FP is only used if STREAM is NULL. */ + estream_t stream; /* Alternative output if not NULL. */ + + const char *pem_name; + + int wrote_begin; + int did_finish; + + struct { + int idx; + int quad_count; + unsigned char radbuf[4]; + } base64; + +}; + + +/* context for this module's functions */ +struct base64_context_s { + union { + struct reader_cb_parm_s rparm; + struct writer_cb_parm_s wparm; + } u; + + union { + ksba_reader_t reader; + ksba_writer_t writer; + } u2; +}; + + +/* The base-64 character list */ +static char bintoasc[64] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; +/* The reverse base-64 list */ +static unsigned char asctobin[256] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, + 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff +}; + + +static int +has_only_base64 (const unsigned char *line, int linelen) +{ + if (linelen < 20) + return 0; + for (; linelen; line++, linelen--) + { + if (*line == '\n' || (linelen > 1 && *line == '\r' && line[1] == '\n')) + break; + if ( !strchr (bintoasc, *line) ) + return 0; + } + return 1; /* yes */ +} + +static int +is_empty_line (const unsigned char *line, int linelen) +{ + if (linelen >= 2 && *line == '\r' && line[1] == '\n') + return 1; + if (linelen >= 1 && *line == '\n') + return 1; + return 0; +} + + +static int +base64_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) +{ + struct reader_cb_parm_s *parm = cb_value; + size_t n; + int c, c2; + + *nread = 0; + if (!buffer) + return -1; /* not supported */ + + next: + if (!parm->linelen) + { + /* read an entire line or up to the size of the buffer */ + parm->line_counter++; + parm->have_lf = 0; + for (n=0; n < DIM(parm->line);) + { + c = getc (parm->fp); + if (c == EOF) + { + parm->eof_seen = 1; + if (ferror (parm->fp)) + return -1; + break; + } + parm->line[n++] = c; + if (c == '\n') + { + parm->have_lf = 1; + /* Fixme: we need to skip overlong lines while detecting + the dashed lines */ + break; + } + } + parm->linelen = n; + if (!n) + return -1; /* eof */ + parm->readpos = 0; + } + + if (!parm->identified) + { + if (!parm->autodetect) + { + if (parm->assume_pem) + { + /* wait for the header line */ + parm->linelen = parm->readpos = 0; + if (!parm->have_lf + || strncmp ((char*)parm->line, "-----BEGIN ", 11) + || !strncmp ((char*)parm->line+11, "PGP ", 4)) + goto next; + parm->is_pem = 1; + } + else if (parm->assume_base64) + parm->is_base64 = 1; + } + else if (parm->line_counter == 1 && !parm->have_lf) + { + /* first line too long - assume DER encoding */ + parm->is_pem = 0; + } + else if (parm->line_counter == 1 && parm->linelen && *parm->line == 0x30) + { + /* the very first byte does pretty much look like a SEQUENCE tag*/ + parm->is_pem = 0; + } + else if ( parm->have_lf + && !strncmp ((char*)parm->line, "-----BEGIN ", 11) + && strncmp ((char *)parm->line+11, "PGP ", 4) ) + { + /* Fixme: we must only compare if the line really starts at + the beginning */ + parm->is_pem = 1; + parm->linelen = parm->readpos = 0; + } + else if ( parm->have_lf && parm->line_counter == 1 + && parm->linelen >= 13 + && !ascii_memcasecmp (parm->line, "Content-Type:", 13)) + { /* might be a S/MIME body */ + parm->might_be_smime = 1; + parm->linelen = parm->readpos = 0; + goto next; + } + else if (parm->might_be_smime == 1 + && is_empty_line (parm->line, parm->linelen)) + { + parm->might_be_smime = 2; + parm->linelen = parm->readpos = 0; + goto next; + } + else if (parm->might_be_smime == 2) + { + parm->might_be_smime = 0; + if ( !has_only_base64 (parm->line, parm->linelen)) + { + parm->linelen = parm->readpos = 0; + goto next; + } + parm->is_pem = 1; + } + else + { + parm->linelen = parm->readpos = 0; + goto next; + } + parm->identified = 1; + parm->base64.stop_seen = 0; + parm->base64.idx = 0; + } + + + n = 0; + if (parm->is_pem || parm->is_base64) + { + if (parm->is_pem && parm->have_lf + && !strncmp ((char*)parm->line, "-----END ", 9)) + { + parm->identified = 0; + parm->linelen = parm->readpos = 0; + + /* If the caller want to read multiple PEM objects from one + file, we have to reset our internal state and return a + EOF immediately. The caller is the expected to use + ksba_reader_clear to clear the EOF condition and continue + to read. If we don't want to do that we just return 0 + bytes which will force the ksba_reader to skip until + EOF. */ + if (parm->allow_multi_pem) + { + parm->identified = 0; + parm->autodetect = 0; + parm->assume_pem = 1; + parm->stop_seen = 0; + return -1; /* Send EOF now. */ + } + } + else if (parm->stop_seen) + { /* skip the rest of the line */ + parm->linelen = parm->readpos = 0; + } + else + { + int idx = parm->base64.idx; + unsigned char val = parm->base64.val; + + while (n < count && parm->readpos < parm->linelen ) + { + c = parm->line[parm->readpos++]; + if (c == '\n' || c == ' ' || c == '\r' || c == '\t') + continue; + if (c == '=') + { /* pad character: stop */ + if (idx == 1) + buffer[n++] = val; + parm->stop_seen = 1; + break; + } + if( (c = asctobin[(c2=c)]) == 255 ) + { + log_error (_("invalid radix64 character %02x skipped\n"), + c2); + continue; + } + switch (idx) + { + case 0: + val = c << 2; + break; + case 1: + val |= (c>>4)&3; + buffer[n++] = val; + val = (c<<4)&0xf0; + break; + case 2: + val |= (c>>2)&15; + buffer[n++] = val; + val = (c<<6)&0xc0; + break; + case 3: + val |= c&0x3f; + buffer[n++] = val; + break; + } + idx = (idx+1) % 4; + } + if (parm->readpos == parm->linelen) + parm->linelen = parm->readpos = 0; + + parm->base64.idx = idx; + parm->base64.val = val; + } + } + else + { /* DER encoded */ + while (n < count && parm->readpos < parm->linelen) + buffer[n++] = parm->line[parm->readpos++]; + if (parm->readpos == parm->linelen) + parm->linelen = parm->readpos = 0; + } + + *nread = n; + return 0; +} + + + +static int +simple_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) +{ + struct reader_cb_parm_s *parm = cb_value; + size_t n; + int c = 0; + + *nread = 0; + if (!buffer) + return -1; /* not supported */ + + for (n=0; n < count; n++) + { + c = getc (parm->fp); + if (c == EOF) + { + parm->eof_seen = 1; + if ( ferror (parm->fp) ) + return -1; + if (n) + break; /* return what we have before an EOF */ + return -1; + } + *(byte *)buffer++ = c; + } + + *nread = n; + return 0; +} + + + + +/* Call either es_putc or the plain putc. */ +static void +do_putc (int value, FILE *fp, estream_t stream) +{ + if (stream) + es_putc (value, stream); + else + putc (value, fp); +} + +/* Call either es_fputs or the plain fputs. */ +static void +do_fputs (const char *string, FILE *fp, estream_t stream) +{ + if (stream) + es_fputs (string, stream); + else + fputs (string, fp); +} + + +static int +base64_writer_cb (void *cb_value, const void *buffer, size_t count) +{ + struct writer_cb_parm_s *parm = cb_value; + unsigned char radbuf[4]; + int i, c, idx, quad_count; + const unsigned char *p; + FILE *fp = parm->fp; + estream_t stream = parm->stream; + + if (!count) + return 0; + + if (!parm->wrote_begin) + { + if (parm->pem_name) + { + do_fputs ("-----BEGIN ", fp, stream); + do_fputs (parm->pem_name, fp, stream); + do_fputs ("-----\n", fp, stream); + } + parm->wrote_begin = 1; + parm->base64.idx = 0; + parm->base64.quad_count = 0; + } + + idx = parm->base64.idx; + quad_count = parm->base64.quad_count; + for (i=0; i < idx; i++) + radbuf[i] = parm->base64.radbuf[i]; + + for (p=buffer; count; p++, count--) + { + radbuf[idx++] = *p; + if (idx > 2) + { + idx = 0; + c = bintoasc[(*radbuf >> 2) & 077]; + do_putc (c, fp, stream); + c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077]; + do_putc (c, fp, stream); + c = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077]; + do_putc (c, fp, stream); + c = bintoasc[radbuf[2]&077]; + do_putc (c, fp, stream); + if (++quad_count >= (64/4)) + { + do_fputs (LF, fp, stream); + quad_count = 0; + } + } + } + for (i=0; i < idx; i++) + parm->base64.radbuf[i] = radbuf[i]; + parm->base64.idx = idx; + parm->base64.quad_count = quad_count; + + return ((stream? es_ferror (stream) : ferror (fp)) + ? gpg_error_from_syserror () + : 0); +} + +/* This callback is only used in stream mode. Hiowever, we don't + restrict it to this. */ +static int +plain_writer_cb (void *cb_value, const void *buffer, size_t count) +{ + struct writer_cb_parm_s *parm = cb_value; + FILE *fp = parm->fp; + estream_t stream = parm->stream; + + if (!count) + return 0; + + if (stream) + es_write (stream, buffer, count, NULL); + else + fwrite (buffer, count, 1, fp); + + return ((stream? es_ferror (stream) : ferror (fp)) + ? gpg_error_from_syserror () + : 0); +} + +static int +base64_finish_write (struct writer_cb_parm_s *parm) +{ + unsigned char radbuf[4]; + int i, c, idx, quad_count; + FILE *fp = parm->fp; + estream_t stream = parm->stream; + + if (!parm->wrote_begin) + return 0; /* Nothing written or we are not called in base-64 mode. */ + + /* flush the base64 encoding */ + idx = parm->base64.idx; + quad_count = parm->base64.quad_count; + for (i=0; i < idx; i++) + radbuf[i] = parm->base64.radbuf[i]; + + if (idx) + { + c = bintoasc[(*radbuf>>2)&077]; + do_putc (c, fp, stream); + if (idx == 1) + { + c = bintoasc[((*radbuf << 4) & 060) & 077]; + do_putc (c, fp, stream); + do_putc ('=', fp, stream); + do_putc ('=', fp, stream); + } + else + { + c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1]>>4)&017))&077]; + do_putc (c, fp, stream); + c = bintoasc[((radbuf[1] << 2) & 074) & 077]; + do_putc (c, fp, stream); + do_putc ('=', fp, stream); + + } + if (++quad_count >= (64/4)) + { + do_fputs (LF, fp, stream); + quad_count = 0; + } + } + + if (quad_count) + do_fputs (LF, fp, stream); + + if (parm->pem_name) + { + do_fputs ("-----END ", fp, stream); + do_fputs (parm->pem_name, fp, stream); + do_fputs ("-----\n", fp, stream); + } + + return ((stream? es_ferror (stream) : ferror (fp)) + ? gpg_error_from_syserror () + : 0); +} + + + + +/* Create a reader for the given file descriptor. Depending on the + control information an input decoding is automagically choosen. + The function returns a Base64Context object which must be passed to + the gpgme_destroy_reader function. The created KsbaReader object + is also returned, but the caller must not call the + ksba_reader_release function on. If ALLOW_MULTI_PEM is true, the + reader expects that the caller uses ksba_reader_clear after EOF + until no more objects were found. */ +int +gpgsm_create_reader (Base64Context *ctx, + ctrl_t ctrl, FILE *fp, int allow_multi_pem, + ksba_reader_t *r_reader) +{ + int rc; + ksba_reader_t r; + + *r_reader = NULL; + *ctx = xtrycalloc (1, sizeof **ctx); + if (!*ctx) + return out_of_core (); + (*ctx)->u.rparm.allow_multi_pem = allow_multi_pem; + + rc = ksba_reader_new (&r); + if (rc) + { + xfree (*ctx); *ctx = NULL; + return rc; + } + + (*ctx)->u.rparm.fp = fp; + if (ctrl->is_pem) + { + (*ctx)->u.rparm.assume_pem = 1; + (*ctx)->u.rparm.assume_base64 = 1; + rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->u.rparm); + } + else if (ctrl->is_base64) + { + (*ctx)->u.rparm.assume_base64 = 1; + rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->u.rparm); + } + else if (ctrl->autodetect_encoding) + { + (*ctx)->u.rparm.autodetect = 1; + rc = ksba_reader_set_cb (r, base64_reader_cb, &(*ctx)->u.rparm); + } + else + rc = ksba_reader_set_cb (r, simple_reader_cb, &(*ctx)->u.rparm); + + if (rc) + { + ksba_reader_release (r); + xfree (*ctx); *ctx = NULL; + return rc; + } + + (*ctx)->u2.reader = r; + *r_reader = r; + return 0; +} + + +int +gpgsm_reader_eof_seen (Base64Context ctx) +{ + return ctx && ctx->u.rparm.eof_seen; +} + +void +gpgsm_destroy_reader (Base64Context ctx) +{ + if (!ctx) + return; + + ksba_reader_release (ctx->u2.reader); + xfree (ctx); +} + + + +/* Create a writer for the given stream FP or STREAM. Depending on + the control information an output encoding is automagically + choosen. The function returns a Base64Context object which must be + passed to the gpgme_destroy_writer function. The created + KsbaWriter object is also returned, but the caller must not call + the ksba_reader_release function on. */ +int +gpgsm_create_writer (Base64Context *ctx, + ctrl_t ctrl, FILE *fp, estream_t stream, + ksba_writer_t *r_writer) +{ + int rc; + ksba_writer_t w; + + *r_writer = NULL; + *ctx = xtrycalloc (1, sizeof **ctx); + if (!*ctx) + return out_of_core (); + + rc = ksba_writer_new (&w); + if (rc) + { + xfree (*ctx); *ctx = NULL; + return rc; + } + + if (ctrl->create_pem || ctrl->create_base64) + { + (*ctx)->u.wparm.fp = fp; + (*ctx)->u.wparm.stream = stream; + if (ctrl->create_pem) + (*ctx)->u.wparm.pem_name = ctrl->pem_name? ctrl->pem_name + : "CMS OBJECT"; + rc = ksba_writer_set_cb (w, base64_writer_cb, &(*ctx)->u.wparm); + } + else if (stream) + { + (*ctx)->u.wparm.fp = fp; + (*ctx)->u.wparm.stream = stream; + rc = ksba_writer_set_cb (w, plain_writer_cb, &(*ctx)->u.wparm); + } + else + rc = ksba_writer_set_file (w, fp); + + if (rc) + { + ksba_writer_release (w); + xfree (*ctx); *ctx = NULL; + return rc; + } + + (*ctx)->u2.writer = w; + *r_writer = w; + return 0; +} + + +int +gpgsm_finish_writer (Base64Context ctx) +{ + struct writer_cb_parm_s *parm; + + if (!ctx) + return gpg_error (GPG_ERR_INV_VALUE); + parm = &ctx->u.wparm; + if (parm->did_finish) + return 0; /* Already done. */ + parm->did_finish = 1; + if (!parm->fp && !parm->stream) + return 0; /* Callback was not used. */ + return base64_finish_write (parm); +} + +void +gpgsm_destroy_writer (Base64Context ctx) +{ + if (!ctx) + return; + + ksba_writer_release (ctx->u2.writer); + xfree (ctx); +} diff --git a/sm/call-agent.c b/sm/call-agent.c new file mode 100644 index 0000000..7eb16ed --- /dev/null +++ b/sm/call-agent.c @@ -0,0 +1,1069 @@ +/* call-agent.c - Divert GPGSM operations to the agent + * Copyright (C) 2001, 2002, 2003, 2005, 2007, + * 2008, 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif + +#include "gpgsm.h" +#include <gcrypt.h> +#include <assuan.h> +#include "i18n.h" +#include "asshelp.h" +#include "keydb.h" /* fixme: Move this to import.c */ +#include "membuf.h" + + +static assuan_context_t agent_ctx = NULL; + + +struct cipher_parm_s +{ + ctrl_t ctrl; + assuan_context_t ctx; + const unsigned char *ciphertext; + size_t ciphertextlen; +}; + +struct genkey_parm_s +{ + ctrl_t ctrl; + assuan_context_t ctx; + const unsigned char *sexp; + size_t sexplen; +}; + +struct learn_parm_s +{ + int error; + ctrl_t ctrl; + assuan_context_t ctx; + membuf_t *data; +}; + + + +/* Try to connect to the agent via socket or fork it off and work by + pipes. Handle the server's initial greeting */ +static int +start_agent (ctrl_t ctrl) +{ + int rc; + + if (agent_ctx) + rc = 0; /* fixme: We need a context for each thread or + serialize the access to the agent (which is + suitable given that the agent is not MT. */ + else + { + rc = start_new_gpg_agent (&agent_ctx, + GPG_ERR_SOURCE_DEFAULT, + opt.homedir, + opt.agent_program, + opt.lc_ctype, opt.lc_messages, + opt.session_env, + opt.verbose, DBG_ASSUAN, + gpgsm_status2, ctrl); + + if (!rc) + { + /* Tell the agent that we support Pinentry notifications. No + error checking so that it will work also with older + agents. */ + assuan_transact (agent_ctx, "OPTION allow-pinentry-notify", + NULL, NULL, NULL, NULL, NULL, NULL); + } + } + + if (!ctrl->agent_seen) + { + ctrl->agent_seen = 1; + audit_log_ok (ctrl->audit, AUDIT_AGENT_READY, rc); + } + + return rc; +} + + + +static gpg_error_t +membuf_data_cb (void *opaque, const void *buffer, size_t length) +{ + membuf_t *data = opaque; + + if (buffer) + put_membuf (data, buffer, length); + return 0; +} + + +/* This is the default inquiry callback. It mainly handles the + Pinentry notifications. */ +static gpg_error_t +default_inq_cb (void *opaque, const char *line) +{ + gpg_error_t err; + ctrl_t ctrl = opaque; + + if (!strncmp (line, "PINENTRY_LAUNCHED", 17) && (line[17]==' '||!line[17])) + { + err = gpgsm_proxy_pinentry_notify (ctrl, line); + if (err) + log_error (_("failed to proxy %s inquiry to client\n"), + "PINENTRY_LAUNCHED"); + /* We do not pass errors to avoid breaking other code. */ + } + else + log_error ("ignoring gpg-agent inquiry `%s'\n", line); + + return 0; +} + + + + +/* Call the agent to do a sign operation using the key identified by + the hex string KEYGRIP. */ +int +gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc, + unsigned char *digest, size_t digestlen, int digestalgo, + unsigned char **r_buf, size_t *r_buflen ) +{ + int rc, i; + char *p, line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t len; + + *r_buf = NULL; + rc = start_agent (ctrl); + if (rc) + return rc; + + if (digestlen*2 + 50 > DIM(line)) + return gpg_error (GPG_ERR_GENERAL); + + rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + + snprintf (line, DIM(line)-1, "SIGKEY %s", keygrip); + line[DIM(line)-1] = 0; + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + + if (desc) + { + snprintf (line, DIM(line)-1, "SETKEYDESC %s", desc); + line[DIM(line)-1] = 0; + rc = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + } + + sprintf (line, "SETHASH %d ", digestalgo); + p = line + strlen (line); + for (i=0; i < digestlen ; i++, p += 2 ) + sprintf (p, "%02X", digest[i]); + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + + init_membuf (&data, 1024); + rc = assuan_transact (agent_ctx, "PKSIGN", + membuf_data_cb, &data, default_inq_cb, ctrl, + NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return rc; + } + *r_buf = get_membuf (&data, r_buflen); + + if (!gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL)) + { + xfree (*r_buf); *r_buf = NULL; + return gpg_error (GPG_ERR_INV_VALUE); + } + + return *r_buf? 0 : out_of_core (); +} + + +/* Call the scdaemon to do a sign operation using the key identified by + the hex string KEYID. */ +int +gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, + unsigned char *digest, size_t digestlen, int digestalgo, + unsigned char **r_buf, size_t *r_buflen ) +{ + int rc, i; + char *p, line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t len; + const char *hashopt; + unsigned char *sigbuf; + size_t sigbuflen; + + (void)desc; + + *r_buf = NULL; + + switch(digestalgo) + { + case GCRY_MD_SHA1: hashopt = "--hash=sha1"; break; + case GCRY_MD_RMD160:hashopt = "--hash=rmd160"; break; + case GCRY_MD_MD5: hashopt = "--hash=md5"; break; + case GCRY_MD_SHA256:hashopt = "--hash=sha256"; break; + default: + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + + rc = start_agent (ctrl); + if (rc) + return rc; + + if (digestlen*2 + 50 > DIM(line)) + return gpg_error (GPG_ERR_GENERAL); + + p = stpcpy (line, "SCD SETDATA " ); + for (i=0; i < digestlen ; i++, p += 2 ) + sprintf (p, "%02X", digest[i]); + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + + init_membuf (&data, 1024); + + snprintf (line, DIM(line)-1, "SCD PKSIGN %s %s", hashopt, keyid); + line[DIM(line)-1] = 0; + rc = assuan_transact (agent_ctx, line, + membuf_data_cb, &data, default_inq_cb, ctrl, + NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return rc; + } + sigbuf = get_membuf (&data, &sigbuflen); + + /* Create an S-expression from it which is formatted like this: + "(7:sig-val(3:rsa(1:sSIGBUFLEN:SIGBUF)))" Fixme: If a card ever + creates non-RSA keys we need to change things. */ + *r_buflen = 21 + 11 + sigbuflen + 4; + p = xtrymalloc (*r_buflen); + *r_buf = (unsigned char*)p; + if (!p) + { + xfree (sigbuf); + return 0; + } + p = stpcpy (p, "(7:sig-val(3:rsa(1:s" ); + sprintf (p, "%u:", (unsigned int)sigbuflen); + p += strlen (p); + memcpy (p, sigbuf, sigbuflen); + p += sigbuflen; + strcpy (p, ")))"); + xfree (sigbuf); + + assert (gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL)); + return 0; +} + + + + +/* Handle a CIPHERTEXT inquiry. Note, we only send the data, + assuan_transact talkes care of flushing and writing the end */ +static gpg_error_t +inq_ciphertext_cb (void *opaque, const char *line) +{ + struct cipher_parm_s *parm = opaque; + int rc; + + if (!strncmp (line, "CIPHERTEXT", 10) && (line[10]==' '||!line[10])) + { + assuan_begin_confidential (parm->ctx); + rc = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen); + assuan_end_confidential (parm->ctx); + } + else + rc = default_inq_cb (parm->ctrl, line); + + return rc; +} + + +/* Call the agent to do a decrypt operation using the key identified by + the hex string KEYGRIP. */ +int +gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, + ksba_const_sexp_t ciphertext, + char **r_buf, size_t *r_buflen ) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + struct cipher_parm_s cipher_parm; + size_t n, len; + char *p, *buf, *endp; + size_t ciphertextlen; + + if (!keygrip || strlen(keygrip) != 40 || !ciphertext || !r_buf || !r_buflen) + return gpg_error (GPG_ERR_INV_VALUE); + *r_buf = NULL; + + ciphertextlen = gcry_sexp_canon_len (ciphertext, 0, NULL, NULL); + if (!ciphertextlen) + return gpg_error (GPG_ERR_INV_VALUE); + + rc = start_agent (ctrl); + if (rc) + return rc; + + rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + + assert ( DIM(line) >= 50 ); + snprintf (line, DIM(line)-1, "SETKEY %s", keygrip); + line[DIM(line)-1] = 0; + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + + if (desc) + { + snprintf (line, DIM(line)-1, "SETKEYDESC %s", desc); + line[DIM(line)-1] = 0; + rc = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + } + + init_membuf (&data, 1024); + cipher_parm.ctrl = ctrl; + cipher_parm.ctx = agent_ctx; + cipher_parm.ciphertext = ciphertext; + cipher_parm.ciphertextlen = ciphertextlen; + rc = assuan_transact (agent_ctx, "PKDECRYPT", + membuf_data_cb, &data, + inq_ciphertext_cb, &cipher_parm, NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return rc; + } + + put_membuf (&data, "", 1); /* Make sure it is 0 terminated. */ + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error (GPG_ERR_ENOMEM); + assert (len); /* (we forced Nul termination.) */ + + if (*buf == '(') + { + if (len < 13 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)\0" */ + return gpg_error (GPG_ERR_INV_SEXP); + len -= 11; /* Count only the data of the second part. */ + p = buf + 8; /* Skip leading parenthesis and the value tag. */ + } + else + { + /* For compatibility with older gpg-agents handle the old style + incomplete S-exps. */ + len--; /* Do not count the Nul. */ + p = buf; + } + + n = strtoul (p, &endp, 10); + if (!n || *endp != ':') + return gpg_error (GPG_ERR_INV_SEXP); + endp++; + if (endp-p+n > len) + return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */ + + memmove (buf, endp, n); + + *r_buflen = n; + *r_buf = buf; + return 0; +} + + + + + +/* Handle a KEYPARMS inquiry. Note, we only send the data, + assuan_transact takes care of flushing and writing the end */ +static gpg_error_t +inq_genkey_parms (void *opaque, const char *line) +{ + struct genkey_parm_s *parm = opaque; + int rc; + + if (!strncmp (line, "KEYPARAM", 8) && (line[8]==' '||!line[8])) + { + rc = assuan_send_data (parm->ctx, parm->sexp, parm->sexplen); + } + else + rc = default_inq_cb (parm->ctrl, line); + + return rc; +} + + + +/* Call the agent to generate a newkey */ +int +gpgsm_agent_genkey (ctrl_t ctrl, + ksba_const_sexp_t keyparms, ksba_sexp_t *r_pubkey) +{ + int rc; + struct genkey_parm_s gk_parm; + membuf_t data; + size_t len; + unsigned char *buf; + + *r_pubkey = NULL; + rc = start_agent (ctrl); + if (rc) + return rc; + + rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + + init_membuf (&data, 1024); + gk_parm.ctrl = ctrl; + gk_parm.ctx = agent_ctx; + gk_parm.sexp = keyparms; + gk_parm.sexplen = gcry_sexp_canon_len (keyparms, 0, NULL, NULL); + if (!gk_parm.sexplen) + return gpg_error (GPG_ERR_INV_VALUE); + rc = assuan_transact (agent_ctx, "GENKEY", + membuf_data_cb, &data, + inq_genkey_parms, &gk_parm, NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return rc; + } + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error (GPG_ERR_ENOMEM); + if (!gcry_sexp_canon_len (buf, len, NULL, NULL)) + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); + } + *r_pubkey = buf; + return 0; +} + + +/* Call the agent to read the public key part for a given keygrip. If + FROMCARD is true, the key is directly read from the current + smartcard. In this case HEXKEYGRIP should be the keyID + (e.g. OPENPGP.3). */ +int +gpgsm_agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip, + ksba_sexp_t *r_pubkey) +{ + int rc; + membuf_t data; + size_t len; + unsigned char *buf; + char line[ASSUAN_LINELENGTH]; + + *r_pubkey = NULL; + rc = start_agent (ctrl); + if (rc) + return rc; + + rc = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + + snprintf (line, DIM(line)-1, "%sREADKEY %s", + fromcard? "SCD ":"", hexkeygrip); + line[DIM(line)-1] = 0; + + init_membuf (&data, 1024); + rc = assuan_transact (agent_ctx, line, + membuf_data_cb, &data, + default_inq_cb, ctrl, NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return rc; + } + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error (GPG_ERR_ENOMEM); + if (!gcry_sexp_canon_len (buf, len, NULL, NULL)) + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); + } + *r_pubkey = buf; + return 0; +} + + + +/* Take the serial number from LINE and return it verbatim in a newly + allocated string. We make sure that only hex characters are + returned. */ +static char * +store_serialno (const char *line) +{ + const char *s; + char *p; + + for (s=line; hexdigitp (s); s++) + ; + p = xtrymalloc (s + 1 - line); + if (p) + { + memcpy (p, line, s-line); + p[s-line] = 0; + } + return p; +} + + +/* Callback for the gpgsm_agent_serialno fucntion. */ +static gpg_error_t +scd_serialno_status_cb (void *opaque, const char *line) +{ + char **r_serialno = 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)) + { + xfree (*r_serialno); + *r_serialno = store_serialno (line); + } + + return 0; +} + + +/* Call the agent to read the serial number of the current card. */ +int +gpgsm_agent_scd_serialno (ctrl_t ctrl, char **r_serialno) +{ + int rc; + char *serialno = NULL; + + *r_serialno = NULL; + rc = start_agent (ctrl); + if (rc) + return rc; + + rc = assuan_transact (agent_ctx, "SCD SERIALNO", + NULL, NULL, + default_inq_cb, ctrl, + scd_serialno_status_cb, &serialno); + if (!rc && !serialno) + rc = gpg_error (GPG_ERR_INTERNAL); + if (rc) + { + xfree (serialno); + return rc; + } + *r_serialno = serialno; + return 0; +} + + + +/* Callback for the gpgsm_agent_serialno fucntion. */ +static gpg_error_t +scd_keypairinfo_status_cb (void *opaque, const char *line) +{ + strlist_t *listaddr = opaque; + const char *keyword = line; + int keywordlen; + strlist_t sl; + char *p; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) + { + sl = append_to_strlist (listaddr, line); + p = sl->d; + /* Make sure that we only have two tokes so that future + extensions of the format won't change the format expected by + the caller. */ + while (*p && !spacep (p)) + p++; + if (*p) + { + while (spacep (p)) + p++; + while (*p && !spacep (p)) + p++; + *p = 0; + } + } + + return 0; +} + + +/* Call the agent to read the keypairinfo lines of the current card. + The list is returned as a string made up of the keygrip, a space + and the keyid. */ +int +gpgsm_agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list) +{ + int rc; + strlist_t list = NULL; + + *r_list = NULL; + rc = start_agent (ctrl); + if (rc) + return rc; + + rc = assuan_transact (agent_ctx, "SCD LEARN --force", + NULL, NULL, + default_inq_cb, ctrl, + scd_keypairinfo_status_cb, &list); + if (!rc && !list) + rc = gpg_error (GPG_ERR_NO_DATA); + if (rc) + { + free_strlist (list); + return rc; + } + *r_list = list; + return 0; +} + + + +static gpg_error_t +istrusted_status_cb (void *opaque, const char *line) +{ + struct rootca_flags_s *flags = opaque; + + if (!strncmp (line, "TRUSTLISTFLAG", 13) && (line[13]==' ' || !line[13])) + { + for (line += 13; *line == ' '; line++) + ; + if (!strncmp (line, "relax", 5) && (line[5] == ' ' || !line[5])) + flags->relax = 1; + else if (!strncmp (line, "cm", 2) && (line[2] == ' ' || !line[2])) + flags->chain_model = 1; + } + return 0; +} + + + +/* Ask the agent whether the certificate is in the list of trusted + keys. The certificate is either specified by the CERT object or by + the fingerprint HEXFPR. ROOTCA_FLAGS is guaranteed to be cleared + on error. */ +int +gpgsm_agent_istrusted (ctrl_t ctrl, ksba_cert_t cert, const char *hexfpr, + struct rootca_flags_s *rootca_flags) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + + memset (rootca_flags, 0, sizeof *rootca_flags); + + if (cert && hexfpr) + return gpg_error (GPG_ERR_INV_ARG); + + rc = start_agent (ctrl); + if (rc) + return rc; + + if (hexfpr) + { + snprintf (line, DIM(line)-1, "ISTRUSTED %s", hexfpr); + line[DIM(line)-1] = 0; + } + else + { + char *fpr; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + if (!fpr) + { + log_error ("error getting the fingerprint\n"); + return gpg_error (GPG_ERR_GENERAL); + } + + snprintf (line, DIM(line)-1, "ISTRUSTED %s", fpr); + line[DIM(line)-1] = 0; + xfree (fpr); + } + + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, + istrusted_status_cb, rootca_flags); + if (!rc) + rootca_flags->valid = 1; + return rc; +} + +/* Ask the agent to mark CERT as a trusted Root-CA one */ +int +gpgsm_agent_marktrusted (ctrl_t ctrl, ksba_cert_t cert) +{ + int rc; + char *fpr, *dn, *dnfmt; + char line[ASSUAN_LINELENGTH]; + + rc = start_agent (ctrl); + if (rc) + return rc; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + if (!fpr) + { + log_error ("error getting the fingerprint\n"); + return gpg_error (GPG_ERR_GENERAL); + } + + dn = ksba_cert_get_issuer (cert, 0); + if (!dn) + { + xfree (fpr); + return gpg_error (GPG_ERR_GENERAL); + } + dnfmt = gpgsm_format_name2 (dn, 0); + xfree (dn); + if (!dnfmt) + return gpg_error_from_syserror (); + snprintf (line, DIM(line)-1, "MARKTRUSTED %s S %s", fpr, dnfmt); + line[DIM(line)-1] = 0; + ksba_free (dnfmt); + xfree (fpr); + + rc = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, ctrl, NULL, NULL); + return rc; +} + + + +/* Ask the agent whether the a corresponding secret key is available + for the given keygrip */ +int +gpgsm_agent_havekey (ctrl_t ctrl, const char *hexkeygrip) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + + rc = start_agent (ctrl); + if (rc) + return rc; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + snprintf (line, DIM(line)-1, "HAVEKEY %s", hexkeygrip); + line[DIM(line)-1] = 0; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + return rc; +} + + +static gpg_error_t +learn_status_cb (void *opaque, const char *line) +{ + struct learn_parm_s *parm = opaque; + + /* Pass progress data to the caller. */ + if (!strncmp (line, "PROGRESS", 8) && (line[8]==' ' || !line[8])) + { + if (parm->ctrl) + { + for (line += 8; *line == ' '; line++) + ; + if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line)) + return gpg_error (GPG_ERR_ASS_CANCELED); + } + } + return 0; +} + +static gpg_error_t +learn_cb (void *opaque, const void *buffer, size_t length) +{ + struct learn_parm_s *parm = opaque; + size_t len; + char *buf; + ksba_cert_t cert; + int rc; + + if (parm->error) + return 0; + + if (buffer) + { + put_membuf (parm->data, buffer, length); + return 0; + } + /* END encountered - process what we have */ + buf = get_membuf (parm->data, &len); + if (!buf) + { + parm->error = gpg_error (GPG_ERR_ENOMEM); + return 0; + } + + if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, "learncard C 0 0")) + return gpg_error (GPG_ERR_ASS_CANCELED); + + /* FIXME: this should go into import.c */ + rc = ksba_cert_new (&cert); + if (rc) + { + parm->error = rc; + return 0; + } + rc = ksba_cert_init_from_mem (cert, buf, len); + if (rc) + { + log_error ("failed to parse a certificate: %s\n", gpg_strerror (rc)); + ksba_cert_release (cert); + parm->error = rc; + return 0; + } + + /* We do not store a certifciate with missing issuers as ephemeral + because we can assume that the --learn-card command has been used + on purpose. */ + rc = gpgsm_basic_cert_check (parm->ctrl, cert); + if (rc && gpg_err_code (rc) != GPG_ERR_MISSING_CERT + && gpg_err_code (rc) != GPG_ERR_MISSING_ISSUER_CERT) + log_error ("invalid certificate: %s\n", gpg_strerror (rc)); + else + { + int existed; + + if (!keydb_store_cert (cert, 0, &existed)) + { + if (opt.verbose > 1 && existed) + log_info ("certificate already in DB\n"); + else if (opt.verbose && !existed) + log_info ("certificate imported\n"); + } + } + + ksba_cert_release (cert); + init_membuf (parm->data, 4096); + return 0; +} + +/* Call the agent to learn about a smartcard */ +int +gpgsm_agent_learn (ctrl_t ctrl) +{ + int rc; + struct learn_parm_s learn_parm; + membuf_t data; + size_t len; + + rc = start_agent (ctrl); + if (rc) + return rc; + + init_membuf (&data, 4096); + learn_parm.error = 0; + learn_parm.ctrl = ctrl; + learn_parm.ctx = agent_ctx; + learn_parm.data = &data; + rc = assuan_transact (agent_ctx, "LEARN --send", + learn_cb, &learn_parm, + NULL, NULL, + learn_status_cb, &learn_parm); + xfree (get_membuf (&data, &len)); + if (rc) + return rc; + return learn_parm.error; +} + + +/* Ask the agent to change the passphrase of the key identified by + HEXKEYGRIP. If DESC is not NULL, display instead of the default + description message. */ +int +gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + + rc = start_agent (ctrl); + if (rc) + return rc; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + if (desc) + { + snprintf (line, DIM(line)-1, "SETKEYDESC %s", desc); + line[DIM(line)-1] = 0; + rc = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + } + + snprintf (line, DIM(line)-1, "PASSWD %s", hexkeygrip); + line[DIM(line)-1] = 0; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, ctrl, NULL, NULL); + return rc; +} + + + +/* Ask the agent to pop up a confirmation dialog with the text DESC + and an okay and cancel button. */ +gpg_error_t +gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + + rc = start_agent (ctrl); + if (rc) + return rc; + + snprintf (line, DIM(line)-1, "GET_CONFIRMATION %s", desc); + line[DIM(line)-1] = 0; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, ctrl, NULL, NULL); + return rc; +} + + + +/* Return 0 if the agent is alive. This is useful to make sure that + an agent has been started. */ +gpg_error_t +gpgsm_agent_send_nop (ctrl_t ctrl) +{ + int rc; + + rc = start_agent (ctrl); + if (!rc) + rc = assuan_transact (agent_ctx, "NOP", + NULL, NULL, NULL, NULL, NULL, NULL); + return rc; +} + + + +static gpg_error_t +keyinfo_status_cb (void *opaque, const char *line) +{ + char **serialno = opaque; + const char *s, *s2; + + if (!strncmp (line, "KEYINFO ", 8) && !*serialno) + { + s = strchr (line+8, ' '); + if (s && s[1] == 'T' && s[2] == ' ' && s[3]) + { + s += 3; + s2 = strchr (s, ' '); + if ( s2 > s ) + { + *serialno = xtrymalloc ((s2 - s)+1); + if (*serialno) + { + memcpy (*serialno, s, s2 - s); + (*serialno)[s2 - s] = 0; + } + } + } + } + return 0; +} + +/* Return the serial number for a secret key. If the returned serial + number is NULL, the key is not stored on a smartcard. Caller needs + to free R_SERIALNO. */ +gpg_error_t +gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + char *serialno = NULL; + + *r_serialno = NULL; + + err = start_agent (ctrl); + if (err) + return err; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + snprintf (line, DIM(line)-1, "KEYINFO %s", hexkeygrip); + line[DIM(line)-1] = 0; + + err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, + keyinfo_status_cb, &serialno); + if (!err && serialno) + { + /* Sanity check for bad characters. */ + if (strpbrk (serialno, ":\n\r")) + err = GPG_ERR_INV_VALUE; + } + if (err) + xfree (serialno); + else + *r_serialno = serialno; + return err; +} + diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c new file mode 100644 index 0000000..6540a8f --- /dev/null +++ b/sm/call-dirmngr.c @@ -0,0 +1,1126 @@ +/* call-dirmngr.c - communication with the dromngr + * Copyright (C) 2002, 2003, 2005, 2007, 2008 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> +#include <ctype.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <assuan.h> + +#include "i18n.h" +#include "keydb.h" + + +struct membuf { + size_t len; + size_t size; + char *buf; + int out_of_core; +}; + + + +/* fixme: We need a context for each thread or serialize the access to + the dirmngr. */ +static assuan_context_t dirmngr_ctx = NULL; +static assuan_context_t dirmngr2_ctx = NULL; + +static int dirmngr_ctx_locked; +static int dirmngr2_ctx_locked; + +static int force_pipe_server = 0; + +struct inq_certificate_parm_s { + ctrl_t ctrl; + assuan_context_t ctx; + ksba_cert_t cert; + ksba_cert_t issuer_cert; +}; + +struct isvalid_status_parm_s { + ctrl_t ctrl; + int seen; + unsigned char fpr[20]; +}; + + +struct lookup_parm_s { + ctrl_t ctrl; + assuan_context_t ctx; + void (*cb)(void *, ksba_cert_t); + void *cb_value; + struct membuf data; + int error; +}; + +struct run_command_parm_s { + assuan_context_t ctx; +}; + + + +static gpg_error_t get_cached_cert (assuan_context_t ctx, + const unsigned char *fpr, + ksba_cert_t *r_cert); + + + +/* A simple implementation of a dynamic buffer. Use init_membuf() to + create a buffer, put_membuf to append bytes and get_membuf to + release and return the buffer. Allocation errors are detected but + only returned at the final get_membuf(), this helps not to clutter + the code with out of core checks. */ + +static void +init_membuf (struct membuf *mb, int initiallen) +{ + mb->len = 0; + mb->size = initiallen; + mb->out_of_core = 0; + mb->buf = xtrymalloc (initiallen); + if (!mb->buf) + mb->out_of_core = 1; +} + +static void +put_membuf (struct membuf *mb, const void *buf, size_t len) +{ + if (mb->out_of_core) + return; + + if (mb->len + len >= mb->size) + { + char *p; + + mb->size += len + 1024; + p = xtryrealloc (mb->buf, mb->size); + if (!p) + { + mb->out_of_core = 1; + return; + } + mb->buf = p; + } + memcpy (mb->buf + mb->len, buf, len); + mb->len += len; +} + +static void * +get_membuf (struct membuf *mb, size_t *len) +{ + char *p; + + if (mb->out_of_core) + { + xfree (mb->buf); + mb->buf = NULL; + return NULL; + } + + p = mb->buf; + *len = mb->len; + mb->buf = NULL; + mb->out_of_core = 1; /* don't allow a reuse */ + return p; +} + + +/* This function prepares the dirmngr for a new session. The + audit-events option is used so that other dirmngr clients won't get + disturbed by such events. */ +static void +prepare_dirmngr (ctrl_t ctrl, assuan_context_t ctx, gpg_error_t err) +{ + struct keyserver_spec *server; + + if (!err) + { + err = assuan_transact (ctx, "OPTION audit-events=1", + NULL, NULL, NULL, NULL, NULL, NULL); + if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) + err = 0; /* Allow the use of old dirmngr versions. */ + } + audit_log_ok (ctrl->audit, AUDIT_DIRMNGR_READY, err); + + if (!ctx || err) + return; + + server = opt.keyserver; + while (server) + { + char line[ASSUAN_LINELENGTH]; + char *user = server->user ? server->user : ""; + char *pass = server->pass ? server->pass : ""; + char *base = server->base ? server->base : ""; + + snprintf (line, DIM (line) - 1, "LDAPSERVER %s:%i:%s:%s:%s", + server->host, server->port, user, pass, base); + line[DIM (line) - 1] = 0; + + err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD) + err = 0; /* Allow the use of old dirmngr versions. */ + + server = server->next; + } +} + + + +/* Try to connect to the agent via socket or fork it off and work by + pipes. Handle the server's initial greeting */ +static int +start_dirmngr_ext (ctrl_t ctrl, assuan_context_t *ctx_r) +{ + int rc; + char *infostr, *p; + assuan_context_t ctx = NULL; + int try_default = 0; + + if (opt.disable_dirmngr) + return gpg_error (GPG_ERR_NO_DIRMNGR); + + if (*ctx_r) + return 0; + + /* Note: if you change this to multiple connections, you also need + to take care of the implicit option sending caching. */ + +#ifdef HAVE_W32_SYSTEM + infostr = NULL; + opt.prefer_system_dirmngr = 1; +#else + infostr = force_pipe_server? NULL : getenv ("DIRMNGR_INFO"); +#endif /*HAVE_W32_SYSTEM*/ + if (infostr && !*infostr) + infostr = NULL; + else if (infostr) + infostr = xstrdup (infostr); + + if (opt.prefer_system_dirmngr && !force_pipe_server && !infostr) + { + infostr = xstrdup (dirmngr_socket_name ()); + try_default = 1; + } + + rc = assuan_new (&ctx); + if (rc) + { + log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); + return rc; + } + + if (!infostr) + { + const char *pgmname; + const char *argv[3]; + int no_close_list[3]; + int i; + + if (!opt.dirmngr_program || !*opt.dirmngr_program) + opt.dirmngr_program = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR); + if ( !(pgmname = strrchr (opt.dirmngr_program, '/'))) + pgmname = opt.dirmngr_program; + else + pgmname++; + + if (opt.verbose) + log_info (_("no running dirmngr - starting `%s'\n"), + opt.dirmngr_program); + + if (fflush (NULL)) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("error flushing pending output: %s\n", strerror (errno)); + return tmperr; + } + + argv[0] = pgmname; + argv[1] = "--server"; + argv[2] = NULL; + + i=0; + 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] = -1; + + /* connect to the agent and perform initial handshaking */ + rc = assuan_pipe_connect (ctx, opt.dirmngr_program, argv, + no_close_list, NULL, NULL, 0); + } + else + { + int prot; + int pid; + + if (!try_default) + { + if ( !(p = strchr (infostr, PATHSEP_C)) || p == infostr) + { + log_error (_("malformed DIRMNGR_INFO environment variable\n")); + xfree (infostr); + force_pipe_server = 1; + return start_dirmngr_ext (ctrl, ctx_r); + } + *p++ = 0; + pid = atoi (p); + while (*p && *p != PATHSEP_C) + p++; + prot = *p? atoi (p+1) : 0; + if (prot != 1) + { + log_error (_("dirmngr protocol version %d is not supported\n"), + prot); + xfree (infostr); + force_pipe_server = 1; + return start_dirmngr_ext (ctrl, ctx_r); + } + } + else + pid = -1; + + rc = assuan_socket_connect (ctx, infostr, pid, 0); +#ifdef HAVE_W32_SYSTEM + if (rc) + log_debug ("connecting dirmngr at `%s' failed\n", infostr); +#endif + + xfree (infostr); +#ifndef HAVE_W32_SYSTEM + if (gpg_err_code (rc) == GPG_ERR_ASS_CONNECT_FAILED) + { + log_info (_("can't connect to the dirmngr - trying fall back\n")); + force_pipe_server = 1; + return start_dirmngr_ext (ctrl, ctx_r); + } +#endif /*!HAVE_W32_SYSTEM*/ + } + + prepare_dirmngr (ctrl, ctx, rc); + + if (rc) + { + assuan_release (ctx); + log_error ("can't connect to the dirmngr: %s\n", gpg_strerror (rc)); + return gpg_error (GPG_ERR_NO_DIRMNGR); + } + *ctx_r = ctx; + + if (DBG_ASSUAN) + log_debug ("connection to dirmngr established\n"); + return 0; +} + + +static int +start_dirmngr (ctrl_t ctrl) +{ + gpg_error_t err; + + assert (! dirmngr_ctx_locked); + dirmngr_ctx_locked = 1; + + err = start_dirmngr_ext (ctrl, &dirmngr_ctx); + /* We do not check ERR but the existance of a context because the + error might come from a failed command send to the dirmngr. + Fixme: Why don't we close the drimngr context if we encountered + an error in prepare_dirmngr? */ + if (!dirmngr_ctx) + dirmngr_ctx_locked = 0; + return err; +} + + +static void +release_dirmngr (ctrl_t ctrl) +{ + (void)ctrl; + + if (!dirmngr_ctx_locked) + log_error ("WARNING: trying to release a non-locked dirmngr ctx\n"); + dirmngr_ctx_locked = 0; +} + + +static int +start_dirmngr2 (ctrl_t ctrl) +{ + gpg_error_t err; + + assert (! dirmngr2_ctx_locked); + dirmngr2_ctx_locked = 1; + + err = start_dirmngr_ext (ctrl, &dirmngr2_ctx); + if (!dirmngr2_ctx) + dirmngr2_ctx_locked = 0; + return err; +} + + +static void +release_dirmngr2 (ctrl_t ctrl) +{ + (void)ctrl; + + if (!dirmngr2_ctx_locked) + log_error ("WARNING: trying to release a non-locked dirmngr2 ctx\n"); + dirmngr2_ctx_locked = 0; +} + + + +/* Handle a SENDCERT inquiry. */ +static gpg_error_t +inq_certificate (void *opaque, const char *line) +{ + struct inq_certificate_parm_s *parm = opaque; + int rc; + const unsigned char *der; + size_t derlen; + int issuer_mode = 0; + ksba_sexp_t ski = NULL; + + if (!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8])) + { + line += 8; + } + else if (!strncmp (line, "SENDCERT_SKI", 12) && (line[12]==' ' || !line[12])) + { + size_t n; + + /* Send a certificate where a sourceKeyIdentifier is included. */ + line += 12; + while (*line == ' ') + line++; + ski = make_simple_sexp_from_hexstr (line, &n); + line += n; + while (*line == ' ') + line++; + } + else if (!strncmp (line, "SENDISSUERCERT", 14) + && (line[14] == ' ' || !line[14])) + { + line += 14; + issuer_mode = 1; + } + else if (!strncmp (line, "ISTRUSTED", 9) && (line[9]==' ' || !line[9])) + { + /* The server is asking us whether the certificate is a trusted + root certificate. */ + const char *s; + size_t n; + char fpr[41]; + struct rootca_flags_s rootca_flags; + + line += 9; + while (*line == ' ') + line++; + + for (s=line,n=0; hexdigitp (s); s++, n++) + ; + if (*s || n != 40) + return gpg_error (GPG_ERR_ASS_PARAMETER); + for (s=line, n=0; n < 40; s++, n++) + fpr[n] = (*s >= 'a')? (*s & 0xdf): *s; + fpr[n] = 0; + + if (!gpgsm_agent_istrusted (parm->ctrl, NULL, fpr, &rootca_flags)) + rc = assuan_send_data (parm->ctx, "1", 1); + else + rc = 0; + return rc; + } + else + { + log_error ("unsupported inquiry `%s'\n", line); + return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); + } + + if (!*line) + { /* Send the current certificate. */ + der = ksba_cert_get_image (issuer_mode? parm->issuer_cert : parm->cert, + &derlen); + if (!der) + rc = gpg_error (GPG_ERR_INV_CERT_OBJ); + else + rc = assuan_send_data (parm->ctx, der, derlen); + } + else if (issuer_mode) + { + log_error ("sending specific issuer certificate back " + "is not yet implemented\n"); + rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); + } + else + { /* Send the given certificate. */ + int err; + ksba_cert_t cert; + + + err = gpgsm_find_cert (line, ski, &cert); + if (err) + { + log_error ("certificate not found: %s\n", gpg_strerror (err)); + rc = gpg_error (GPG_ERR_NOT_FOUND); + } + else + { + der = ksba_cert_get_image (cert, &derlen); + if (!der) + rc = gpg_error (GPG_ERR_INV_CERT_OBJ); + else + rc = assuan_send_data (parm->ctx, der, derlen); + ksba_cert_release (cert); + } + } + + xfree (ski); + return rc; +} + + +/* Take a 20 byte hexencoded string and put it into the the provided + 20 byte buffer FPR in binary format. */ +static int +unhexify_fpr (const char *hexstr, unsigned char *fpr) +{ + const char *s; + int n; + + for (s=hexstr, n=0; hexdigitp (s); s++, n++) + ; + if (*s || (n != 40)) + return 0; /* no fingerprint (invalid or wrong length). */ + n /= 2; + for (s=hexstr, n=0; *s; s += 2, n++) + fpr[n] = xtoi_2 (s); + return 1; /* okay */ +} + + +static gpg_error_t +isvalid_status_cb (void *opaque, const char *line) +{ + struct isvalid_status_parm_s *parm = opaque; + + if (!strncmp (line, "PROGRESS", 8) && (line[8]==' ' || !line[8])) + { + if (parm->ctrl) + { + for (line += 8; *line == ' '; line++) + ; + if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line)) + return gpg_error (GPG_ERR_ASS_CANCELED); + } + } + else if (!strncmp (line, "ONLY_VALID_IF_CERT_VALID", 24) + && (line[24]==' ' || !line[24])) + { + parm->seen++; + if (!line[24] || !unhexify_fpr (line+25, parm->fpr)) + parm->seen++; /* Bumb it to indicate an error. */ + } + return 0; +} + + + + +/* Call the directory manager to check whether the certificate is valid + Returns 0 for valid or usually one of the errors: + + GPG_ERR_CERTIFICATE_REVOKED + GPG_ERR_NO_CRL_KNOWN + GPG_ERR_CRL_TOO_OLD + + Values for USE_OCSP: + 0 = Do CRL check. + 1 = Do an OCSP check. + 2 = Do an OCSP check using only the default responder. + */ +int +gpgsm_dirmngr_isvalid (ctrl_t ctrl, + ksba_cert_t cert, ksba_cert_t issuer_cert, int use_ocsp) +{ + static int did_options; + int rc; + char *certid; + char line[ASSUAN_LINELENGTH]; + struct inq_certificate_parm_s parm; + struct isvalid_status_parm_s stparm; + + rc = start_dirmngr (ctrl); + if (rc) + return rc; + + if (use_ocsp) + { + certid = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + } + else + { + certid = gpgsm_get_certid (cert); + if (!certid) + { + log_error ("error getting the certificate ID\n"); + release_dirmngr (ctrl); + return gpg_error (GPG_ERR_GENERAL); + } + } + + if (opt.verbose > 1) + { + char *fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1); + log_info ("asking dirmngr about %s%s\n", fpr, + use_ocsp? " (using OCSP)":""); + xfree (fpr); + } + + parm.ctx = dirmngr_ctx; + parm.ctrl = ctrl; + parm.cert = cert; + parm.issuer_cert = issuer_cert; + + stparm.ctrl = ctrl; + stparm.seen = 0; + memset (stparm.fpr, 0, 20); + + /* FIXME: If --disable-crl-checks has been set, we should pass an + option to dirmngr, so that no fallback CRL check is done after an + ocsp check. It is not a problem right now as dirmngr does not + fallback to CRL checking. */ + + /* It is sufficient to send the options only once because we have + one connection per process only. */ + if (!did_options) + { + if (opt.force_crl_refresh) + assuan_transact (dirmngr_ctx, "OPTION force-crl-refresh=1", + NULL, NULL, NULL, NULL, NULL, NULL); + did_options = 1; + } + snprintf (line, DIM(line)-1, "ISVALID%s %s", + use_ocsp == 2? " --only-ocsp --force-default-responder":"", + certid); + line[DIM(line)-1] = 0; + xfree (certid); + + rc = assuan_transact (dirmngr_ctx, line, NULL, NULL, + inq_certificate, &parm, + isvalid_status_cb, &stparm); + if (opt.verbose > 1) + log_info ("response of dirmngr: %s\n", rc? gpg_strerror (rc): "okay"); + rc = rc; + + if (!rc && stparm.seen) + { + /* Need to also check the certificate validity. */ + if (stparm.seen != 1) + { + log_error ("communication problem with dirmngr detected\n"); + rc = gpg_error (GPG_ERR_INV_CRL); + } + else + { + ksba_cert_t rspcert = NULL; + + if (get_cached_cert (dirmngr_ctx, stparm.fpr, &rspcert)) + { + /* Ooops: Something went wrong getting the certificate + from the dirmngr. Try our own cert store now. */ + KEYDB_HANDLE kh; + + kh = keydb_new (0); + if (!kh) + rc = gpg_error (GPG_ERR_ENOMEM); + if (!rc) + rc = keydb_search_fpr (kh, stparm.fpr); + if (!rc) + rc = keydb_get_cert (kh, &rspcert); + if (rc) + { + log_error ("unable to find the certificate used " + "by the dirmngr: %s\n", gpg_strerror (rc)); + rc = gpg_error (GPG_ERR_INV_CRL); + } + keydb_release (kh); + } + + if (!rc) + { + rc = gpgsm_cert_use_ocsp_p (rspcert); + if (rc) + rc = gpg_error (GPG_ERR_INV_CRL); + else + { + /* Note the no_dirmngr flag: This avoids checking + this certificate over and over again. */ + rc = gpgsm_validate_chain (ctrl, rspcert, "", NULL, 0, NULL, + VALIDATE_FLAG_NO_DIRMNGR, NULL); + if (rc) + { + log_error ("invalid certificate used for CRL/OCSP: %s\n", + gpg_strerror (rc)); + rc = gpg_error (GPG_ERR_INV_CRL); + } + } + } + ksba_cert_release (rspcert); + } + } + release_dirmngr (ctrl); + return rc; +} + + + +/* Lookup helpers*/ +static gpg_error_t +lookup_cb (void *opaque, const void *buffer, size_t length) +{ + struct lookup_parm_s *parm = opaque; + size_t len; + char *buf; + ksba_cert_t cert; + int rc; + + if (parm->error) + return 0; + + if (buffer) + { + put_membuf (&parm->data, buffer, length); + return 0; + } + /* END encountered - process what we have */ + buf = get_membuf (&parm->data, &len); + if (!buf) + { + parm->error = gpg_error (GPG_ERR_ENOMEM); + return 0; + } + + rc = ksba_cert_new (&cert); + if (rc) + { + parm->error = rc; + return 0; + } + rc = ksba_cert_init_from_mem (cert, buf, len); + if (rc) + { + log_error ("failed to parse a certificate: %s\n", gpg_strerror (rc)); + } + else + { + parm->cb (parm->cb_value, cert); + } + + ksba_cert_release (cert); + init_membuf (&parm->data, 4096); + return 0; +} + +/* Return a properly escaped pattern from NAMES. The only error + return is NULL to indicate a malloc failure. */ +static char * +pattern_from_strlist (strlist_t names) +{ + strlist_t sl; + int n; + const char *s; + char *pattern, *p; + + for (n=0, sl=names; sl; sl = sl->next) + { + for (s=sl->d; *s; s++, n++) + { + if (*s == '%' || *s == ' ' || *s == '+') + n += 2; + } + n++; + } + + p = pattern = xtrymalloc (n+1); + if (!pattern) + return NULL; + + for (sl=names; sl; sl = sl->next) + { + for (s=sl->d; *s; s++) + { + switch (*s) + { + case '%': + *p++ = '%'; + *p++ = '2'; + *p++ = '5'; + break; + case ' ': + *p++ = '%'; + *p++ = '2'; + *p++ = '0'; + break; + case '+': + *p++ = '%'; + *p++ = '2'; + *p++ = 'B'; + break; + default: + *p++ = *s; + break; + } + } + *p++ = ' '; + } + if (p == pattern) + *pattern = 0; /* is empty */ + else + p[-1] = '\0'; /* remove trailing blank */ + + return pattern; +} + +static gpg_error_t +lookup_status_cb (void *opaque, const char *line) +{ + struct lookup_parm_s *parm = opaque; + + if (!strncmp (line, "PROGRESS", 8) && (line[8]==' ' || !line[8])) + { + if (parm->ctrl) + { + for (line += 8; *line == ' '; line++) + ; + if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line)) + return gpg_error (GPG_ERR_ASS_CANCELED); + } + } + else if (!strncmp (line, "TRUNCATED", 9) && (line[9]==' ' || !line[9])) + { + if (parm->ctrl) + { + for (line +=9; *line == ' '; line++) + ; + gpgsm_status (parm->ctrl, STATUS_TRUNCATED, line); + } + } + return 0; +} + + +/* Run the Directory Manager's lookup command using the pattern + compiled from the strings given in NAMES. The caller must provide + the callback CB which will be passed cert by cert. Note that CTRL + is optional. With CACHE_ONLY the dirmngr will search only its own + key cache. */ +int +gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names, int cache_only, + void (*cb)(void*, ksba_cert_t), void *cb_value) +{ + int rc; + char *pattern; + char line[ASSUAN_LINELENGTH]; + struct lookup_parm_s parm; + size_t len; + assuan_context_t ctx; + + /* The lookup function can be invoked from the callback of a lookup + function, for example to walk the chain. */ + if (!dirmngr_ctx_locked) + { + rc = start_dirmngr (ctrl); + if (rc) + return rc; + ctx = dirmngr_ctx; + } + else if (!dirmngr2_ctx_locked) + { + rc = start_dirmngr2 (ctrl); + if (rc) + return rc; + ctx = dirmngr2_ctx; + } + else + { + log_fatal ("both dirmngr contexts are in use\n"); + } + + pattern = pattern_from_strlist (names); + if (!pattern) + { + if (ctx == dirmngr_ctx) + release_dirmngr (ctrl); + else + release_dirmngr2 (ctrl); + + return out_of_core (); + } + snprintf (line, DIM(line)-1, "LOOKUP%s %s", + cache_only? " --cache-only":"", pattern); + line[DIM(line)-1] = 0; + xfree (pattern); + + parm.ctrl = ctrl; + parm.ctx = ctx; + parm.cb = cb; + parm.cb_value = cb_value; + parm.error = 0; + init_membuf (&parm.data, 4096); + + rc = assuan_transact (ctx, line, lookup_cb, &parm, + NULL, NULL, lookup_status_cb, &parm); + xfree (get_membuf (&parm.data, &len)); + + if (ctx == dirmngr_ctx) + release_dirmngr (ctrl); + else + release_dirmngr2 (ctrl); + + if (rc) + return rc; + return parm.error; +} + + + +static gpg_error_t +get_cached_cert_data_cb (void *opaque, const void *buffer, size_t length) +{ + struct membuf *mb = opaque; + + if (buffer) + put_membuf (mb, buffer, length); + return 0; +} + +/* Return a certificate from the Directory Manager's cache. This + function only returns one certificate which must be specified using + the fingerprint FPR and will be stored at R_CERT. On error NULL is + stored at R_CERT and an error code returned. Note that the caller + must provide the locked dirmngr context CTX. */ +static gpg_error_t +get_cached_cert (assuan_context_t ctx, + const unsigned char *fpr, ksba_cert_t *r_cert) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + char hexfpr[2*20+1]; + struct membuf mb; + char *buf; + size_t buflen; + ksba_cert_t cert; + + *r_cert = NULL; + + bin2hex (fpr, 20, hexfpr); + snprintf (line, DIM(line)-1, "LOOKUP --single --cache-only 0x%s", hexfpr); + + init_membuf (&mb, 4096); + err = assuan_transact (ctx, line, get_cached_cert_data_cb, &mb, + NULL, NULL, NULL, NULL); + buf = get_membuf (&mb, &buflen); + if (err) + { + xfree (buf); + return err; + } + if (!buf) + return gpg_error (GPG_ERR_ENOMEM); + + err = ksba_cert_new (&cert); + if (err) + { + xfree (buf); + return err; + } + err = ksba_cert_init_from_mem (cert, buf, buflen); + xfree (buf); + if (err) + { + log_error ("failed to parse a certificate: %s\n", gpg_strerror (err)); + ksba_cert_release (cert); + return err; + } + + *r_cert = cert; + return 0; +} + + + +/* Run Command helpers*/ + +/* Fairly simple callback to write all output of dirmngr to stdout. */ +static gpg_error_t +run_command_cb (void *opaque, const void *buffer, size_t length) +{ + (void)opaque; + + if (buffer) + { + if ( fwrite (buffer, length, 1, stdout) != 1 ) + log_error ("error writing to stdout: %s\n", strerror (errno)); + } + return 0; +} + +/* Handle inquiries from the dirmngr COMMAND. */ +static gpg_error_t +run_command_inq_cb (void *opaque, const char *line) +{ + struct run_command_parm_s *parm = opaque; + int rc = 0; + + if ( !strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8]) ) + { /* send the given certificate */ + int err; + ksba_cert_t cert; + const unsigned char *der; + size_t derlen; + + line += 8; + if (!*line) + return gpg_error (GPG_ERR_ASS_PARAMETER); + + err = gpgsm_find_cert (line, NULL, &cert); + if (err) + { + log_error ("certificate not found: %s\n", gpg_strerror (err)); + rc = gpg_error (GPG_ERR_NOT_FOUND); + } + else + { + der = ksba_cert_get_image (cert, &derlen); + if (!der) + rc = gpg_error (GPG_ERR_INV_CERT_OBJ); + else + rc = assuan_send_data (parm->ctx, der, derlen); + ksba_cert_release (cert); + } + } + else if ( !strncmp (line, "PRINTINFO", 9) && (line[9] == ' ' || !line[9]) ) + { /* Simply show the message given in the argument. */ + line += 9; + log_info ("dirmngr: %s\n", line); + } + else + { + log_error ("unsupported inquiry `%s'\n", line); + rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); + } + + return rc; +} + +static gpg_error_t +run_command_status_cb (void *opaque, const char *line) +{ + ctrl_t ctrl = opaque; + + if (opt.verbose) + { + log_info ("dirmngr status: %s\n", line); + } + if (!strncmp (line, "PROGRESS", 8) && (line[8]==' ' || !line[8])) + { + if (ctrl) + { + for (line += 8; *line == ' '; line++) + ; + if (gpgsm_status (ctrl, STATUS_PROGRESS, line)) + return gpg_error (GPG_ERR_ASS_CANCELED); + } + } + return 0; +} + + + +/* Pass COMMAND to dirmngr and print all output generated by Dirmngr + to stdout. A couple of inquiries are defined (see above). ARGC + arguments in ARGV are given to the Dirmngr. Spaces, plus and + percent characters within the argument strings are percent escaped + so that blanks can act as delimiters. */ +int +gpgsm_dirmngr_run_command (ctrl_t ctrl, const char *command, + int argc, char **argv) +{ + int rc; + int i; + const char *s; + char *line, *p; + size_t len; + struct run_command_parm_s parm; + + rc = start_dirmngr (ctrl); + if (rc) + return rc; + + parm.ctx = dirmngr_ctx; + + len = strlen (command) + 1; + for (i=0; i < argc; i++) + len += 1 + 3*strlen (argv[i]); /* enough space for percent escaping */ + line = xtrymalloc (len); + if (!line) + { + release_dirmngr (ctrl); + return out_of_core (); + } + + p = stpcpy (line, command); + for (i=0; i < argc; i++) + { + *p++ = ' '; + for (s=argv[i]; *s; s++) + { + if (!isascii (*s)) + *p++ = *s; + else if (*s == ' ') + *p++ = '+'; + else if (!isprint (*s) || *s == '+') + { + sprintf (p, "%%%02X", *(const unsigned char *)s); + p += 3; + } + else + *p++ = *s; + } + } + *p = 0; + + rc = assuan_transact (dirmngr_ctx, line, + run_command_cb, NULL, + run_command_inq_cb, &parm, + run_command_status_cb, ctrl); + xfree (line); + log_info ("response of dirmngr: %s\n", rc? gpg_strerror (rc): "okay"); + release_dirmngr (ctrl); + return rc; +} diff --git a/sm/certchain.c b/sm/certchain.c new file mode 100644 index 0000000..f4ad214 --- /dev/null +++ b/sm/certchain.c @@ -0,0 +1,2044 @@ +/* certchain.c - certificate chain validation + * Copyright (C) 2001, 2002, 2003, 2004, 2005, + * 2006, 2007, 2008 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <stdarg.h> +#include <assert.h> + +#define JNLIB_NEED_LOG_LOGV /* We need log_logv. */ + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */ +#include "i18n.h" +#include "tlv.h" + + +/* Object to keep track of certain root certificates. */ +struct marktrusted_info_s +{ + struct marktrusted_info_s *next; + unsigned char fpr[20]; +}; +static struct marktrusted_info_s *marktrusted_info; + + +/* While running the validation function we want to keep track of the + certificates in the chain. This type is used for that. */ +struct chain_item_s +{ + struct chain_item_s *next; + ksba_cert_t cert; /* The certificate. */ + int is_root; /* The certificate is the root certificate. */ +}; +typedef struct chain_item_s *chain_item_t; + + +static int is_root_cert (ksba_cert_t cert, + const char *issuerdn, const char *subjectdn); +static int get_regtp_ca_info (ctrl_t ctrl, ksba_cert_t cert, int *chainlen); + + +/* This function returns true if we already asked during this session + whether the root certificate CERT shall be marked as trusted. */ +static int +already_asked_marktrusted (ksba_cert_t cert) +{ + unsigned char fpr[20]; + struct marktrusted_info_s *r; + + gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, fpr, NULL); + /* No context switches in the loop! */ + for (r=marktrusted_info; r; r= r->next) + if (!memcmp (r->fpr, fpr, 20)) + return 1; + return 0; +} + +/* Flag certificate CERT as already asked whether it shall be marked + as trusted. */ +static void +set_already_asked_marktrusted (ksba_cert_t cert) +{ + unsigned char fpr[20]; + struct marktrusted_info_s *r; + + gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, fpr, NULL); + for (r=marktrusted_info; r; r= r->next) + if (!memcmp (r->fpr, fpr, 20)) + return; /* Already marked. */ + r = xtrycalloc (1, sizeof *r); + if (!r) + return; + memcpy (r->fpr, fpr, 20); + r->next = marktrusted_info; + marktrusted_info = r; +} + +/* If LISTMODE is true, print FORMAT using LISTMODE to FP. If + LISTMODE is false, use the string to print an log_info or, if + IS_ERROR is true, and log_error. */ +static void +do_list (int is_error, int listmode, estream_t fp, const char *format, ...) +{ + va_list arg_ptr; + + va_start (arg_ptr, format) ; + if (listmode) + { + if (fp) + { + es_fputs (" [", fp); + es_vfprintf (fp, format, arg_ptr); + es_fputs ("]\n", fp); + } + } + else + { + log_logv (is_error? JNLIB_LOG_ERROR: JNLIB_LOG_INFO, format, arg_ptr); + log_printf ("\n"); + } + va_end (arg_ptr); +} + +/* Return 0 if A and B are equal. */ +static int +compare_certs (ksba_cert_t a, ksba_cert_t b) +{ + const unsigned char *img_a, *img_b; + size_t len_a, len_b; + + img_a = ksba_cert_get_image (a, &len_a); + if (!img_a) + return 1; + img_b = ksba_cert_get_image (b, &len_b); + if (!img_b) + return 1; + return !(len_a == len_b && !memcmp (img_a, img_b, len_a)); +} + + +/* Return true if CERT has the validityModel extensions and defines + the use of the chain model. */ +static int +has_validation_model_chain (ksba_cert_t cert, int listmode, estream_t listfp) +{ + gpg_error_t err; + int idx, yes; + const char *oid; + size_t off, derlen, objlen, hdrlen; + const unsigned char *der; + int class, tag, constructed, ndef; + char *oidbuf; + + for (idx=0; !(err=ksba_cert_get_extension (cert, idx, + &oid, NULL, &off, &derlen));idx++) + if (!strcmp (oid, "1.3.6.1.4.1.8301.3.5") ) + break; + if (err) + return 0; /* Not found. */ + der = ksba_cert_get_image (cert, NULL); + if (!der) + { + err = gpg_error (GPG_ERR_INV_OBJ); /* Oops */ + goto leave; + } + der += off; + + err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > derlen || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + derlen = objlen; + err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > derlen || tag != TAG_OBJECT_ID)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + oidbuf = ksba_oid_to_str (der, objlen); + if (!oidbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (opt.verbose) + do_list (0, listmode, listfp, + _("validation model requested by certificate: %s"), + !strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.1")? _("chain") : + !strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.2")? _("shell") : + /* */ oidbuf); + yes = !strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.1"); + ksba_free (oidbuf); + return yes; + + + leave: + log_error ("error parsing validityModel: %s\n", gpg_strerror (err)); + return 0; +} + + + +static int +unknown_criticals (ksba_cert_t cert, int listmode, estream_t fp) +{ + static const char *known[] = { + "2.5.29.15", /* keyUsage */ + "2.5.29.17", /* subjectAltName + Japanese DoCoMo certs mark them as critical. PKIX + only requires them as critical if subjectName is + empty. I don't know whether our code gracefully + handles such empry subjectNames but that is + another story. */ + "2.5.29.19", /* basic Constraints */ + "2.5.29.32", /* certificatePolicies */ + "2.5.29.37", /* extendedKeyUsage - handled by certlist.c */ + "1.3.6.1.4.1.8301.3.5", /* validityModel - handled here. */ + NULL + }; + int rc = 0, i, idx, crit; + const char *oid; + gpg_error_t err; + int unsupported; + strlist_t sl; + + for (idx=0; !(err=ksba_cert_get_extension (cert, idx, + &oid, &crit, NULL, NULL));idx++) + { + if (!crit) + continue; + for (i=0; known[i] && strcmp (known[i],oid); i++) + ; + unsupported = !known[i]; + + /* If this critical extension is not supoported, check the list + of to be ignored extensions to se whether we claim that it is + supported. */ + if (unsupported && opt.ignored_cert_extensions) + { + for (sl=opt.ignored_cert_extensions; + sl && strcmp (sl->d, oid); sl = sl->next) + ; + if (sl) + unsupported = 0; + } + if (unsupported) + { + do_list (1, listmode, fp, + _("critical certificate extension %s is not supported"), + oid); + rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT); + } + } + /* We ignore the error codes EOF as well as no-value. The later will + occur for certificates with no extensions at all. */ + if (err + && gpg_err_code (err) != GPG_ERR_EOF + && gpg_err_code (err) != GPG_ERR_NO_VALUE) + rc = err; + + return rc; +} + + +/* Check whether CERT is an allowed certificate. This requires that + CERT matches all requirements for such a CA, i.e. the + BasicConstraints extension. The function returns 0 on success and + the awlloed length of the chain at CHAINLEN. */ +static int +allowed_ca (ctrl_t ctrl, + ksba_cert_t cert, int *chainlen, int listmode, estream_t fp) +{ + gpg_error_t err; + int flag; + + err = ksba_cert_is_ca (cert, &flag, chainlen); + if (err) + return err; + if (!flag) + { + if (get_regtp_ca_info (ctrl, cert, chainlen)) + { + /* Note that dirmngr takes a different way to cope with such + certs. */ + return 0; /* RegTP issued certificate. */ + } + + do_list (1, listmode, fp,_("issuer certificate is not marked as a CA")); + return gpg_error (GPG_ERR_BAD_CA_CERT); + } + return 0; +} + + +static int +check_cert_policy (ksba_cert_t cert, int listmode, estream_t fplist) +{ + gpg_error_t err; + char *policies; + FILE *fp; + int any_critical; + + err = ksba_cert_get_cert_policies (cert, &policies); + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + return 0; /* No policy given. */ + if (err) + return err; + + /* STRING is a line delimited list of certificate policies as stored + in the certificate. The line itself is colon delimited where the + first field is the OID of the policy and the second field either + N or C for normal or critical extension */ + + if (opt.verbose > 1 && !listmode) + log_info ("certificate's policy list: %s\n", policies); + + /* The check is very minimal but won't give false positives */ + any_critical = !!strstr (policies, ":C"); + + if (!opt.policy_file) + { + xfree (policies); + if (any_critical) + { + do_list (1, listmode, fplist, + _("critical marked policy without configured policies")); + return gpg_error (GPG_ERR_NO_POLICY_MATCH); + } + return 0; + } + + fp = fopen (opt.policy_file, "r"); + if (!fp) + { + if (opt.verbose || errno != ENOENT) + log_info (_("failed to open `%s': %s\n"), + opt.policy_file, strerror (errno)); + xfree (policies); + /* With no critical policies this is only a warning */ + if (!any_critical) + { + if (!opt.quiet) + do_list (0, listmode, fplist, + _("note: non-critical certificate policy not allowed")); + return 0; + } + do_list (1, listmode, fplist, + _("certificate policy not allowed")); + return gpg_error (GPG_ERR_NO_POLICY_MATCH); + } + + for (;;) + { + int c; + char *p, line[256]; + char *haystack, *allowed; + + /* read line */ + do + { + if (!fgets (line, DIM(line)-1, fp) ) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + + xfree (policies); + if (feof (fp)) + { + fclose (fp); + /* With no critical policies this is only a warning */ + if (!any_critical) + { + do_list (0, listmode, fplist, + _("note: non-critical certificate policy not allowed")); + return 0; + } + do_list (1, listmode, fplist, + _("certificate policy not allowed")); + return gpg_error (GPG_ERR_NO_POLICY_MATCH); + } + fclose (fp); + return tmperr; + } + + if (!*line || line[strlen(line)-1] != '\n') + { + /* eat until end of line */ + while ( (c=getc (fp)) != EOF && c != '\n') + ; + fclose (fp); + xfree (policies); + 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 == '#'); + + /* parse line */ + for (allowed=line; spacep (allowed); allowed++) + ; + p = strpbrk (allowed, " :\n"); + if (!*p || p == allowed) + { + fclose (fp); + xfree (policies); + return gpg_error (GPG_ERR_CONFIGURATION); + } + *p = 0; /* strip the rest of the line */ + /* See whether we find ALLOWED (which is an OID) in POLICIES */ + for (haystack=policies; (p=strstr (haystack, allowed)); haystack = p+1) + { + if ( !(p == policies || p[-1] == '\n') ) + continue; /* Does not match the begin of a line. */ + if (p[strlen (allowed)] != ':') + continue; /* The length does not match. */ + /* Yep - it does match so return okay. */ + fclose (fp); + xfree (policies); + return 0; + } + } +} + + +/* Helper function for find_up. This resets the key handle and search + for an issuer ISSUER with a subjectKeyIdentifier of KEYID. Returns + 0 on success or -1 when not found. */ +static int +find_up_search_by_keyid (KEYDB_HANDLE kh, + const char *issuer, ksba_sexp_t keyid) +{ + int rc; + ksba_cert_t cert = NULL; + ksba_sexp_t subj = NULL; + + keydb_search_reset (kh); + while (!(rc = keydb_search_subject (kh, issuer))) + { + ksba_cert_release (cert); cert = NULL; + rc = keydb_get_cert (kh, &cert); + if (rc) + { + log_error ("keydb_get_cert() failed: rc=%d\n", rc); + rc = -1; + break; + } + xfree (subj); + if (!ksba_cert_get_subj_key_id (cert, NULL, &subj)) + { + if (!cmp_simple_canon_sexp (keyid, subj)) + break; /* Found matching cert. */ + } + } + + ksba_cert_release (cert); + xfree (subj); + return rc? -1:0; +} + + +static void +find_up_store_certs_cb (void *cb_value, ksba_cert_t cert) +{ + if (keydb_store_cert (cert, 1, NULL)) + log_error ("error storing issuer certificate as ephemeral\n"); + ++*(int*)cb_value; +} + + +/* Helper for find_up(). Locate the certificate for ISSUER using an + external lookup. KH is the keydb context we are currently using. + On success 0 is returned and the certificate may be retrieved from + the keydb using keydb_get_cert(). KEYID is the keyIdentifier from + the AKI or NULL. */ +static int +find_up_external (ctrl_t ctrl, KEYDB_HANDLE kh, + const char *issuer, ksba_sexp_t keyid) +{ + int rc; + strlist_t names = NULL; + int count = 0; + char *pattern; + const char *s; + + if (opt.verbose) + log_info (_("looking up issuer at external location\n")); + /* The Dirmngr process is confused about unknown attributes. As a + quick and ugly hack we locate the CN and use the issuer string + starting at this attribite. Fixme: we should have far better + parsing for external lookups in the Dirmngr. */ + s = strstr (issuer, "CN="); + if (!s || s == issuer || s[-1] != ',') + s = issuer; + pattern = xtrymalloc (strlen (s)+2); + if (!pattern) + return gpg_error_from_syserror (); + strcpy (stpcpy (pattern, "/"), s); + add_to_strlist (&names, pattern); + xfree (pattern); + + rc = gpgsm_dirmngr_lookup (ctrl, names, 0, find_up_store_certs_cb, &count); + free_strlist (names); + + if (opt.verbose) + log_info (_("number of issuers matching: %d\n"), count); + if (rc) + { + log_error ("external key lookup failed: %s\n", gpg_strerror (rc)); + rc = -1; + } + else if (!count) + rc = -1; + else + { + int old; + /* The issuers are currently stored in the ephemeral key DB, so + we temporary switch to ephemeral mode. */ + old = keydb_set_ephemeral (kh, 1); + if (keyid) + rc = find_up_search_by_keyid (kh, issuer, keyid); + else + { + keydb_search_reset (kh); + rc = keydb_search_subject (kh, issuer); + } + keydb_set_ephemeral (kh, old); + } + return rc; +} + + +/* Helper for find_up(). Ask the dirmngr for the certificate for + ISSUER with optional SERIALNO. KH is the keydb context we are + currently using. With SUBJECT_MODE set, ISSUER is searched as the + subject. On success 0 is returned and the certificate is available + in the ephemeral DB. */ +static int +find_up_dirmngr (ctrl_t ctrl, KEYDB_HANDLE kh, + ksba_sexp_t serialno, const char *issuer, int subject_mode) +{ + int rc; + strlist_t names = NULL; + int count = 0; + char *pattern; + + (void)kh; + + if (opt.verbose) + log_info (_("looking up issuer from the Dirmngr cache\n")); + if (subject_mode) + { + pattern = xtrymalloc (strlen (issuer)+2); + if (pattern) + strcpy (stpcpy (pattern, "/"), issuer); + } + else if (serialno) + pattern = gpgsm_format_sn_issuer (serialno, issuer); + else + { + pattern = xtrymalloc (strlen (issuer)+3); + if (pattern) + strcpy (stpcpy (pattern, "#/"), issuer); + } + if (!pattern) + return gpg_error_from_syserror (); + add_to_strlist (&names, pattern); + xfree (pattern); + + rc = gpgsm_dirmngr_lookup (ctrl, names, 1, find_up_store_certs_cb, &count); + free_strlist (names); + + if (opt.verbose) + log_info (_("number of matching certificates: %d\n"), count); + if (rc && !opt.quiet) + log_info (_("dirmngr cache-only key lookup failed: %s\n"), + gpg_strerror (rc)); + return (!rc && count)? 0 : -1; +} + + + +/* Locate issuing certificate for CERT. ISSUER is the name of the + issuer used as a fallback if the other methods don't work. If + FIND_NEXT is true, the function shall return the next possible + issuer. The certificate itself is not directly returned but a + keydb_get_cert on the keyDb context KH will return it. Returns 0 + on success, -1 if not found or an error code. */ +static int +find_up (ctrl_t ctrl, KEYDB_HANDLE kh, + ksba_cert_t cert, const char *issuer, int find_next) +{ + ksba_name_t authid; + ksba_sexp_t authidno; + ksba_sexp_t keyid; + int rc = -1; + + if (!ksba_cert_get_auth_key_id (cert, &keyid, &authid, &authidno)) + { + const char *s = ksba_name_enum (authid, 0); + if (s && *authidno) + { + rc = keydb_search_issuer_sn (kh, s, authidno); + if (rc) + keydb_search_reset (kh); + + /* In case of an error, try to get the certificate from the + dirmngr. That is done by trying to put that certifcate + into the ephemeral DB and let the code below do the + actual retrieve. Thus there is no error checking. + Skipped in find_next mode as usual. */ + if (rc == -1 && !find_next) + find_up_dirmngr (ctrl, kh, authidno, s, 0); + + /* In case of an error try the ephemeral DB. We can't do + that in find_next mode because we can't keep the search + state then. */ + if (rc == -1 && !find_next) + { + int old = keydb_set_ephemeral (kh, 1); + if (!old) + { + rc = keydb_search_issuer_sn (kh, s, authidno); + if (rc) + keydb_search_reset (kh); + } + keydb_set_ephemeral (kh, old); + } + if (rc) + rc = -1; /* Need to make sure to have this error code. */ + } + + if (rc == -1 && keyid && !find_next) + { + /* Not found by AIK.issuer_sn. Lets try the AIK.ki + instead. Loop over all certificates with that issuer as + subject and stop for the one with a matching + subjectKeyIdentifier. */ + /* Fixme: Should we also search in the dirmngr? */ + rc = find_up_search_by_keyid (kh, issuer, keyid); + if (rc) + { + int old = keydb_set_ephemeral (kh, 1); + if (!old) + rc = find_up_search_by_keyid (kh, issuer, keyid); + keydb_set_ephemeral (kh, old); + } + if (rc) + rc = -1; /* Need to make sure to have this error code. */ + } + + /* If we still didn't found it, try to find it via the subject + from the dirmngr-cache. */ + if (rc == -1 && !find_next) + { + if (!find_up_dirmngr (ctrl, kh, NULL, issuer, 1)) + { + int old = keydb_set_ephemeral (kh, 1); + if (keyid) + rc = find_up_search_by_keyid (kh, issuer, keyid); + else + { + keydb_search_reset (kh); + rc = keydb_search_subject (kh, issuer); + } + keydb_set_ephemeral (kh, old); + } + if (rc) + rc = -1; /* Need to make sure to have this error code. */ + } + + /* If we still didn't found it, try an external lookup. */ + if (rc == -1 && opt.auto_issuer_key_retrieve && !find_next) + rc = find_up_external (ctrl, kh, issuer, keyid); + + /* Print a note so that the user does not feel too helpless when + an issuer certificate was found and gpgsm prints BAD + signature because it is not the correct one. */ + if (rc == -1 && opt.quiet) + ; + else if (rc == -1) + { + log_info ("%sissuer certificate ", find_next?"next ":""); + if (keyid) + { + log_printf ("{"); + gpgsm_dump_serial (keyid); + log_printf ("} "); + } + if (authidno) + { + log_printf ("(#"); + gpgsm_dump_serial (authidno); + log_printf ("/"); + gpgsm_dump_string (s); + log_printf (") "); + } + log_printf ("not found using authorityKeyIdentifier\n"); + } + else if (rc) + log_error ("failed to find authorityKeyIdentifier: rc=%d\n", rc); + xfree (keyid); + ksba_name_release (authid); + xfree (authidno); + } + + if (rc) /* Not found via authorithyKeyIdentifier, try regular issuer name. */ + rc = keydb_search_subject (kh, issuer); + if (rc == -1 && !find_next) + { + int old; + + /* Also try to get it from the Dirmngr cache. The function + merely puts it into the ephemeral database. */ + find_up_dirmngr (ctrl, kh, NULL, issuer, 0); + + /* Not found, let us see whether we have one in the ephemeral key DB. */ + old = keydb_set_ephemeral (kh, 1); + if (!old) + { + keydb_search_reset (kh); + rc = keydb_search_subject (kh, issuer); + } + keydb_set_ephemeral (kh, old); + } + + /* Still not found. If enabled, try an external lookup. */ + if (rc == -1 && opt.auto_issuer_key_retrieve && !find_next) + rc = find_up_external (ctrl, kh, issuer, NULL); + + return rc; +} + + +/* Return the next certificate up in the chain starting at START. + Returns -1 when there are no more certificates. */ +int +gpgsm_walk_cert_chain (ctrl_t ctrl, ksba_cert_t start, ksba_cert_t *r_next) +{ + int rc = 0; + char *issuer = NULL; + char *subject = NULL; + KEYDB_HANDLE kh = keydb_new (0); + + *r_next = NULL; + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + issuer = ksba_cert_get_issuer (start, 0); + subject = ksba_cert_get_subject (start, 0); + if (!issuer) + { + log_error ("no issuer found in certificate\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + if (!subject) + { + log_error ("no subject found in certificate\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + + if (is_root_cert (start, issuer, subject)) + { + rc = -1; /* we are at the root */ + goto leave; + } + + rc = find_up (ctrl, kh, start, issuer, 0); + if (rc) + { + /* It is quite common not to have a certificate, so better don't + print an error here. */ + if (rc != -1 && opt.verbose > 1) + log_error ("failed to find issuer's certificate: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_MISSING_ISSUER_CERT); + goto leave; + } + + rc = keydb_get_cert (kh, r_next); + if (rc) + { + log_error ("keydb_get_cert() failed: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_GENERAL); + } + + leave: + xfree (issuer); + xfree (subject); + keydb_release (kh); + return rc; +} + + +/* Helper for gpgsm_is_root_cert. This one is used if the subject and + issuer DNs are already known. */ +static int +is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn) +{ + gpg_error_t err; + int result = 0; + ksba_sexp_t serialno; + ksba_sexp_t ak_keyid; + ksba_name_t ak_name; + ksba_sexp_t ak_sn; + const char *ak_name_str; + ksba_sexp_t subj_keyid = NULL; + + if (!issuerdn || !subjectdn) + return 0; /* No. */ + + if (strcmp (issuerdn, subjectdn)) + return 0; /* No. */ + + err = ksba_cert_get_auth_key_id (cert, &ak_keyid, &ak_name, &ak_sn); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + return 1; /* Yes. Without a authorityKeyIdentifier this needs + to be the Root certifcate (our trust anchor). */ + log_error ("error getting authorityKeyIdentifier: %s\n", + gpg_strerror (err)); + return 0; /* Well, it is broken anyway. Return No. */ + } + + serialno = ksba_cert_get_serial (cert); + if (!serialno) + { + log_error ("error getting serialno: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Check whether the auth name's matches the issuer name+sn. If + that is the case this is a root certificate. */ + ak_name_str = ksba_name_enum (ak_name, 0); + if (ak_name_str + && !strcmp (ak_name_str, issuerdn) + && !cmp_simple_canon_sexp (ak_sn, serialno)) + { + result = 1; /* Right, CERT is self-signed. */ + goto leave; + } + + /* Similar for the ak_keyid. */ + if (ak_keyid && !ksba_cert_get_subj_key_id (cert, NULL, &subj_keyid) + && !cmp_simple_canon_sexp (ak_keyid, subj_keyid)) + { + result = 1; /* Right, CERT is self-signed. */ + goto leave; + } + + + leave: + ksba_free (subj_keyid); + ksba_free (ak_keyid); + ksba_name_release (ak_name); + ksba_free (ak_sn); + ksba_free (serialno); + return result; +} + + + +/* Check whether the CERT is a root certificate. Returns True if this + is the case. */ +int +gpgsm_is_root_cert (ksba_cert_t cert) +{ + char *issuer; + char *subject; + int yes; + + issuer = ksba_cert_get_issuer (cert, 0); + subject = ksba_cert_get_subject (cert, 0); + yes = is_root_cert (cert, issuer, subject); + xfree (issuer); + xfree (subject); + return yes; +} + + +/* This is a helper for gpgsm_validate_chain. */ +static gpg_error_t +is_cert_still_valid (ctrl_t ctrl, int force_ocsp, int lm, estream_t fp, + ksba_cert_t subject_cert, ksba_cert_t issuer_cert, + int *any_revoked, int *any_no_crl, int *any_crl_too_old) +{ + gpg_error_t err; + + if (opt.no_crl_check && !ctrl->use_ocsp) + { + audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK, + gpg_error (GPG_ERR_NOT_ENABLED)); + return 0; + } + + err = gpgsm_dirmngr_isvalid (ctrl, + subject_cert, issuer_cert, + force_ocsp? 2 : !!ctrl->use_ocsp); + audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK, err); + + if (err) + { + if (!lm) + gpgsm_cert_log_name (NULL, subject_cert); + switch (gpg_err_code (err)) + { + case GPG_ERR_CERT_REVOKED: + do_list (1, lm, fp, _("certificate has been revoked")); + *any_revoked = 1; + /* Store that in the keybox so that key listings are able to + return the revoked flag. We don't care about error, + though. */ + keydb_set_cert_flags (subject_cert, 1, KEYBOX_FLAG_VALIDITY, 0, + ~0, VALIDITY_REVOKED); + break; + + case GPG_ERR_NO_CRL_KNOWN: + do_list (1, lm, fp, _("no CRL found for certificate")); + *any_no_crl = 1; + break; + + case GPG_ERR_NO_DATA: + do_list (1, lm, fp, _("the status of the certificate is unknown")); + *any_no_crl = 1; + break; + + case GPG_ERR_CRL_TOO_OLD: + do_list (1, lm, fp, _("the available CRL is too old")); + if (!lm) + log_info (_("please make sure that the " + "\"dirmngr\" is properly installed\n")); + *any_crl_too_old = 1; + break; + + default: + do_list (1, lm, fp, _("checking the CRL failed: %s"), + gpg_strerror (err)); + return err; + } + } + return 0; +} + + +/* Helper for gpgsm_validate_chain to check the validity period of + SUBJECT_CERT. The caller needs to pass EXPTIME which will be + updated to the nearest expiration time seen. A DEPTH of 0 indicates + the target certifciate, -1 the final root certificate and other + values intermediate certificates. */ +static gpg_error_t +check_validity_period (ksba_isotime_t current_time, + ksba_cert_t subject_cert, + ksba_isotime_t exptime, + int listmode, estream_t listfp, int depth) +{ + gpg_error_t err; + ksba_isotime_t not_before, not_after; + + err = ksba_cert_get_validity (subject_cert, 0, not_before); + if (!err) + err = ksba_cert_get_validity (subject_cert, 1, not_after); + if (err) + { + do_list (1, listmode, listfp, + _("certificate with invalid validity: %s"), gpg_strerror (err)); + return gpg_error (GPG_ERR_BAD_CERT); + } + + if (*not_after) + { + if (!*exptime) + gnupg_copy_time (exptime, not_after); + else if (strcmp (not_after, exptime) < 0 ) + gnupg_copy_time (exptime, not_after); + } + + if (*not_before && strcmp (current_time, not_before) < 0 ) + { + do_list (1, listmode, listfp, + depth == 0 ? _("certificate not yet valid") : + depth == -1 ? _("root certificate not yet valid") : + /* other */ _("intermediate certificate not yet valid")); + if (!listmode) + { + log_info (" (valid from "); + dump_isotime (not_before); + log_printf (")\n"); + } + return gpg_error (GPG_ERR_CERT_TOO_YOUNG); + } + + if (*not_after && strcmp (current_time, not_after) > 0 ) + { + do_list (opt.ignore_expiration?0:1, listmode, listfp, + depth == 0 ? _("certificate has expired") : + depth == -1 ? _("root certificate has expired") : + /* other */ _("intermediate certificate has expired")); + if (!listmode) + { + log_info (" (expired at "); + dump_isotime (not_after); + log_printf (")\n"); + } + if (opt.ignore_expiration) + log_info ("WARNING: ignoring expiration\n"); + else + return gpg_error (GPG_ERR_CERT_EXPIRED); + } + + return 0; +} + +/* This is a variant of check_validity_period used with the chain + model. The dextra contraint here is that notBefore and notAfter + must exists and if the additional argument CHECK_TIME is given this + time is used to check the validity period of SUBJECT_CERT. */ +static gpg_error_t +check_validity_period_cm (ksba_isotime_t current_time, + ksba_isotime_t check_time, + ksba_cert_t subject_cert, + ksba_isotime_t exptime, + int listmode, estream_t listfp, int depth) +{ + gpg_error_t err; + ksba_isotime_t not_before, not_after; + + err = ksba_cert_get_validity (subject_cert, 0, not_before); + if (!err) + err = ksba_cert_get_validity (subject_cert, 1, not_after); + if (err) + { + do_list (1, listmode, listfp, + _("certificate with invalid validity: %s"), gpg_strerror (err)); + return gpg_error (GPG_ERR_BAD_CERT); + } + if (!*not_before || !*not_after) + { + do_list (1, listmode, listfp, + _("required certificate attributes missing: %s%s%s"), + !*not_before? "notBefore":"", + (!*not_before && !*not_after)? ", ":"", + !*not_before? "notAfter":""); + return gpg_error (GPG_ERR_BAD_CERT); + } + if (strcmp (not_before, not_after) > 0 ) + { + do_list (1, listmode, listfp, + _("certificate with invalid validity")); + log_info (" (valid from "); + dump_isotime (not_before); + log_printf (" expired at "); + dump_isotime (not_after); + log_printf (")\n"); + return gpg_error (GPG_ERR_BAD_CERT); + } + + if (!*exptime) + gnupg_copy_time (exptime, not_after); + else if (strcmp (not_after, exptime) < 0 ) + gnupg_copy_time (exptime, not_after); + + if (strcmp (current_time, not_before) < 0 ) + { + do_list (1, listmode, listfp, + depth == 0 ? _("certificate not yet valid") : + depth == -1 ? _("root certificate not yet valid") : + /* other */ _("intermediate certificate not yet valid")); + if (!listmode) + { + log_info (" (valid from "); + dump_isotime (not_before); + log_printf (")\n"); + } + return gpg_error (GPG_ERR_CERT_TOO_YOUNG); + } + + if (*check_time + && (strcmp (check_time, not_before) < 0 + || strcmp (check_time, not_after) > 0)) + { + /* Note that we don't need a case for the root certificate + because its own consitency has already been checked. */ + do_list(opt.ignore_expiration?0:1, listmode, listfp, + depth == 0 ? + _("signature not created during lifetime of certificate") : + depth == 1 ? + _("certificate not created during lifetime of issuer") : + _("intermediate certificate not created during lifetime " + "of issuer")); + if (!listmode) + { + log_info (depth== 0? _(" ( signature created at ") : + /* */ _(" (certificate created at ") ); + dump_isotime (check_time); + log_printf (")\n"); + log_info (depth==0? _(" (certificate valid from ") : + /* */ _(" ( issuer valid from ") ); + dump_isotime (not_before); + log_info (" to "); + dump_isotime (not_after); + log_printf (")\n"); + } + if (opt.ignore_expiration) + log_info ("WARNING: ignoring expiration\n"); + else + return gpg_error (GPG_ERR_CERT_EXPIRED); + } + + return 0; +} + + + +/* Ask the user whether he wants to mark the certificate CERT trusted. + Returns true if the CERT is the trusted. We also check whether the + agent is at all enabled to allow marktrusted and don't call it in + this session again if it is not. */ +static int +ask_marktrusted (ctrl_t ctrl, ksba_cert_t cert, int listmode) +{ + static int no_more_questions; + int rc; + char *fpr; + int success = 0; + + fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1); + log_info (_("fingerprint=%s\n"), fpr? fpr : "?"); + xfree (fpr); + + if (no_more_questions) + rc = gpg_error (GPG_ERR_NOT_SUPPORTED); + else + rc = gpgsm_agent_marktrusted (ctrl, cert); + if (!rc) + { + log_info (_("root certificate has now been marked as trusted\n")); + success = 1; + } + else if (!listmode) + { + gpgsm_dump_cert ("issuer", cert); + log_info ("after checking the fingerprint, you may want " + "to add it manually to the list of trusted certificates.\n"); + } + + if (gpg_err_code (rc) == GPG_ERR_NOT_SUPPORTED) + { + if (!no_more_questions) + log_info (_("interactive marking as trusted " + "not enabled in gpg-agent\n")); + no_more_questions = 1; + } + else if (gpg_err_code (rc) == GPG_ERR_CANCELED) + { + log_info (_("interactive marking as trusted " + "disabled for this session\n")); + no_more_questions = 1; + } + else + set_already_asked_marktrusted (cert); + + return success; +} + + + + +/* Validate a chain and optionally return the nearest expiration time + in R_EXPTIME. With LISTMODE set to 1 a special listmode is + activated where only information about the certificate is printed + to LISTFP and no output is send to the usual log stream. If + CHECKTIME_ARG is set, it is used only in the chain model instead of the + current time. + + Defined flag bits + + VALIDATE_FLAG_NO_DIRMNGR - Do not do any dirmngr isvalid checks. + VALIDATE_FLAG_CHAIN_MODEL - Check according to chain model. +*/ +static int +do_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime_arg, + ksba_isotime_t r_exptime, + int listmode, estream_t listfp, unsigned int flags, + struct rootca_flags_s *rootca_flags) +{ + int rc = 0, depth, maxdepth; + char *issuer = NULL; + char *subject = NULL; + KEYDB_HANDLE kh = NULL; + ksba_cert_t subject_cert = NULL, issuer_cert = NULL; + ksba_isotime_t current_time; + ksba_isotime_t check_time; + ksba_isotime_t exptime; + int any_expired = 0; + int any_revoked = 0; + int any_no_crl = 0; + int any_crl_too_old = 0; + int any_no_policy_match = 0; + int is_qualified = -1; /* Indicates whether the certificate stems + from a qualified root certificate. + -1 = unknown, 0 = no, 1 = yes. */ + chain_item_t chain = NULL; /* A list of all certificates in the chain. */ + + + gnupg_get_isotime (current_time); + + if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) ) + { + if (!strcmp (checktime_arg, "19700101T000000")) + { + do_list (1, listmode, listfp, + _("WARNING: creation time of signature not known - " + "assuming current time")); + gnupg_copy_time (check_time, current_time); + } + else + gnupg_copy_time (check_time, checktime_arg); + } + else + *check_time = 0; + + if (r_exptime) + *r_exptime = 0; + *exptime = 0; + + if (opt.no_chain_validation && !listmode) + { + log_info ("WARNING: bypassing certificate chain validation\n"); + return 0; + } + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + if (DBG_X509 && !listmode) + gpgsm_dump_cert ("target", cert); + + subject_cert = cert; + ksba_cert_ref (subject_cert); + maxdepth = 50; + depth = 0; + + for (;;) + { + int is_root; + gpg_error_t istrusted_rc = -1; + + /* Put the certificate on our list. */ + { + chain_item_t ci; + + ci = xtrycalloc (1, sizeof *ci); + if (!ci) + { + rc = gpg_error_from_syserror (); + goto leave; + } + ksba_cert_ref (subject_cert); + ci->cert = subject_cert; + ci->next = chain; + chain = ci; + } + + xfree (issuer); + xfree (subject); + issuer = ksba_cert_get_issuer (subject_cert, 0); + subject = ksba_cert_get_subject (subject_cert, 0); + + if (!issuer) + { + do_list (1, listmode, listfp, _("no issuer found in certificate")); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + + + /* Is this a self-issued certificate (i.e. the root certificate)? */ + is_root = is_root_cert (subject_cert, issuer, subject); + if (is_root) + { + chain->is_root = 1; + /* Check early whether the certificate is listed as trusted. + We used to do this only later but changed it to call the + check right here so that we can access special flags + associated with that specific root certificate. */ + istrusted_rc = gpgsm_agent_istrusted (ctrl, subject_cert, NULL, + rootca_flags); + audit_log_cert (ctrl->audit, AUDIT_ROOT_TRUSTED, + subject_cert, istrusted_rc); + /* If the chain model extended attribute is used, make sure + that our chain model flag is set. */ + if (has_validation_model_chain (subject_cert, listmode, listfp)) + rootca_flags->chain_model = 1; + } + + + /* Check the validity period. */ + if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) ) + rc = check_validity_period_cm (current_time, check_time, subject_cert, + exptime, listmode, listfp, + (depth && is_root)? -1: depth); + else + rc = check_validity_period (current_time, subject_cert, + exptime, listmode, listfp, + (depth && is_root)? -1: depth); + if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED) + { + any_expired = 1; + rc = 0; + } + else if (rc) + goto leave; + + + /* Assert that we understand all critical extensions. */ + rc = unknown_criticals (subject_cert, listmode, listfp); + if (rc) + goto leave; + + /* Do a policy check. */ + if (!opt.no_policy_check) + { + rc = check_cert_policy (subject_cert, listmode, listfp); + if (gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH) + { + any_no_policy_match = 1; + rc = 1; + } + else if (rc) + goto leave; + } + + + /* If this is the root certificate we are at the end of the chain. */ + if (is_root) + { + if (!istrusted_rc) + ; /* No need to check the certificate for a trusted one. */ + else if (gpgsm_check_cert_sig (subject_cert, subject_cert) ) + { + /* We only check the signature if the certificate is not + trusted for better diagnostics. */ + do_list (1, listmode, listfp, + _("self-signed certificate has a BAD signature")); + if (DBG_X509) + { + gpgsm_dump_cert ("self-signing cert", subject_cert); + } + rc = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN + : GPG_ERR_BAD_CERT); + goto leave; + } + if (!rootca_flags->relax) + { + rc = allowed_ca (ctrl, subject_cert, NULL, listmode, listfp); + if (rc) + goto leave; + } + + + /* Set the flag for qualified signatures. This flag is + deduced from a list of root certificates allowed for + qualified signatures. */ + if (is_qualified == -1) + { + gpg_error_t err; + size_t buflen; + char buf[1]; + + if (!ksba_cert_get_user_data (cert, "is_qualified", + &buf, sizeof (buf), + &buflen) && buflen) + { + /* We already checked this for this certificate, + thus we simply take it from the user data. */ + is_qualified = !!*buf; + } + else + { + /* Need to consult the list of root certificates for + qualified signatures. */ + err = gpgsm_is_in_qualified_list (ctrl, subject_cert, NULL); + if (!err) + is_qualified = 1; + else if ( gpg_err_code (err) == GPG_ERR_NOT_FOUND) + is_qualified = 0; + else + log_error ("checking the list of qualified " + "root certificates failed: %s\n", + gpg_strerror (err)); + if ( is_qualified != -1 ) + { + /* Cache the result but don't care too much + about an error. */ + buf[0] = !!is_qualified; + err = ksba_cert_set_user_data (subject_cert, + "is_qualified", buf, 1); + if (err) + log_error ("set_user_data(is_qualified) failed: %s\n", + gpg_strerror (err)); + } + } + } + + + /* Act on the check for a trusted root certificates. */ + rc = istrusted_rc; + if (!rc) + ; + else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED) + { + do_list (0, listmode, listfp, + _("root certificate is not marked trusted")); + /* If we already figured out that the certificate is + expired it does not make much sense to ask the user + whether we wants to trust the root certificate. We + should do this only if the certificate under question + will then be usable. */ + if ( !any_expired + && (!listmode || !already_asked_marktrusted (subject_cert)) + && ask_marktrusted (ctrl, subject_cert, listmode) ) + rc = 0; + } + else + { + log_error (_("checking the trust list failed: %s\n"), + gpg_strerror (rc)); + } + + if (rc) + goto leave; + + /* Check for revocations etc. */ + if ((flags & VALIDATE_FLAG_NO_DIRMNGR)) + ; + else if (opt.no_trusted_cert_crl_check || rootca_flags->relax) + ; + else + rc = is_cert_still_valid (ctrl, + (flags & VALIDATE_FLAG_CHAIN_MODEL), + listmode, listfp, + subject_cert, subject_cert, + &any_revoked, &any_no_crl, + &any_crl_too_old); + if (rc) + goto leave; + + break; /* Okay: a self-signed certicate is an end-point. */ + } /* End is_root. */ + + + /* Take care that the chain does not get too long. */ + if ((depth+1) > maxdepth) + { + do_list (1, listmode, listfp, _("certificate chain too long\n")); + rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); + goto leave; + } + + /* Find the next cert up the tree. */ + keydb_search_reset (kh); + rc = find_up (ctrl, kh, subject_cert, issuer, 0); + if (rc) + { + if (rc == -1) + { + do_list (0, listmode, listfp, _("issuer certificate not found")); + if (!listmode) + { + log_info ("issuer certificate: #/"); + gpgsm_dump_string (issuer); + log_printf ("\n"); + } + } + else + log_error ("failed to find issuer's certificate: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_MISSING_ISSUER_CERT); + goto leave; + } + + ksba_cert_release (issuer_cert); issuer_cert = NULL; + rc = keydb_get_cert (kh, &issuer_cert); + if (rc) + { + log_error ("keydb_get_cert() failed: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + try_another_cert: + if (DBG_X509) + { + log_debug ("got issuer's certificate:\n"); + gpgsm_dump_cert ("issuer", issuer_cert); + } + + rc = gpgsm_check_cert_sig (issuer_cert, subject_cert); + if (rc) + { + do_list (0, listmode, listfp, _("certificate has a BAD signature")); + if (DBG_X509) + { + gpgsm_dump_cert ("signing issuer", issuer_cert); + gpgsm_dump_cert ("signed subject", subject_cert); + } + if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE) + { + /* We now try to find other issuer certificates which + might have been used. This is required because some + CAs are reusing the issuer and subject DN for new + root certificates. */ + /* FIXME: Do this only if we don't have an + AKI.keyIdentifier */ + rc = find_up (ctrl, kh, subject_cert, issuer, 1); + if (!rc) + { + ksba_cert_t tmp_cert; + + rc = keydb_get_cert (kh, &tmp_cert); + if (rc || !compare_certs (issuer_cert, tmp_cert)) + { + /* The find next did not work or returned an + identical certificate. We better stop here + to avoid infinite checks. */ + rc = gpg_error (GPG_ERR_BAD_SIGNATURE); + ksba_cert_release (tmp_cert); + } + else + { + do_list (0, listmode, listfp, + _("found another possible matching " + "CA certificate - trying again")); + ksba_cert_release (issuer_cert); + issuer_cert = tmp_cert; + goto try_another_cert; + } + } + } + + /* We give a more descriptive error code than the one + returned from the signature checking. */ + rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); + goto leave; + } + + is_root = gpgsm_is_root_cert (issuer_cert); + istrusted_rc = -1; + + + /* Check that a CA is allowed to issue certificates. */ + { + int chainlen; + + rc = allowed_ca (ctrl, issuer_cert, &chainlen, listmode, listfp); + if (rc) + { + /* Not allowed. Check whether this is a trusted root + certificate and whether we allow special exceptions. + We could carry the result of the test over to the + regular root check at the top of the loop but for + clarity we won't do that. Given that the majority of + certificates carry proper BasicContraints our way of + overriding an error in the way is justified for + performance reasons. */ + if (is_root) + { + istrusted_rc = gpgsm_agent_istrusted (ctrl, issuer_cert, NULL, + rootca_flags); + if (!istrusted_rc && rootca_flags->relax) + { + /* Ignore the error due to the relax flag. */ + rc = 0; + chainlen = -1; + } + } + } + if (rc) + goto leave; + if (chainlen >= 0 && depth > chainlen) + { + do_list (1, listmode, listfp, + _("certificate chain longer than allowed by CA (%d)"), + chainlen); + rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); + goto leave; + } + } + + /* Is the certificate allowed to sign other certificates. */ + if (!listmode) + { + rc = gpgsm_cert_use_cert_p (issuer_cert); + if (rc) + { + char numbuf[50]; + sprintf (numbuf, "%d", rc); + gpgsm_status2 (ctrl, STATUS_ERROR, "certcert.issuer.keyusage", + numbuf, NULL); + goto leave; + } + } + + /* Check for revocations etc. Note that for a root certificate + this test is done a second time later. This should eventually + be fixed. */ + if ((flags & VALIDATE_FLAG_NO_DIRMNGR)) + rc = 0; + else if (is_root && (opt.no_trusted_cert_crl_check + || (!istrusted_rc && rootca_flags->relax))) + rc = 0; + else + rc = is_cert_still_valid (ctrl, + (flags & VALIDATE_FLAG_CHAIN_MODEL), + listmode, listfp, + subject_cert, issuer_cert, + &any_revoked, &any_no_crl, &any_crl_too_old); + if (rc) + goto leave; + + + if (opt.verbose && !listmode) + log_info (depth == 0 ? _("certificate is good\n") : + !is_root ? _("intermediate certificate is good\n") : + /* other */ _("root certificate is good\n")); + + /* Under the chain model the next check time is the creation + time of the subject certificate. */ + if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) ) + { + rc = ksba_cert_get_validity (subject_cert, 0, check_time); + if (rc) + { + /* That will never happen as we have already checked + this above. */ + BUG (); + } + } + + /* For the next round the current issuer becomes the new subject. */ + keydb_search_reset (kh); + ksba_cert_release (subject_cert); + subject_cert = issuer_cert; + issuer_cert = NULL; + depth++; + } /* End chain traversal. */ + + if (!listmode && !opt.quiet) + { + if (opt.no_policy_check) + log_info ("policies not checked due to %s option\n", + "--disable-policy-checks"); + if (opt.no_crl_check && !ctrl->use_ocsp) + log_info ("CRLs not checked due to %s option\n", + "--disable-crl-checks"); + } + + if (!rc) + { /* If we encountered an error somewhere during the checks, set + the error code to the most critical one */ + if (any_revoked) + rc = gpg_error (GPG_ERR_CERT_REVOKED); + else if (any_expired) + rc = gpg_error (GPG_ERR_CERT_EXPIRED); + else if (any_no_crl) + rc = gpg_error (GPG_ERR_NO_CRL_KNOWN); + else if (any_crl_too_old) + rc = gpg_error (GPG_ERR_CRL_TOO_OLD); + else if (any_no_policy_match) + rc = gpg_error (GPG_ERR_NO_POLICY_MATCH); + } + + leave: + /* If we have traversed a complete chain up to the root we will + reset the ephemeral flag for all these certificates. This is done + regardless of any error because those errors may only be + transient. */ + if (chain && chain->is_root) + { + gpg_error_t err; + chain_item_t ci; + + for (ci = chain; ci; ci = ci->next) + { + /* Note that it is possible for the last certificate in the + chain (i.e. our target certificate) that it has not yet + been stored in the keybox and thus the flag can't be set. + We ignore this error becuase it will later be stored + anyway. */ + err = keydb_set_cert_flags (ci->cert, 1, KEYBOX_FLAG_BLOB, 0, + KEYBOX_FLAG_BLOB_EPHEMERAL, 0); + if (!ci->next && gpg_err_code (err) == GPG_ERR_NOT_FOUND) + ; + else if (err) + log_error ("clearing ephemeral flag failed: %s\n", + gpg_strerror (err)); + } + } + + /* If we have figured something about the qualified signature + capability of the certificate under question, store the result as + user data in all certificates of the chain. We do this even if the + validation itself failed. */ + if (is_qualified != -1) + { + gpg_error_t err; + chain_item_t ci; + char buf[1]; + + buf[0] = !!is_qualified; + + for (ci = chain; ci; ci = ci->next) + { + err = ksba_cert_set_user_data (ci->cert, "is_qualified", buf, 1); + if (err) + { + log_error ("set_user_data(is_qualified) failed: %s\n", + gpg_strerror (err)); + if (!rc) + rc = err; + } + } + } + + /* If auditing has been enabled, record what is in the chain. */ + if (ctrl->audit) + { + chain_item_t ci; + + audit_log (ctrl->audit, AUDIT_CHAIN_BEGIN); + for (ci = chain; ci; ci = ci->next) + { + audit_log_cert (ctrl->audit, + ci->is_root? AUDIT_CHAIN_ROOTCERT : AUDIT_CHAIN_CERT, + ci->cert, 0); + } + audit_log (ctrl->audit, AUDIT_CHAIN_END); + } + + if (r_exptime) + gnupg_copy_time (r_exptime, exptime); + xfree (issuer); + xfree (subject); + keydb_release (kh); + while (chain) + { + chain_item_t ci_next = chain->next; + ksba_cert_release (chain->cert); + xfree (chain); + chain = ci_next; + } + ksba_cert_release (issuer_cert); + ksba_cert_release (subject_cert); + return rc; +} + + +/* Validate a certificate chain. For a description see + do_validate_chain. This function is a wrapper to handle a root + certificate with the chain_model flag set. If RETFLAGS is not + NULL, flags indicating now the verification was done are stored + there. The only defined flag for RETFLAGS is + VALIDATE_FLAG_CHAIN_MODEL. + + If you are verifying a signature you should set CHECKTIME to the + creation time of the signature. If your are verifying a + certificate, set it nil (i.e. the empty string). If the creation + date of the signature is not known use the special date + "19700101T000000" which is treated in a special way here. */ +int +gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime, + ksba_isotime_t r_exptime, + int listmode, estream_t listfp, unsigned int flags, + unsigned int *retflags) +{ + int rc; + struct rootca_flags_s rootca_flags; + unsigned int dummy_retflags; + + if (!retflags) + retflags = &dummy_retflags; + + if (ctrl->validation_model == 1) + flags |= VALIDATE_FLAG_CHAIN_MODEL; + + *retflags = (flags & VALIDATE_FLAG_CHAIN_MODEL); + memset (&rootca_flags, 0, sizeof rootca_flags); + + rc = do_validate_chain (ctrl, cert, checktime, + r_exptime, listmode, listfp, flags, + &rootca_flags); + if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED + && !(flags & VALIDATE_FLAG_CHAIN_MODEL) + && (rootca_flags.valid && rootca_flags.chain_model)) + { + do_list (0, listmode, listfp, _("switching to chain model")); + rc = do_validate_chain (ctrl, cert, checktime, + r_exptime, listmode, listfp, + (flags |= VALIDATE_FLAG_CHAIN_MODEL), + &rootca_flags); + *retflags |= VALIDATE_FLAG_CHAIN_MODEL; + } + + if (opt.verbose) + do_list (0, listmode, listfp, _("validation model used: %s"), + (*retflags & VALIDATE_FLAG_CHAIN_MODEL)? + _("chain"):_("shell")); + + return rc; +} + + +/* Check that the given certificate is valid but DO NOT check any + constraints. We assume that the issuers certificate is already in + the DB and that this one is valid; which it should be because it + has been checked using this function. */ +int +gpgsm_basic_cert_check (ctrl_t ctrl, ksba_cert_t cert) +{ + int rc = 0; + char *issuer = NULL; + char *subject = NULL; + KEYDB_HANDLE kh; + ksba_cert_t issuer_cert = NULL; + + if (opt.no_chain_validation) + { + log_info ("WARNING: bypassing basic certificate checks\n"); + return 0; + } + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + issuer = ksba_cert_get_issuer (cert, 0); + subject = ksba_cert_get_subject (cert, 0); + if (!issuer) + { + log_error ("no issuer found in certificate\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + + if (is_root_cert (cert, issuer, subject)) + { + rc = gpgsm_check_cert_sig (cert, cert); + if (rc) + { + log_error ("self-signed certificate has a BAD signature: %s\n", + gpg_strerror (rc)); + if (DBG_X509) + { + gpgsm_dump_cert ("self-signing cert", cert); + } + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + } + else + { + /* Find the next cert up the tree. */ + keydb_search_reset (kh); + rc = find_up (ctrl, kh, cert, issuer, 0); + if (rc) + { + if (rc == -1) + { + log_info ("issuer certificate (#/"); + gpgsm_dump_string (issuer); + log_printf (") not found\n"); + } + else + log_error ("failed to find issuer's certificate: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_MISSING_ISSUER_CERT); + goto leave; + } + + ksba_cert_release (issuer_cert); issuer_cert = NULL; + rc = keydb_get_cert (kh, &issuer_cert); + if (rc) + { + log_error ("keydb_get_cert() failed: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + rc = gpgsm_check_cert_sig (issuer_cert, cert); + if (rc) + { + log_error ("certificate has a BAD signature: %s\n", + gpg_strerror (rc)); + if (DBG_X509) + { + gpgsm_dump_cert ("signing issuer", issuer_cert); + gpgsm_dump_cert ("signed subject", cert); + } + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + if (opt.verbose) + log_info (_("certificate is good\n")); + } + + leave: + xfree (issuer); + xfree (subject); + keydb_release (kh); + ksba_cert_release (issuer_cert); + return rc; +} + + + +/* Check whether the certificate CERT has been issued by the German + authority for qualified signature. They do not set the + basicConstraints and thus we need this workaround. It works by + looking up the root certificate and checking whether that one is + listed as a qualified certificate for Germany. + + We also try to cache this data but as long as don't keep a + reference to the certificate this won't be used. + + Returns: True if CERT is a RegTP issued CA cert (i.e. the root + certificate itself or one of the CAs). In that case CHAINLEN will + receive the length of the chain which is either 0 or 1. +*/ +static int +get_regtp_ca_info (ctrl_t ctrl, ksba_cert_t cert, int *chainlen) +{ + gpg_error_t err; + ksba_cert_t next; + int rc = 0; + int i, depth; + char country[3]; + ksba_cert_t array[4]; + char buf[2]; + size_t buflen; + int dummy_chainlen; + + if (!chainlen) + chainlen = &dummy_chainlen; + + *chainlen = 0; + err = ksba_cert_get_user_data (cert, "regtp_ca_chainlen", + &buf, sizeof (buf), &buflen); + if (!err) + { + /* Got info. */ + if (buflen < 2 || !*buf) + return 0; /* Nothing found. */ + *chainlen = buf[1]; + return 1; /* This is a regtp CA. */ + } + else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) + { + log_error ("ksba_cert_get_user_data(%s) failed: %s\n", + "regtp_ca_chainlen", gpg_strerror (err)); + return 0; /* Nothing found. */ + } + + /* Need to gather the info. This requires to walk up the chain + until we have found the root. Because we are only interested in + German Bundesnetzagentur (former RegTP) derived certificates 3 + levels are enough. (The German signature law demands a 3 tier + hierachy; thus there is only one CA between the EE and the Root + CA.) */ + memset (&array, 0, sizeof array); + + depth = 0; + ksba_cert_ref (cert); + array[depth++] = cert; + ksba_cert_ref (cert); + while (depth < DIM(array) && !(rc=gpgsm_walk_cert_chain (ctrl, cert, &next))) + { + ksba_cert_release (cert); + ksba_cert_ref (next); + array[depth++] = next; + cert = next; + } + ksba_cert_release (cert); + if (rc != -1 || !depth || depth == DIM(array) ) + { + /* We did not reached the root. */ + goto leave; + } + + /* If this is a German signature law issued certificate, we store + additional additional information. */ + if (!gpgsm_is_in_qualified_list (NULL, array[depth-1], country) + && !strcmp (country, "de")) + { + /* Setting the pathlen for the root CA and the CA flag for the + next one is all what we need to do. */ + err = ksba_cert_set_user_data (array[depth-1], "regtp_ca_chainlen", + "\x01\x01", 2); + if (!err && depth > 1) + err = ksba_cert_set_user_data (array[depth-2], "regtp_ca_chainlen", + "\x01\x00", 2); + if (err) + log_error ("ksba_set_user_data(%s) failed: %s\n", + "regtp_ca_chainlen", gpg_strerror (err)); + for (i=0; i < depth; i++) + ksba_cert_release (array[i]); + *chainlen = (depth>1? 0:1); + return 1; + } + + leave: + /* Nothing special with this certificate. Mark the target + certificate anyway to avoid duplicate lookups. */ + err = ksba_cert_set_user_data (cert, "regtp_ca_chainlen", "", 1); + if (err) + log_error ("ksba_set_user_data(%s) failed: %s\n", + "regtp_ca_chainlen", gpg_strerror (err)); + for (i=0; i < depth; i++) + ksba_cert_release (array[i]); + return 0; +} diff --git a/sm/certcheck.c b/sm/certcheck.c new file mode 100644 index 0000000..51a809b --- /dev/null +++ b/sm/certcheck.c @@ -0,0 +1,439 @@ +/* certcheck.c - check one certificate + * Copyright (C) 2001, 2003, 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + + +/* 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, "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; +} + + +static int +do_encode_md (gcry_md_hd_t md, int algo, int pkalgo, unsigned int nbits, + gcry_sexp_t pkey, gcry_mpi_t *r_val) +{ + int n; + size_t nframe; + unsigned char *frame; + + if (pkalgo == GCRY_PK_DSA || pkalgo == GCRY_PK_ECDSA) + { + unsigned int qbits; + + if ( pkalgo == GCRY_PK_ECDSA ) + qbits = gcry_pk_get_nbits (pkey); + else + qbits = get_dsa_qbits (pkey); + + if ( (qbits%8) ) + { + log_error(_("DSA requires the hash length to be a" + " multiple of 8 bits\n")); + return gpg_error (GPG_ERR_INTERNAL); + } + + /* 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_INTERNAL); + } + + /* Check if we're too short. Too long is safe as we'll + automatically left-truncate. */ + nframe = gcry_md_get_algo_dlen (algo); + if (nframe < qbits/8) + { + log_error (_("a %u bit hash is not valid for a %u bit %s key\n"), + (unsigned int)nframe*8, + gcry_pk_get_nbits (pkey), + gcry_pk_algo_name (pkalgo)); + /* FIXME: we need to check the requirements for ECDSA. */ + if (nframe < 20 || pkalgo == GCRY_PK_DSA ) + return gpg_error (GPG_ERR_INTERNAL); + } + + frame = xtrymalloc (nframe); + if (!frame) + return out_of_core (); + memcpy (frame, gcry_md_read (md, algo), nframe); + n = nframe; + /* Truncate. */ + if (n > qbits/8) + n = qbits/8; + } + else + { + int i; + unsigned char asn[100]; + size_t asnlen; + size_t len; + + nframe = (nbits+7) / 8; + + 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); + } + + len = gcry_md_get_algo_dlen (algo); + + if ( len + asnlen + 4 > nframe ) + { + log_error ("can't encode a %d bit MD into a %d bits frame\n", + (int)(len*8), (int)nbits); + return gpg_error (GPG_ERR_INTERNAL); + } + + /* We encode the MD in this way: + * + * 0 A PAD(n bytes) 0 ASN(asnlen bytes) MD(len bytes) + * + * PAD consists of FF bytes. + */ + frame = xtrymalloc (nframe); + if (!frame) + return out_of_core (); + n = 0; + frame[n++] = 0; + frame[n++] = 1; /* block type */ + i = nframe - len - asnlen -3 ; + assert ( i > 1 ); + memset ( frame+n, 0xff, i ); n += i; + frame[n++] = 0; + memcpy ( frame+n, asn, asnlen ); n += asnlen; + memcpy ( frame+n, gcry_md_read(md, algo), len ); n += len; + assert ( n == nframe ); + } + if (DBG_CRYPTO) + { + int j; + log_debug ("encoded hash:"); + for (j=0; j < nframe; j++) + log_printf (" %02X", frame[j]); + log_printf ("\n"); + } + + gcry_mpi_scan (r_val, GCRYMPI_FMT_USG, frame, n, &nframe); + xfree (frame); + return 0; +} + +/* Return the public key algorithm id from the S-expression PKEY. + FIXME: libgcrypt should provide such a function. Note that this + implementation uses the names as used by libksba. */ +static int +pk_algo_from_sexp (gcry_sexp_t pkey) +{ + gcry_sexp_t l1, l2; + const char *name; + size_t n; + int algo; + + l1 = gcry_sexp_find_token (pkey, "public-key", 0); + if (!l1) + return 0; /* Not found. */ + l2 = gcry_sexp_cadr (l1); + gcry_sexp_release (l1); + + name = gcry_sexp_nth_data (l2, 0, &n); + if (!name) + algo = 0; /* Not found. */ + else if (n==3 && !memcmp (name, "rsa", 3)) + algo = GCRY_PK_RSA; + else if (n==3 && !memcmp (name, "dsa", 3)) + algo = GCRY_PK_DSA; + /* Because this function is called only for verification we can + assume that ECC actually means ECDSA. */ + else if (n==3 && !memcmp (name, "ecc", 3)) + algo = GCRY_PK_ECDSA; + else if (n==13 && !memcmp (name, "ambiguous-rsa", 13)) + algo = GCRY_PK_RSA; + else + algo = 0; + gcry_sexp_release (l2); + return algo; +} + + +/* Check the signature on CERT using the ISSUER-CERT. This function + does only test the cryptographic signature and nothing else. It is + assumed that the ISSUER_CERT is valid. */ +int +gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert) +{ + const char *algoid; + gcry_md_hd_t md; + int rc, algo; + gcry_mpi_t frame; + ksba_sexp_t p; + size_t n; + gcry_sexp_t s_sig, s_hash, s_pkey; + + algo = gcry_md_map_name ( (algoid=ksba_cert_get_digest_algo (cert))); + if (!algo) + { + log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?"); + if (algoid + && ( !strcmp (algoid, "1.2.840.113549.1.1.2") + ||!strcmp (algoid, "1.2.840.113549.2.2"))) + log_info (_("(this is the MD2 algorithm)\n")); + return gpg_error (GPG_ERR_GENERAL); + } + rc = gcry_md_open (&md, algo, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + return rc; + } + if (DBG_HASHING) + gcry_md_start_debug (md, "hash.cert"); + + rc = ksba_cert_hash (cert, 1, HASH_FNC, md); + if (rc) + { + log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (rc)); + gcry_md_close (md); + return rc; + } + gcry_md_final (md); + + p = ksba_cert_get_sig_val (cert); + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + gcry_md_close (md); + ksba_free (p); + return gpg_error (GPG_ERR_BUG); + } + if (DBG_CRYPTO) + { + int j; + log_debug ("signature value:"); + for (j=0; j < n; j++) + log_printf (" %02X", p[j]); + log_printf ("\n"); + } + + rc = gcry_sexp_sscan ( &s_sig, NULL, (char*)p, n); + ksba_free (p); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + gcry_md_close (md); + return rc; + } + + p = ksba_cert_get_public_key (issuer_cert); + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + gcry_md_close (md); + ksba_free (p); + gcry_sexp_release (s_sig); + return gpg_error (GPG_ERR_BUG); + } + rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n); + ksba_free (p); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + gcry_md_close (md); + gcry_sexp_release (s_sig); + return rc; + } + + rc = do_encode_md (md, algo, pk_algo_from_sexp (s_pkey), + gcry_pk_get_nbits (s_pkey), s_pkey, &frame); + if (rc) + { + gcry_md_close (md); + gcry_sexp_release (s_sig); + gcry_sexp_release (s_pkey); + return rc; + } + + /* put hash into the S-Exp s_hash */ + if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) ) + BUG (); + gcry_mpi_release (frame); + + + rc = gcry_pk_verify (s_sig, s_hash, s_pkey); + if (DBG_X509) + log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc)); + gcry_md_close (md); + gcry_sexp_release (s_sig); + gcry_sexp_release (s_hash); + gcry_sexp_release (s_pkey); + return rc; +} + + + +int +gpgsm_check_cms_signature (ksba_cert_t cert, ksba_const_sexp_t sigval, + gcry_md_hd_t md, int mdalgo, int *r_pkalgo) +{ + int rc; + ksba_sexp_t p; + gcry_mpi_t frame; + gcry_sexp_t s_sig, s_hash, s_pkey; + size_t n; + int pkalgo; + + if (r_pkalgo) + *r_pkalgo = 0; + + n = gcry_sexp_canon_len (sigval, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + return gpg_error (GPG_ERR_BUG); + } + rc = gcry_sexp_sscan (&s_sig, NULL, (char*)sigval, n); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + return rc; + } + + p = ksba_cert_get_public_key (cert); + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + ksba_free (p); + gcry_sexp_release (s_sig); + return gpg_error (GPG_ERR_BUG); + } + if (DBG_CRYPTO) + log_printhex ("public key: ", p, n); + + rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n); + ksba_free (p); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + gcry_sexp_release (s_sig); + return rc; + } + + pkalgo = pk_algo_from_sexp (s_pkey); + if (r_pkalgo) + *r_pkalgo = pkalgo; + rc = do_encode_md (md, mdalgo, pkalgo, + gcry_pk_get_nbits (s_pkey), s_pkey, &frame); + if (rc) + { + gcry_sexp_release (s_sig); + gcry_sexp_release (s_pkey); + return rc; + } + /* put hash into the S-Exp s_hash */ + if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) ) + BUG (); + gcry_mpi_release (frame); + + rc = gcry_pk_verify (s_sig, s_hash, s_pkey); + if (DBG_X509) + log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc)); + gcry_sexp_release (s_sig); + gcry_sexp_release (s_hash); + gcry_sexp_release (s_pkey); + return rc; +} + + + +int +gpgsm_create_cms_signature (ctrl_t ctrl, ksba_cert_t cert, + gcry_md_hd_t md, int mdalgo, + unsigned char **r_sigval) +{ + int rc; + char *grip, *desc; + size_t siglen; + + grip = gpgsm_get_keygrip_hexstring (cert); + if (!grip) + return gpg_error (GPG_ERR_BAD_CERT); + + desc = gpgsm_format_keydesc (cert); + + rc = gpgsm_agent_pksign (ctrl, grip, desc, gcry_md_read(md, mdalgo), + gcry_md_get_algo_dlen (mdalgo), mdalgo, + r_sigval, &siglen); + xfree (desc); + xfree (grip); + return rc; +} + + + diff --git a/sm/certdump.c b/sm/certdump.c new file mode 100644 index 0000000..d339070 --- /dev/null +++ b/sm/certdump.c @@ -0,0 +1,974 @@ +/* certdump.c - Dump a certificate for debugging + * Copyright (C) 2001, 2004, 2007 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif +#ifdef HAVE_LANGINFO_CODESET +#include <langinfo.h> +#endif + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + + +struct dn_array_s { + char *key; + char *value; + int multivalued; + int done; +}; + + +/* Print the first element of an S-Expression. */ +void +gpgsm_print_serial (estream_t fp, ksba_const_sexp_t sn) +{ + const char *p = (const char *)sn; + unsigned long n; + char *endp; + + if (!p) + es_fputs (_("none"), fp); + else if (*p != '(') + es_fputs ("[Internal error - not an S-expression]", fp); + else + { + p++; + n = strtoul (p, &endp, 10); + p = endp; + if (*p++ != ':') + es_fputs ("[Internal Error - invalid S-expression]", fp); + else + es_write_hexstring (fp, p, n, 0, NULL); + } +} + + +/* Dump the serial number or any other simple S-expression. */ +void +gpgsm_dump_serial (ksba_const_sexp_t sn) +{ + const char *p = (const char *)sn; + unsigned long n; + char *endp; + + if (!p) + log_printf ("none"); + else if (*p != '(') + log_printf ("ERROR - not an S-expression"); + else + { + p++; + n = strtoul (p, &endp, 10); + p = endp; + if (*p!=':') + log_printf ("ERROR - invalid S-expression"); + else + { + for (p++; n; n--, p++) + log_printf ("%02X", *(const unsigned char *)p); + } + } +} + + +char * +gpgsm_format_serial (ksba_const_sexp_t sn) +{ + const char *p = (const char *)sn; + unsigned long n; + char *endp; + char *buffer; + int i; + + if (!p) + return NULL; + + if (*p != '(') + BUG (); /* Not a valid S-expression. */ + + p++; + n = strtoul (p, &endp, 10); + p = endp; + if (*p!=':') + BUG (); /* Not a valid S-expression. */ + p++; + + buffer = xtrymalloc (n*2+1); + if (buffer) + { + for (i=0; n; n--, p++, i+=2) + sprintf (buffer+i, "%02X", *(unsigned char *)p); + buffer[i] = 0; + } + return buffer; +} + + + + +void +gpgsm_print_time (estream_t fp, ksba_isotime_t t) +{ + if (!t || !*t) + es_fputs (_("none"), fp); + else + es_fprintf (fp, "%.4s-%.2s-%.2s %.2s:%.2s:%s", + t, t+4, t+6, t+9, t+11, t+13); +} + + +void +gpgsm_dump_string (const char *string) +{ + + if (!string) + log_printf ("[error]"); + else + { + const unsigned char *s; + + for (s=(const unsigned char*)string; *s; s++) + { + if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0)) + break; + } + if (!*s && *string != '[') + log_printf ("%s", string); + else + { + log_printf ( "[ "); + log_printhex (NULL, string, strlen (string)); + log_printf ( " ]"); + } + } +} + + +/* This simple dump function is mainly used for debugging purposes. */ +void +gpgsm_dump_cert (const char *text, ksba_cert_t cert) +{ + ksba_sexp_t sexp; + char *p; + char *dn; + ksba_isotime_t t; + + log_debug ("BEGIN Certificate `%s':\n", text? text:""); + if (cert) + { + sexp = ksba_cert_get_serial (cert); + log_debug (" serial: "); + gpgsm_dump_serial (sexp); + ksba_free (sexp); + log_printf ("\n"); + + ksba_cert_get_validity (cert, 0, t); + log_debug (" notBefore: "); + dump_isotime (t); + log_printf ("\n"); + ksba_cert_get_validity (cert, 1, t); + log_debug (" notAfter: "); + dump_isotime (t); + log_printf ("\n"); + + dn = ksba_cert_get_issuer (cert, 0); + log_debug (" issuer: "); + gpgsm_dump_string (dn); + ksba_free (dn); + log_printf ("\n"); + + dn = ksba_cert_get_subject (cert, 0); + log_debug (" subject: "); + gpgsm_dump_string (dn); + ksba_free (dn); + log_printf ("\n"); + + log_debug (" hash algo: %s\n", ksba_cert_get_digest_algo (cert)); + + p = gpgsm_get_fingerprint_string (cert, 0); + log_debug (" SHA1 Fingerprint: %s\n", p); + xfree (p); + } + log_debug ("END Certificate\n"); +} + + +/* Return a new string holding the format serial number and issuer + ("#SN/issuer"). No filtering on invalid characters is done. + Caller must release the string. On memory failure NULL is + returned. */ +char * +gpgsm_format_sn_issuer (ksba_sexp_t sn, const char *issuer) +{ + char *p, *p1; + + if (sn && issuer) + { + p1 = gpgsm_format_serial (sn); + if (!p1) + p = xtrystrdup ("[invalid SN]"); + else + { + p = xtrymalloc (strlen (p1) + strlen (issuer) + 2 + 1); + if (p) + { + *p = '#'; + strcpy (stpcpy (stpcpy (p+1, p1),"/"), issuer); + } + xfree (p1); + } + } + else + p = xtrystrdup ("[invalid SN/issuer]"); + return p; +} + + +/* Log the certificate's name in "#SN/ISSUERDN" format along with + TEXT. */ +void +gpgsm_cert_log_name (const char *text, ksba_cert_t cert) +{ + log_info ("%s", text? text:"certificate" ); + if (cert) + { + ksba_sexp_t sn; + char *p; + + p = ksba_cert_get_issuer (cert, 0); + sn = ksba_cert_get_serial (cert); + if (p && sn) + { + log_printf (" #"); + gpgsm_dump_serial (sn); + log_printf ("/"); + gpgsm_dump_string (p); + } + else + log_printf (" [invalid]"); + ksba_free (sn); + xfree (p); + } + log_printf ("\n"); +} + + + + + + +/* helper for the rfc2253 string parser */ +static const unsigned char * +parse_dn_part (struct dn_array_s *array, const unsigned char *string) +{ + static struct { + const char *label; + const char *oid; + } label_map[] = { + /* Warning: When adding new labels, make sure that the buffer + below we be allocated large enough. */ + {"EMail", "1.2.840.113549.1.9.1" }, + {"T", "2.5.4.12" }, + {"GN", "2.5.4.42" }, + {"SN", "2.5.4.4" }, + {"NameDistinguisher", "0.2.262.1.10.7.20"}, + {"ADDR", "2.5.4.16" }, + {"BC", "2.5.4.15" }, + {"D", "2.5.4.13" }, + {"PostalCode", "2.5.4.17" }, + {"Pseudo", "2.5.4.65" }, + {"SerialNumber", "2.5.4.5" }, + {NULL, NULL} + }; + const unsigned char *s, *s1; + size_t n; + char *p; + int i; + + /* Parse attributeType */ + for (s = string+1; *s && *s != '='; s++) + ; + if (!*s) + return NULL; /* error */ + n = s - string; + if (!n) + return NULL; /* empty key */ + + /* We need to allocate a few bytes more due to the possible mapping + from the shorter OID to the longer label. */ + array->key = p = xtrymalloc (n+10); + if (!array->key) + return NULL; + memcpy (p, string, n); + p[n] = 0; + trim_trailing_spaces (p); + + if (digitp (p)) + { + for (i=0; label_map[i].label; i++ ) + if ( !strcmp (p, label_map[i].oid) ) + { + strcpy (p, label_map[i].label); + break; + } + } + string = s + 1; + + if (*string == '#') + { /* hexstring */ + string++; + for (s=string; hexdigitp (s); s++) + s++; + n = s - string; + if (!n || (n & 1)) + return NULL; /* Empty or odd number of digits. */ + n /= 2; + array->value = p = xtrymalloc (n+1); + if (!p) + return NULL; + for (s1=string; n; s1 += 2, n--, p++) + { + *(unsigned char *)p = xtoi_2 (s1); + if (!*p) + *p = 0x01; /* Better print a wrong value than truncating + the string. */ + } + *p = 0; + } + else + { /* regular v3 quoted string */ + for (n=0, s=string; *s; s++) + { + if (*s == '\\') + { /* pair */ + s++; + if (*s == ',' || *s == '=' || *s == '+' + || *s == '<' || *s == '>' || *s == '#' || *s == ';' + || *s == '\\' || *s == '\"' || *s == ' ') + n++; + else if (hexdigitp (s) && hexdigitp (s+1)) + { + s++; + n++; + } + else + return NULL; /* invalid escape sequence */ + } + else if (*s == '\"') + return NULL; /* invalid encoding */ + else if (*s == ',' || *s == '=' || *s == '+' + || *s == '<' || *s == '>' || *s == ';' ) + break; + else + n++; + } + + array->value = p = xtrymalloc (n+1); + if (!p) + return NULL; + for (s=string; n; s++, n--) + { + if (*s == '\\') + { + s++; + if (hexdigitp (s)) + { + *(unsigned char *)p++ = xtoi_2 (s); + s++; + } + else + *p++ = *s; + } + else + *p++ = *s; + } + *p = 0; + } + return s; +} + + +/* Parse a DN and return an array-ized one. This is not a validating + parser and it does not support any old-stylish syntax; KSBA is + expected to return only rfc2253 compatible strings. */ +static struct dn_array_s * +parse_dn (const unsigned char *string) +{ + struct dn_array_s *array; + size_t arrayidx, arraysize; + int i; + + arraysize = 7; /* C,ST,L,O,OU,CN,email */ + arrayidx = 0; + array = xtrymalloc ((arraysize+1) * sizeof *array); + if (!array) + return NULL; + while (*string) + { + while (*string == ' ') + string++; + if (!*string) + break; /* ready */ + if (arrayidx >= arraysize) + { + struct dn_array_s *a2; + + arraysize += 5; + a2 = xtryrealloc (array, (arraysize+1) * sizeof *array); + if (!a2) + goto failure; + array = a2; + } + array[arrayidx].key = NULL; + array[arrayidx].value = NULL; + string = parse_dn_part (array+arrayidx, string); + if (!string) + goto failure; + while (*string == ' ') + string++; + array[arrayidx].multivalued = (*string == '+'); + array[arrayidx].done = 0; + arrayidx++; + if (*string && *string != ',' && *string != ';' && *string != '+') + goto failure; /* invalid delimiter */ + if (*string) + string++; + } + array[arrayidx].key = NULL; + array[arrayidx].value = NULL; + return array; + + failure: + for (i=0; i < arrayidx; i++) + { + xfree (array[i].key); + xfree (array[i].value); + } + xfree (array); + return NULL; +} + + +/* Print a DN part to STREAM or if STREAM is NULL to FP. */ +static void +print_dn_part (FILE *fp, estream_t stream, + struct dn_array_s *dn, const char *key, int translate) +{ + struct dn_array_s *first_dn = dn; + + for (; dn->key; dn++) + { + if (!dn->done && !strcmp (dn->key, key)) + { + /* Forward to the last multi-valued RDN, so that we can + print them all in reverse in the correct order. Note + that this overrides the the standard sequence but that + seems to a reasonable thing to do with multi-valued + RDNs. */ + while (dn->multivalued && dn[1].key) + dn++; + next: + if (!dn->done && dn->value && *dn->value) + { + if (stream) + { + es_fprintf (stream, "/%s=", dn->key); + if (translate) + es_write_sanitized_utf8_buffer (stream, dn->value, + strlen (dn->value), + "/", NULL); + else + es_write_sanitized (stream, dn->value, strlen (dn->value), + "/", NULL); + } + else + { + fprintf (fp, "/%s=", dn->key); + if (translate) + print_sanitized_utf8_string (fp, dn->value, '/'); + else + print_sanitized_string (fp, dn->value, '/'); + } + } + dn->done = 1; + if (dn > first_dn && dn[-1].multivalued) + { + dn--; + goto next; + } + } + } +} + +/* Print all parts of a DN in a "standard" sequence. We first print + all the known parts, followed by the uncommon ones */ +static void +print_dn_parts (FILE *fp, estream_t stream, + struct dn_array_s *dn, int translate) +{ + const char *stdpart[] = { + "CN", "OU", "O", "STREET", "L", "ST", "C", "EMail", NULL + }; + int i; + + for (i=0; stdpart[i]; i++) + print_dn_part (fp, stream, dn, stdpart[i], translate); + + /* Now print the rest without any specific ordering */ + for (; dn->key; dn++) + print_dn_part (fp, stream, dn, dn->key, translate); +} + + +/* Print the S-Expression in BUF, which has a valid length of BUFLEN, + as a human readable string in one line to FP. */ +static void +pretty_print_sexp (FILE *fp, const unsigned char *buf, size_t buflen) +{ + size_t len; + gcry_sexp_t sexp; + char *result, *p; + + if ( gcry_sexp_sscan (&sexp, NULL, (const char*)buf, buflen) ) + { + fputs (_("[Error - invalid encoding]"), fp); + return; + } + len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0); + assert (len); + result = xtrymalloc (len); + if (!result) + { + fputs (_("[Error - out of core]"), fp); + gcry_sexp_release (sexp); + return; + } + len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len); + assert (len); + for (p = result; len; len--, p++) + { + if (*p == '\n') + { + if (len > 1) /* Avoid printing the trailing LF. */ + fputs ("\\n", fp); + } + else if (*p == '\r') + fputs ("\\r", fp); + else if (*p == '\v') + fputs ("\\v", fp); + else if (*p == '\t') + fputs ("\\t", fp); + else + putc (*p, fp); + } + xfree (result); + gcry_sexp_release (sexp); +} + +/* Print the S-Expression in BUF to extended STREAM, which has a valid + length of BUFLEN, as a human readable string in one line to FP. */ +static void +pretty_es_print_sexp (estream_t fp, const unsigned char *buf, size_t buflen) +{ + size_t len; + gcry_sexp_t sexp; + char *result, *p; + + if ( gcry_sexp_sscan (&sexp, NULL, (const char*)buf, buflen) ) + { + es_fputs (_("[Error - invalid encoding]"), fp); + return; + } + len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0); + assert (len); + result = xtrymalloc (len); + if (!result) + { + es_fputs (_("[Error - out of core]"), fp); + gcry_sexp_release (sexp); + return; + } + len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len); + assert (len); + for (p = result; len; len--, p++) + { + if (*p == '\n') + { + if (len > 1) /* Avoid printing the trailing LF. */ + es_fputs ("\\n", fp); + } + else if (*p == '\r') + es_fputs ("\\r", fp); + else if (*p == '\v') + es_fputs ("\\v", fp); + else if (*p == '\t') + es_fputs ("\\t", fp); + else + es_putc (*p, fp); + } + xfree (result); + gcry_sexp_release (sexp); +} + + + + +void +gpgsm_print_name2 (FILE *fp, const char *name, int translate) +{ + const unsigned char *s = (const unsigned char *)name; + int i; + + if (!s) + { + fputs (_("[Error - No name]"), fp); + } + else if (*s == '<') + { + const char *s2 = strchr ( (char*)s+1, '>'); + if (s2) + { + if (translate) + print_sanitized_utf8_buffer (fp, s + 1, s2 - (char*)s - 1, 0); + else + print_sanitized_buffer (fp, s + 1, s2 - (char*)s - 1, 0); + } + } + else if (*s == '(') + { + pretty_print_sexp (fp, s, gcry_sexp_canon_len (s, 0, NULL, NULL)); + } + else if (!((*s >= '0' && *s < '9') + || (*s >= 'A' && *s <= 'Z') + || (*s >= 'a' && *s <= 'z'))) + fputs (_("[Error - invalid encoding]"), fp); + else + { + struct dn_array_s *dn = parse_dn (s); + if (!dn) + fputs (_("[Error - invalid DN]"), fp); + else + { + print_dn_parts (fp, NULL, dn, translate); + for (i=0; dn[i].key; i++) + { + xfree (dn[i].key); + xfree (dn[i].value); + } + xfree (dn); + } + } +} + + +void +gpgsm_print_name (FILE *fp, const char *name) +{ + gpgsm_print_name2 (fp, name, 1); +} + + +/* This is a variant of gpgsm_print_name sending it output to an estream. */ +void +gpgsm_es_print_name2 (estream_t fp, const char *name, int translate) +{ + const unsigned char *s = (const unsigned char *)name; + int i; + + if (!s) + { + es_fputs (_("[Error - No name]"), fp); + } + else if (*s == '<') + { + const char *s2 = strchr ( (char*)s+1, '>'); + + if (s2) + { + if (translate) + es_write_sanitized_utf8_buffer (fp, s + 1, s2 - (char*)s - 1, + NULL, NULL); + else + es_write_sanitized (fp, s + 1, s2 - (char*)s - 1, NULL, NULL); + } + } + else if (*s == '(') + { + pretty_es_print_sexp (fp, s, gcry_sexp_canon_len (s, 0, NULL, NULL)); + } + else if (!((*s >= '0' && *s < '9') + || (*s >= 'A' && *s <= 'Z') + || (*s >= 'a' && *s <= 'z'))) + es_fputs (_("[Error - invalid encoding]"), fp); + else + { + struct dn_array_s *dn = parse_dn (s); + + if (!dn) + es_fputs (_("[Error - invalid DN]"), fp); + else + { + print_dn_parts (NULL, fp, dn, translate); + for (i=0; dn[i].key; i++) + { + xfree (dn[i].key); + xfree (dn[i].value); + } + xfree (dn); + } + } +} + + +void +gpgsm_es_print_name (estream_t fp, const char *name) +{ + gpgsm_es_print_name2 (fp, name, 1); +} + + +/* A cookie structure used for the memory stream. */ +struct format_name_cookie +{ + char *buffer; /* Malloced buffer with the data to deliver. */ + size_t size; /* Allocated size of this buffer. */ + size_t len; /* strlen (buffer). */ + int error; /* system error code if any. */ +}; + +/* The writer function for the memory stream. */ +static ssize_t +format_name_writer (void *cookie, const void *buffer, size_t size) +{ + struct format_name_cookie *c = cookie; + char *p; + + if (!c->buffer) + { + p = xtrymalloc (size + 1 + 1); + if (p) + { + c->size = size + 1; + c->buffer = p; + c->len = 0; + } + } + else if (c->len + size < c->len) + { + p = NULL; + errno = ENOMEM; + } + else if (c->size < c->len + size) + { + p = xtryrealloc (c->buffer, c->len + size + 1); + if (p) + { + c->size = c->len + size; + c->buffer = p; + } + } + else + p = c->buffer; + if (!p) + { + c->error = errno; + xfree (c->buffer); + c->buffer = NULL; + errno = c->error; + return -1; + } + memcpy (p + c->len, buffer, size); + c->len += size; + p[c->len] = 0; /* Terminate string. */ + + return (ssize_t)size; +} + + +/* Format NAME which is expected to be in rfc2253 format into a better + human readable format. Caller must free the returned string. NULL + is returned in case of an error. With TRANSLATE set to true the + name will be translated to the native encoding. Note that NAME is + internally always UTF-8 encoded. */ +char * +gpgsm_format_name2 (const char *name, int translate) +{ + estream_t fp; + struct format_name_cookie cookie; + es_cookie_io_functions_t io = { NULL }; + + memset (&cookie, 0, sizeof cookie); + + io.func_write = format_name_writer; + fp = es_fopencookie (&cookie, "w", io); + if (!fp) + { + int save_errno = errno; + log_error ("error creating memory stream: %s\n", strerror (errno)); + errno = save_errno; + return NULL; + } + gpgsm_es_print_name2 (fp, name, translate); + es_fclose (fp); + if (cookie.error || !cookie.buffer) + { + xfree (cookie.buffer); + errno = cookie.error; + return NULL; + } + return cookie.buffer; +} + + +char * +gpgsm_format_name (const char *name) +{ + return gpgsm_format_name2 (name, 1); +} + + +/* Return fingerprint and a percent escaped name in a human readable + format suitable for status messages like GOODSIG. May return NULL + on error (out of core). */ +char * +gpgsm_fpr_and_name_for_status (ksba_cert_t cert) +{ + char *fpr, *name, *p; + char *buffer; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + if (!fpr) + return NULL; + + name = ksba_cert_get_subject (cert, 0); + if (!name) + { + xfree (fpr); + return NULL; + } + + p = gpgsm_format_name2 (name, 0); + ksba_free (name); + name = p; + if (!name) + { + xfree (fpr); + return NULL; + } + + buffer = xtrymalloc (strlen (fpr) + 1 + 3*strlen (name) + 1); + if (buffer) + { + const char *s; + + p = stpcpy (stpcpy (buffer, fpr), " "); + for (s = name; *s; s++) + { + if (*s < ' ') + { + sprintf (p, "%%%02X", *(const unsigned char*)s); + p += 3; + } + else + *p++ = *s; + } + *p = 0; + } + xfree (fpr); + xfree (name); + return buffer; +} + + +/* Create a key description for the CERT, this may be passed to the + pinentry. The caller must free the returned string. NULL may be + returned on error. */ +char * +gpgsm_format_keydesc (ksba_cert_t cert) +{ + char *name, *subject, *buffer; + ksba_isotime_t t; + char created[20]; + char expires[20]; + char *sn; + ksba_sexp_t sexp; + char *orig_codeset; + + name = ksba_cert_get_subject (cert, 0); + subject = name? gpgsm_format_name2 (name, 0) : NULL; + ksba_free (name); name = NULL; + + sexp = ksba_cert_get_serial (cert); + sn = sexp? gpgsm_format_serial (sexp) : NULL; + ksba_free (sexp); + + ksba_cert_get_validity (cert, 0, t); + if (*t) + sprintf (created, "%.4s-%.2s-%.2s", t, t+4, t+6); + else + *created = 0; + ksba_cert_get_validity (cert, 1, t); + if (*t) + sprintf (expires, "%.4s-%.2s-%.2s", t, t+4, t+6); + else + *expires = 0; + + orig_codeset = i18n_switchto_utf8 (); + + name = xtryasprintf (_("Please enter the passphrase to unlock the" + " secret key for the X.509 certificate:\n" + "\"%s\"\n" + "S/N %s, ID 0x%08lX,\n" + "created %s, expires %s.\n" ), + subject? subject:"?", + sn? sn: "?", + gpgsm_get_short_fingerprint (cert, NULL), + created, expires); + + i18n_switchback (orig_codeset); + + if (!name) + { + xfree (subject); + xfree (sn); + return NULL; + } + + xfree (subject); + xfree (sn); + + buffer = percent_plus_escape (name); + xfree (name); + return buffer; +} + diff --git a/sm/certlist.c b/sm/certlist.c new file mode 100644 index 0000000..4137437 --- /dev/null +++ b/sm/certlist.c @@ -0,0 +1,555 @@ +/* certlist.c - build list of certificates + * Copyright (C) 2001, 2003, 2004, 2005, 2007, + * 2008 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + + +static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1"; +static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2"; +static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3"; +static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4"; +static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8"; +static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9"; + +/* Return 0 if the cert is usable for encryption. A MODE of 0 checks + for signing a MODE of 1 checks for encryption, a MODE of 2 checks + for verification and a MODE of 3 for decryption (just for + debugging). MODE 4 is for certificate signing, MODE for COSP + response signing. */ +static int +cert_usage_p (ksba_cert_t cert, int mode) +{ + gpg_error_t err; + unsigned int use; + char *extkeyusages; + int have_ocsp_signing = 0; + + err = ksba_cert_get_ext_key_usages (cert, &extkeyusages); + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = 0; /* no policy given */ + if (!err) + { + unsigned int extusemask = ~0; /* Allow all. */ + + if (extkeyusages) + { + char *p, *pend; + int any_critical = 0; + + extusemask = 0; + + p = extkeyusages; + while (p && (pend=strchr (p, ':'))) + { + *pend++ = 0; + /* Only care about critical flagged usages. */ + if ( *pend == 'C' ) + { + any_critical = 1; + if ( !strcmp (p, oid_kp_serverAuth)) + extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE + | KSBA_KEYUSAGE_KEY_ENCIPHERMENT + | KSBA_KEYUSAGE_KEY_AGREEMENT); + else if ( !strcmp (p, oid_kp_clientAuth)) + extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE + | KSBA_KEYUSAGE_KEY_AGREEMENT); + else if ( !strcmp (p, oid_kp_codeSigning)) + extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE); + else if ( !strcmp (p, oid_kp_emailProtection)) + extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE + | KSBA_KEYUSAGE_NON_REPUDIATION + | KSBA_KEYUSAGE_KEY_ENCIPHERMENT + | KSBA_KEYUSAGE_KEY_AGREEMENT); + else if ( !strcmp (p, oid_kp_timeStamping)) + extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE + | KSBA_KEYUSAGE_NON_REPUDIATION); + } + + /* This is a hack to cope with OCSP. Note that we do + not yet fully comply with the requirements and that + the entire CRL/OCSP checking thing should undergo a + thorough review and probably redesign. */ + if ( !strcmp (p, oid_kp_ocspSigning)) + have_ocsp_signing = 1; + + if ((p = strchr (pend, '\n'))) + p++; + } + xfree (extkeyusages); + extkeyusages = NULL; + + if (!any_critical) + extusemask = ~0; /* Reset to the don't care mask. */ + } + + + err = ksba_cert_get_key_usage (cert, &use); + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + { + err = 0; + if (opt.verbose && mode < 2) + log_info (_("no key usage specified - assuming all usages\n")); + use = ~0; + } + + /* Apply extKeyUsage. */ + use &= extusemask; + + } + if (err) + { + log_error (_("error getting key usage information: %s\n"), + gpg_strerror (err)); + xfree (extkeyusages); + return err; + } + + if (mode == 4) + { + if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN))) + return 0; + log_info (_("certificate should have not " + "been used for certification\n")); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + if (mode == 5) + { + if (use != ~0 + && (have_ocsp_signing + || (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN + |KSBA_KEYUSAGE_CRL_SIGN)))) + return 0; + log_info (_("certificate should have not " + "been used for OCSP response signing\n")); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + if ((use & ((mode&1)? + (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT): + (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) + ) + return 0; + + log_info (mode==3? _("certificate should have not been used for encryption\n"): + mode==2? _("certificate should have not been used for signing\n"): + mode==1? _("certificate is not usable for encryption\n"): + _("certificate is not usable for signing\n")); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); +} + + +/* Return 0 if the cert is usable for signing */ +int +gpgsm_cert_use_sign_p (ksba_cert_t cert) +{ + return cert_usage_p (cert, 0); +} + + +/* Return 0 if the cert is usable for encryption */ +int +gpgsm_cert_use_encrypt_p (ksba_cert_t cert) +{ + return cert_usage_p (cert, 1); +} + +int +gpgsm_cert_use_verify_p (ksba_cert_t cert) +{ + return cert_usage_p (cert, 2); +} + +int +gpgsm_cert_use_decrypt_p (ksba_cert_t cert) +{ + return cert_usage_p (cert, 3); +} + +int +gpgsm_cert_use_cert_p (ksba_cert_t cert) +{ + return cert_usage_p (cert, 4); +} + +int +gpgsm_cert_use_ocsp_p (ksba_cert_t cert) +{ + return cert_usage_p (cert, 5); +} + + +static int +same_subject_issuer (const char *subject, const char *issuer, ksba_cert_t cert) +{ + char *subject2 = ksba_cert_get_subject (cert, 0); + char *issuer2 = ksba_cert_get_issuer (cert, 0); + int tmp; + + tmp = (subject && subject2 + && !strcmp (subject, subject2) + && issuer && issuer2 + && !strcmp (issuer, issuer2)); + xfree (subject2); + xfree (issuer2); + return tmp; +} + + +/* Return true if CERT_A is the same as CERT_B. */ +int +gpgsm_certs_identical_p (ksba_cert_t cert_a, ksba_cert_t cert_b) +{ + const unsigned char *img_a, *img_b; + size_t len_a, len_b; + + img_a = ksba_cert_get_image (cert_a, &len_a); + if (img_a) + { + img_b = ksba_cert_get_image (cert_b, &len_b); + if (img_b && len_a == len_b && !memcmp (img_a, img_b, len_a)) + return 1; /* Identical. */ + } + return 0; +} + + +/* Return true if CERT is already contained in CERTLIST. */ +static int +is_cert_in_certlist (ksba_cert_t cert, certlist_t certlist) +{ + const unsigned char *img_a, *img_b; + size_t len_a, len_b; + + img_a = ksba_cert_get_image (cert, &len_a); + if (img_a) + { + for ( ; certlist; certlist = certlist->next) + { + img_b = ksba_cert_get_image (certlist->cert, &len_b); + if (img_b && len_a == len_b && !memcmp (img_a, img_b, len_a)) + return 1; /* Already contained. */ + } + } + return 0; +} + + +/* Add CERT to the list of certificates at CERTADDR but avoid + duplicates. */ +int +gpgsm_add_cert_to_certlist (ctrl_t ctrl, ksba_cert_t cert, + certlist_t *listaddr, int is_encrypt_to) +{ + (void)ctrl; + + if (!is_cert_in_certlist (cert, *listaddr)) + { + certlist_t cl = xtrycalloc (1, sizeof *cl); + if (!cl) + return out_of_core (); + cl->cert = cert; + ksba_cert_ref (cert); + cl->next = *listaddr; + cl->is_encrypt_to = is_encrypt_to; + *listaddr = cl; + } + return 0; +} + +/* Add a certificate to a list of certificate and make sure that it is + a valid certificate. With SECRET set to true a secret key must be + available for the certificate. IS_ENCRYPT_TO sets the corresponding + flag in the new create LISTADDR item. */ +int +gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret, + certlist_t *listaddr, int is_encrypt_to) +{ + int rc; + KEYDB_SEARCH_DESC desc; + KEYDB_HANDLE kh = NULL; + ksba_cert_t cert = NULL; + + rc = keydb_classify_name (name, &desc); + if (!rc) + { + kh = keydb_new (0); + if (!kh) + rc = gpg_error (GPG_ERR_ENOMEM); + else + { + int wrong_usage = 0; + char *first_subject = NULL; + char *first_issuer = NULL; + + get_next: + rc = keydb_search (kh, &desc, 1); + if (!rc) + rc = keydb_get_cert (kh, &cert); + if (!rc) + { + if (!first_subject) + { + /* Save the the subject and the issuer for key usage + and ambiguous name tests. */ + first_subject = ksba_cert_get_subject (cert, 0); + first_issuer = ksba_cert_get_issuer (cert, 0); + } + rc = secret? gpgsm_cert_use_sign_p (cert) + : gpgsm_cert_use_encrypt_p (cert); + if (gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE) + { + /* There might be another certificate with the + correct usage, so we try again */ + if (!wrong_usage) + { /* save the first match */ + wrong_usage = rc; + ksba_cert_release (cert); + cert = NULL; + goto get_next; + } + else if (same_subject_issuer (first_subject, first_issuer, + cert)) + { + wrong_usage = rc; + ksba_cert_release (cert); + cert = NULL; + goto get_next; + } + else + wrong_usage = rc; + + } + } + /* We want the error code from the first match in this case. */ + if (rc && wrong_usage) + rc = wrong_usage; + + if (!rc) + { + certlist_t dup_certs = NULL; + + next_ambigious: + rc = keydb_search (kh, &desc, 1); + if (rc == -1) + rc = 0; + else if (!rc) + { + ksba_cert_t cert2 = NULL; + + /* If this is the first possible duplicate, add the original + certificate to our list of duplicates. */ + if (!dup_certs) + gpgsm_add_cert_to_certlist (ctrl, cert, &dup_certs, 0); + + /* We have to ignore ambigious names as long as + there only fault is a bad key usage. This is + required to support encryption and signing + certificates of the same subject. + + Further we ignore them if they are due to an + identical certificate (which may happen if a + certificate is accidential duplicated in the + keybox). */ + if (!keydb_get_cert (kh, &cert2)) + { + int tmp = (same_subject_issuer (first_subject, + first_issuer, + cert2) + && ((gpg_err_code ( + secret? gpgsm_cert_use_sign_p (cert2) + : gpgsm_cert_use_encrypt_p (cert2) + ) + ) == GPG_ERR_WRONG_KEY_USAGE)); + if (tmp) + gpgsm_add_cert_to_certlist (ctrl, cert2, + &dup_certs, 0); + else + { + if (is_cert_in_certlist (cert2, dup_certs)) + tmp = 1; + } + + ksba_cert_release (cert2); + if (tmp) + goto next_ambigious; + } + rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + } + gpgsm_release_certlist (dup_certs); + } + xfree (first_subject); + xfree (first_issuer); + first_subject = NULL; + first_issuer = NULL; + + if (!rc && !is_cert_in_certlist (cert, *listaddr)) + { + if (!rc && secret) + { + char *p; + + rc = gpg_error (GPG_ERR_NO_SECKEY); + p = gpgsm_get_keygrip_hexstring (cert); + if (p) + { + if (!gpgsm_agent_havekey (ctrl, p)) + rc = 0; + xfree (p); + } + } + if (!rc) + rc = gpgsm_validate_chain (ctrl, cert, "", NULL, + 0, NULL, 0, NULL); + if (!rc) + { + certlist_t cl = xtrycalloc (1, sizeof *cl); + if (!cl) + rc = out_of_core (); + else + { + cl->cert = cert; cert = NULL; + cl->next = *listaddr; + cl->is_encrypt_to = is_encrypt_to; + *listaddr = cl; + } + } + } + } + } + + keydb_release (kh); + ksba_cert_release (cert); + return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc; +} + + +void +gpgsm_release_certlist (certlist_t list) +{ + while (list) + { + certlist_t cl = list->next; + ksba_cert_release (list->cert); + xfree (list); + list = cl; + } +} + + +/* Like gpgsm_add_to_certlist, but look only for one certificate. No + chain validation is done. If KEYID is not NULL it is taken as an + additional filter value which must match the + subjectKeyIdentifier. */ +int +gpgsm_find_cert (const char *name, ksba_sexp_t keyid, ksba_cert_t *r_cert) +{ + int rc; + KEYDB_SEARCH_DESC desc; + KEYDB_HANDLE kh = NULL; + + *r_cert = NULL; + rc = keydb_classify_name (name, &desc); + if (!rc) + { + kh = keydb_new (0); + if (!kh) + rc = gpg_error (GPG_ERR_ENOMEM); + else + { + nextone: + rc = keydb_search (kh, &desc, 1); + if (!rc) + { + rc = keydb_get_cert (kh, r_cert); + if (!rc && keyid) + { + ksba_sexp_t subj; + + rc = ksba_cert_get_subj_key_id (*r_cert, NULL, &subj); + if (!rc) + { + if (cmp_simple_canon_sexp (keyid, subj)) + { + xfree (subj); + goto nextone; + } + xfree (subj); + /* Okay: Here we know that the certificate's + subjectKeyIdentifier matches the requested + one. */ + } + else if (gpg_err_code (rc) == GPG_ERR_NO_DATA) + goto nextone; + } + } + + /* If we don't have the KEYID filter we need to check for + ambigious search results. Note, that it is somehwat + reasonable to assume that a specification of a KEYID + won't lead to ambiguous names. */ + if (!rc && !keyid) + { + next_ambiguous: + rc = keydb_search (kh, &desc, 1); + if (rc == -1) + rc = 0; + else + { + if (!rc) + { + ksba_cert_t cert2 = NULL; + + if (!keydb_get_cert (kh, &cert2)) + { + if (gpgsm_certs_identical_p (*r_cert, cert2)) + { + ksba_cert_release (cert2); + goto next_ambiguous; + } + ksba_cert_release (cert2); + } + rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + } + ksba_cert_release (*r_cert); + *r_cert = NULL; + } + } + } + } + + keydb_release (kh); + return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc; +} + diff --git a/sm/certreqgen-ui.c b/sm/certreqgen-ui.c new file mode 100644 index 0000000..3e98b66 --- /dev/null +++ b/sm/certreqgen-ui.c @@ -0,0 +1,415 @@ +/* certreqgen-ui.c - Simple user interface for certreqgen.c + * Copyright (C) 2007 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> + +#include "i18n.h" +#include "ttyio.h" +#include "membuf.h" + + +/* Prompt for lines and append them to MB. */ +static void +ask_mb_lines (membuf_t *mb, const char *prefix) +{ + char *answer = NULL; + + do + { + xfree (answer); + answer = tty_get ("> "); + tty_kill_prompt (); + trim_spaces (answer); + if (*answer) + { + put_membuf_str (mb, prefix); + put_membuf_str (mb, answer); + put_membuf (mb, "\n", 1); + } + } + while (*answer); + xfree (answer); +} + +/* Helper to store stuff in a membuf. */ +void +store_key_value_lf (membuf_t *mb, const char *key, const char *value) +{ + put_membuf_str (mb, key); + put_membuf_str (mb, value); + put_membuf (mb, "\n", 1); +} + +/* Helper tp store a membuf create by mb_ask_lines into MB. Returns + -1 on error. */ +int +store_mb_lines (membuf_t *mb, membuf_t *lines) +{ + char *p; + + if (get_membuf_len (lines)) + { + put_membuf (lines, "", 1); + p = get_membuf (lines, NULL); + if (!p) + return -1; + put_membuf_str (mb, p); + xfree (p); + } + return 0; +} + + +/* Chech whether we have a key for the key with HEXGRIP. Returns NULL + if not or a string describing the type of the key (RSA, ELG, DSA, + etc..). */ +static const char * +check_keygrip (ctrl_t ctrl, const char *hexgrip) +{ + gpg_error_t err; + ksba_sexp_t public; + size_t publiclen; + int algo; + + if (hexgrip[0] == '&') + hexgrip++; + + err = gpgsm_agent_readkey (ctrl, 0, hexgrip, &public); + if (err) + return NULL; + publiclen = gcry_sexp_canon_len (public, 0, NULL, NULL); + + get_pk_algo_from_canon_sexp (public, publiclen, &algo); + xfree (public); + + switch (algo) + { + case GCRY_PK_RSA: return "RSA"; + case GCRY_PK_DSA: return "DSA"; + case GCRY_PK_ELG: return "ELG"; + case GCRY_PK_ECDSA: return "ECDSA"; + default: return NULL; + } +} + + +/* This function is used to create a certificate request from the + command line. In the past the similar gpgsm-gencert.sh script has + been used for it; however that scripts requires a full Unix shell + and thus is not suitable for the Windows port. So here is the + re-implementation. */ +void +gpgsm_gencertreq_tty (ctrl_t ctrl, FILE *output_fp) +{ + gpg_error_t err; + char *answer; + int selection; + estream_t fp = NULL; + int method; + char *keytype_buffer = NULL; + const char *keytype; + char *keygrip = NULL; + unsigned int nbits; + int minbits = 1024; + int maxbits = 4096; + int defbits = 2048; + const char *keyusage; + char *subject_name; + membuf_t mb_email, mb_dns, mb_uri, mb_result; + char *result = NULL; + int i; + const char *s, *s2; + + answer = NULL; + init_membuf (&mb_email, 100); + init_membuf (&mb_dns, 100); + init_membuf (&mb_uri, 100); + init_membuf (&mb_result, 512); + + again: + /* Get the type of the key. */ + tty_printf (_("Please select what kind of key you want:\n")); + tty_printf (_(" (%d) RSA\n"), 1 ); + tty_printf (_(" (%d) Existing key\n"), 2 ); + tty_printf (_(" (%d) Existing key from card\n"), 3 ); + + do + { + xfree (answer); + answer = tty_get (_("Your selection? ")); + tty_kill_prompt (); + selection = *answer? atoi (answer): 1; + } + while (!(selection >= 1 && selection <= 3)); + method = selection; + + /* Get size of the key. */ + if (method == 1) + { + keytype = "RSA"; + for (;;) + { + xfree (answer); + answer = tty_getf (_("What keysize do you want? (%u) "), defbits); + tty_kill_prompt (); + trim_spaces (answer); + nbits = *answer? atoi (answer): defbits; + if (nbits < minbits || nbits > maxbits) + tty_printf(_("%s keysizes must be in the range %u-%u\n"), + "RSA", minbits, maxbits); + else + break; /* Okay. */ + } + tty_printf (_("Requested keysize is %u bits\n"), nbits); + /* We round it up so that it better matches the word size. */ + if (( nbits % 64)) + { + nbits = ((nbits + 63) / 64) * 64; + tty_printf (_("rounded up to %u bits\n"), nbits); + } + } + else if (method == 2) + { + for (;;) + { + xfree (answer); + answer = tty_get (_("Enter the keygrip: ")); + tty_kill_prompt (); + trim_spaces (answer); + + if (!*answer) + goto again; + else if (strlen (answer) != 40 && + !(answer[0] == '&' && strlen (answer+1) == 40)) + tty_printf (_("Not a valid keygrip (expecting 40 hex digits)\n")); + else if (!(keytype = check_keygrip (ctrl, answer)) ) + tty_printf (_("No key with this keygrip\n")); + else + break; /* Okay. */ + } + xfree (keygrip); + keygrip = answer; + answer = NULL; + nbits = 1024; /* A dummy value is sufficient. */ + } + else /* method == 3 */ + { + char *serialno; + strlist_t keypairlist, sl; + int count; + + err = gpgsm_agent_scd_serialno (ctrl, &serialno); + if (err) + { + tty_printf (_("error reading the card: %s\n"), gpg_strerror (err)); + goto again; + } + tty_printf (_("Serial number of the card: %s\n"), serialno); + xfree (serialno); + + err = gpgsm_agent_scd_keypairinfo (ctrl, &keypairlist); + if (err) + { + tty_printf (_("error reading the card: %s\n"), gpg_strerror (err)); + goto again; + } + + do + { + tty_printf (_("Available keys:\n")); + for (count=1,sl=keypairlist; sl; sl = sl->next, count++) + tty_printf (" (%d) %s\n", count, sl->d); + xfree (answer); + answer = tty_get (_("Your selection? ")); + tty_kill_prompt (); + trim_spaces (answer); + selection = atoi (answer); + } + while (!(selection > 0 && selection < count)); + + for (count=1,sl=keypairlist; sl; sl = sl->next, count++) + if (count == selection) + break; + + s = sl->d; + while (*s && !spacep (s)) + s++; + while (spacep (s)) + s++; + + xfree (keygrip); + keygrip = NULL; + xfree (keytype_buffer); + keytype_buffer = xasprintf ("card:%s", s); + free_strlist (keypairlist); + keytype = keytype_buffer; + nbits = 1024; /* A dummy value is sufficient. */ + } + + /* Ask for the key usage. */ + tty_printf (_("Possible actions for a %s key:\n"), "RSA"); + tty_printf (_(" (%d) sign, encrypt\n"), 1 ); + tty_printf (_(" (%d) sign\n"), 2 ); + tty_printf (_(" (%d) encrypt\n"), 3 ); + do + { + xfree (answer); + answer = tty_get (_("Your selection? ")); + tty_kill_prompt (); + trim_spaces (answer); + selection = *answer? atoi (answer): 1; + switch (selection) + { + case 1: keyusage = "sign, encrypt"; break; + case 2: keyusage = "sign"; break; + case 3: keyusage = "encrypt"; break; + default: keyusage = NULL; break; + } + } + while (!keyusage); + + /* Get the subject name. */ + do + { + size_t erroff, errlen; + + xfree (answer); + answer = tty_get (_("Enter the X.509 subject name: ")); + tty_kill_prompt (); + trim_spaces (answer); + if (!*answer) + tty_printf (_("No subject name given\n")); + else if ( (err = ksba_dn_teststr (answer, 0, &erroff, &errlen)) ) + { + if (gpg_err_code (err) == GPG_ERR_UNKNOWN_NAME) + tty_printf (_("Invalid subject name label `%.*s'\n"), + (int)errlen, answer+erroff); + else + { + /* TRANSLATORS: The 22 in the second string is the + length of the first string up to the "%s". Please + adjust it do the length of your translation. The + second string is merely passed to atoi so you can + drop everything after the number. */ + tty_printf (_("Invalid subject name `%s'\n"), answer); + tty_printf ("%*s^\n", + atoi (_("22 translator: see " + "certreg-ui.c:gpgsm_gencertreq_tty")) + + (int)erroff, ""); + } + *answer = 0; + } + } + while (!*answer); + subject_name = answer; + answer = NULL; + + /* Get the email addresses. */ + tty_printf (_("Enter email addresses")); + tty_printf (_(" (end with an empty line):\n")); + ask_mb_lines (&mb_email, "Name-Email: "); + + /* DNS names. */ + tty_printf (_("Enter DNS names")); + tty_printf (_(" (optional; end with an empty line):\n")); + ask_mb_lines (&mb_email, "Name-DNS: "); + + /* URIs. */ + tty_printf (_("Enter URIs")); + tty_printf (_(" (optional; end with an empty line):\n")); + ask_mb_lines (&mb_email, "Name-URI: "); + + + /* Put it all together. */ + store_key_value_lf (&mb_result, "Key-Type: ", keytype); + { + char numbuf[30]; + snprintf (numbuf, sizeof numbuf, "%u", nbits); + store_key_value_lf (&mb_result, "Key-Length: ", numbuf); + } + store_key_value_lf (&mb_result, "Key-Usage: ", keyusage); + store_key_value_lf (&mb_result, "Name-DN: ", subject_name); + if (keygrip) + store_key_value_lf (&mb_result, "Key-Grip: ", keygrip); + if (store_mb_lines (&mb_result, &mb_email)) + goto mem_error; + if (store_mb_lines (&mb_result, &mb_dns)) + goto mem_error; + if (store_mb_lines (&mb_result, &mb_uri)) + goto mem_error; + put_membuf (&mb_result, "", 1); + result = get_membuf (&mb_result, NULL); + if (!result) + goto mem_error; + + tty_printf (_("Parameters to be used for the certificate request:\n")); + for (s=result; (s2 = strchr (s, '\n')); s = s2+1, i++) + tty_printf (" %.*s\n", (int)(s2-s), s); + tty_printf ("\n"); + + + if (!tty_get_answer_is_yes ("Really create request? (y/N) ")) + goto leave; + + /* Now create a parameter file and generate the key. */ + fp = es_fopenmem (0, "w+"); + if (!fp) + { + log_error (_("error creating temporary file: %s\n"), strerror (errno)); + goto leave; + } + es_fputs (result, fp); + es_rewind (fp); + tty_printf (_("Now creating certificate request. " + "This may take a while ...\n")); + { + int save_pem = ctrl->create_pem; + ctrl->create_pem = 1; /* Force creation of PEM. */ + err = gpgsm_genkey (ctrl, fp, output_fp); + ctrl->create_pem = save_pem; + } + if (!err) + tty_printf (_("Ready. You should now send this request to your CA.\n")); + + + goto leave; + mem_error: + log_error (_("resource problem: out of core\n")); + leave: + es_fclose (fp); + xfree (answer); + xfree (subject_name); + xfree (keytype_buffer); + xfree (keygrip); + xfree (get_membuf (&mb_email, NULL)); + xfree (get_membuf (&mb_dns, NULL)); + xfree (get_membuf (&mb_uri, NULL)); + xfree (get_membuf (&mb_result, NULL)); + xfree (result); +} diff --git a/sm/certreqgen.c b/sm/certreqgen.c new file mode 100644 index 0000000..49b2b92 --- /dev/null +++ b/sm/certreqgen.c @@ -0,0 +1,884 @@ +/* certreqgen.c - Generate a key and a certification request + * Copyright (C) 2002, 2003, 2005, 2007 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 <http://www.gnu.org/licenses/>. + */ + +/* +The format of the native parameter file is follows: + o Text only, line length is limited to about 1000 chars. + o You must use UTF-8 encoding to specify non-ascii characters. + o Empty lines are ignored. + o Leading and trailing spaces are ignored. + o A hash sign as the first non white space character is a comment line. + o Control statements are indicated by a leading percent sign, the + arguments are separated by white space from the keyword. + o Parameters are specified by a keyword, followed by a colon. Arguments + are separated by white space. + o The first parameter must be "Key-Type", control statements + may be placed anywhere. + o Key generation takes place when either the end of the parameter file + is reached, the next "Key-Type" parameter is encountered or at the + controlstatement "%commit" + o Control statements: + %echo <text> + Print <text>. + %dry-run + Suppress actual key generation (useful for syntax checking). + %commit + Perform the key generation. Note that an implicit commit is done + at the next "Key-Type" parameter. + %certfile <filename> + Do not write the certificate to the keyDB but to <filename>. + This must be given before the first + commit to take place, duplicate specification of the same filename + is ignored, the last filename before a commit is used. + The filename is used until a new filename is used (at commit points) + and all keys are written to that file. If a new filename is given, + this file is created (and overwrites an existing one). + Both control statements must be given. + o The order of the parameters does not matter except for "Key-Type" + which must be the first parameter. The parameters are only for the + generated keyblock and parameters from previous key generations are not + used. Some syntactically checks may be performed. + The currently defined parameters are: + Key-Type: <algo> + Starts a new parameter block by giving the type of the + primary key. The algorithm must be capable of signing. + This is a required parameter. For now the only supported + algorithm is "rsa". + Key-Length: <length-in-bits> + Length of the key in bits. Default is 2048. + Key-Grip: hexstring + This is optional and used to generate a request for an already + existing key. Key-Length will be ignored when given, + Key-Usage: <usage-list> + Space or comma delimited list of key usage, allowed values are + "encrypt" and "sign". This is used to generate the KeyUsage extension. + Please make sure that the algorithm is capable of this usage. Default + is to allow encrypt and sign. + Name-DN: subject name + This is the DN name of the subject in rfc2253 format. + Name-Email: <string> + The is an email address for the altSubjectName + Name-DNS: <string> + The is an DNS name for the altSubjectName + Name-URI: <string> + The is an URI for the altSubjectName + +Here is an example: +$ cat >foo <<EOF +%echo Generating a standard key +Key-Type: RSA +Key-Length: 2048 +Name-DN: CN=test cert 1,OU=Aegypten Project,O=g10 Code GmbH,L=Düsseldorf,C=DE +Name-Email: joe@foo.bar +# Do a commit here, so that we can later print "done" :-) +%commit +%echo done +EOF +*/ + + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + + +enum para_name { + pKEYTYPE, + pKEYLENGTH, + pKEYGRIP, + pKEYUSAGE, + pNAMEDN, + pNAMEEMAIL, + pNAMEDNS, + pNAMEURI +}; + +struct para_data_s { + struct para_data_s *next; + int lnr; + enum para_name key; + union { + unsigned int usage; + char value[1]; + } u; +}; + +struct reqgen_ctrl_s { + int lnr; + int dryrun; + ksba_writer_t writer; +}; + + +static const char oidstr_keyUsage[] = "2.5.29.15"; + + +static int proc_parameters (ctrl_t ctrl, + struct para_data_s *para, + struct reqgen_ctrl_s *outctrl); +static int create_request (ctrl_t ctrl, + struct para_data_s *para, + const char *carddirect, + ksba_const_sexp_t public, + struct reqgen_ctrl_s *outctrl); + + + +static void +release_parameter_list (struct para_data_s *r) +{ + struct para_data_s *r2; + + for (; r ; r = r2) + { + r2 = r->next; + xfree(r); + } +} + +static struct para_data_s * +get_parameter (struct para_data_s *para, enum para_name key, int seq) +{ + struct para_data_s *r; + + for (r = para; r ; r = r->next) + if ( r->key == key && !seq--) + return r; + return NULL; +} + +static const char * +get_parameter_value (struct para_data_s *para, enum para_name key, int seq) +{ + struct para_data_s *r = get_parameter (para, key, seq); + return (r && *r->u.value)? r->u.value : NULL; +} + +static int +get_parameter_algo (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key, 0); + if (!r) + return -1; + if (digitp (r->u.value)) + return atoi( r->u.value ); + return gcry_pk_map_name (r->u.value); +} + +/* Parse the usage parameter. Returns 0 on success. Note that we + only care about sign and encrypt and don't (yet) allow all the + other X.509 usage to be specified; instead we will use a fixed + mapping to the X.509 usage flags. */ +static int +parse_parameter_usage (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key, 0); + char *p, *pn; + unsigned int use; + + if (!r) + return 0; /* none (this is an optional parameter)*/ + + use = 0; + pn = r->u.value; + while ( (p = strsep (&pn, " \t,")) ) + { + if (!*p) + ; + else if ( !ascii_strcasecmp (p, "sign") ) + use |= GCRY_PK_USAGE_SIGN; + else if ( !ascii_strcasecmp (p, "encrypt") ) + use |= GCRY_PK_USAGE_ENCR; + else + { + log_error ("line %d: invalid usage list\n", r->lnr); + return -1; /* error */ + } + } + r->u.usage = use; + return 0; +} + + +static unsigned int +get_parameter_uint (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key, 0); + + if (!r) + return 0; + + if (r->key == pKEYUSAGE) + return r->u.usage; + + return (unsigned int)strtoul (r->u.value, NULL, 10); +} + + + +/* Read the certificate generation parameters from FP and generate + (all) certificate requests. */ +static int +read_parameters (ctrl_t ctrl, estream_t fp, ksba_writer_t writer) +{ + static struct { + const char *name; + enum para_name key; + int allow_dups; + } keywords[] = { + { "Key-Type", pKEYTYPE}, + { "Key-Length", pKEYLENGTH }, + { "Key-Grip", pKEYGRIP }, + { "Key-Usage", pKEYUSAGE }, + { "Name-DN", pNAMEDN }, + { "Name-Email", pNAMEEMAIL, 1 }, + { "Name-DNS", pNAMEDNS, 1 }, + { "Name-URI", pNAMEURI, 1 }, + { NULL, 0 } + }; + char line[1024], *p; + const char *err = NULL; + struct para_data_s *para, *r; + int i, rc = 0, any = 0; + struct reqgen_ctrl_s outctrl; + + memset (&outctrl, 0, sizeof (outctrl)); + outctrl.writer = writer; + + err = NULL; + para = NULL; + while (es_fgets (line, DIM(line)-1, fp) ) + { + char *keyword, *value; + + outctrl.lnr++; + if (*line && line[strlen(line)-1] != '\n') + { + err = "line too long"; + break; + } + for (p=line; spacep (p); p++) + ; + if (!*p || *p == '#') + continue; + + keyword = p; + if (*keyword == '%') + { + for (; *p && !spacep (p); p++) + ; + if (*p) + *p++ = 0; + for (; spacep (p); p++) + ; + value = p; + trim_trailing_spaces (value); + + if (!ascii_strcasecmp (keyword, "%echo")) + log_info ("%s\n", value); + else if (!ascii_strcasecmp (keyword, "%dry-run")) + outctrl.dryrun = 1; + else if (!ascii_strcasecmp( keyword, "%commit")) + { + rc = proc_parameters (ctrl, para, &outctrl); + if (rc) + goto leave; + any = 1; + release_parameter_list (para); + para = NULL; + } + else + log_info ("skipping control `%s' (%s)\n", keyword, value); + + continue; + } + + + if (!(p = strchr (p, ':')) || p == keyword) + { + err = "missing colon"; + break; + } + if (*p) + *p++ = 0; + for (; spacep (p); p++) + ; + if (!*p) + { + err = "missing argument"; + break; + } + value = p; + trim_trailing_spaces (value); + + for (i=0; (keywords[i].name + && ascii_strcasecmp (keywords[i].name, keyword)); i++) + ; + if (!keywords[i].name) + { + err = "unknown keyword"; + break; + } + if (keywords[i].key != pKEYTYPE && !para) + { + err = "parameter block does not start with \"Key-Type\""; + break; + } + + if (keywords[i].key == pKEYTYPE && para) + { + rc = proc_parameters (ctrl, para, &outctrl); + if (rc) + goto leave; + any = 1; + release_parameter_list (para); + para = NULL; + } + else if (!keywords[i].allow_dups) + { + for (r = para; r && r->key != keywords[i].key; r = r->next) + ; + if (r) + { + err = "duplicate keyword"; + break; + } + } + + r = xtrycalloc (1, sizeof *r + strlen( value )); + if (!r) + { + err = "out of core"; + break; + } + r->lnr = outctrl.lnr; + r->key = keywords[i].key; + strcpy (r->u.value, value); + r->next = para; + para = r; + } + + if (err) + { + log_error ("line %d: %s\n", outctrl.lnr, err); + rc = gpg_error (GPG_ERR_GENERAL); + } + else if (es_ferror(fp)) + { + log_error ("line %d: read error: %s\n", outctrl.lnr, strerror(errno) ); + rc = gpg_error (GPG_ERR_GENERAL); + } + else if (para) + { + rc = proc_parameters (ctrl, para, &outctrl); + if (rc) + goto leave; + any = 1; + } + + if (!rc && !any) + rc = gpg_error (GPG_ERR_NO_DATA); + + leave: + release_parameter_list (para); + return rc; +} + +/* check whether there are invalid characters in the email address S */ +static int +has_invalid_email_chars (const char *s) +{ + int at_seen=0; + static char valid_chars[] = "01234567890_-." + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + for (; *s; s++) + { + if (*s & 0x80) + return 1; + if (*s == '@') + at_seen++; + else if (!at_seen && !( !!strchr (valid_chars, *s) || *s == '+')) + return 1; + else if (at_seen && !strchr (valid_chars, *s)) + return 1; + } + return at_seen != 1; +} + + +/* Check that all required parameters are given and perform the action */ +static int +proc_parameters (ctrl_t ctrl, + struct para_data_s *para, struct reqgen_ctrl_s *outctrl) +{ + gpg_error_t err; + struct para_data_s *r; + const char *s; + int i; + unsigned int nbits; + char numbuf[20]; + unsigned char keyparms[100]; + int rc; + ksba_sexp_t public; + int seq; + size_t erroff, errlen; + char *cardkeyid = NULL; + + /* Check that we have all required parameters; */ + assert (get_parameter (para, pKEYTYPE, 0)); + + /* We can only use RSA for now. There is a problem with pkcs-10 on + how to use ElGamal because it is expected that a PK algorithm can + always be used for signing. Another problem is that on-card + generated encryption keys may not be used for signing. */ + i = get_parameter_algo (para, pKEYTYPE); + if (!i && (s = get_parameter_value (para, pKEYTYPE, 0)) && *s) + { + /* Hack to allow creation of certificates directly from a smart + card. For example: "Key-Type: card:OPENPGP.3". */ + if (!strncmp (s, "card:", 5) && s[5]) + cardkeyid = xtrystrdup (s+5); + } + if ( (i < 1 || i != GCRY_PK_RSA) && !cardkeyid ) + { + r = get_parameter (para, pKEYTYPE, 0); + log_error (_("line %d: invalid algorithm\n"), r->lnr); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* Check the keylength. */ + if (!get_parameter (para, pKEYLENGTH, 0)) + nbits = 2048; + else + nbits = get_parameter_uint (para, pKEYLENGTH); + if ((nbits < 1024 || nbits > 4096) && !cardkeyid) + { + /* The BSI specs dated 2002-11-25 don't allow lengths below 1024. */ + r = get_parameter (para, pKEYLENGTH, 0); + log_error (_("line %d: invalid key length %u (valid are %d to %d)\n"), + r->lnr, nbits, 1024, 4096); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* Check the usage. */ + if (parse_parameter_usage (para, pKEYUSAGE)) + { + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* Check that there is a subject name and that this DN fits our + requirements. */ + if (!(s=get_parameter_value (para, pNAMEDN, 0))) + { + r = get_parameter (para, pNAMEDN, 0); + log_error (_("line %d: no subject name given\n"), r->lnr); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + err = ksba_dn_teststr (s, 0, &erroff, &errlen); + if (err) + { + r = get_parameter (para, pNAMEDN, 0); + if (gpg_err_code (err) == GPG_ERR_UNKNOWN_NAME) + log_error (_("line %d: invalid subject name label `%.*s'\n"), + r->lnr, (int)errlen, s+erroff); + else + log_error (_("line %d: invalid subject name `%s' at pos %d\n"), + r->lnr, s, (int)erroff); + + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* Check that the optional email address is okay. */ + for (seq=0; (s=get_parameter_value (para, pNAMEEMAIL, seq)); seq++) + { + if (has_invalid_email_chars (s) + || *s == '@' + || s[strlen(s)-1] == '@' + || s[strlen(s)-1] == '.' + || strstr(s, "..")) + { + r = get_parameter (para, pNAMEEMAIL, seq); + log_error (_("line %d: not a valid email address\n"), r->lnr); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + if (cardkeyid) /* Take the key from the current smart card. */ + { + rc = gpgsm_agent_readkey (ctrl, 1, cardkeyid, &public); + if (rc) + { + r = get_parameter (para, pKEYTYPE, 0); + log_error (_("line %d: error reading key `%s' from card: %s\n"), + r->lnr, cardkeyid, gpg_strerror (rc)); + xfree (cardkeyid); + return rc; + } + } + else if ((s=get_parameter_value (para, pKEYGRIP, 0))) /* Use existing key.*/ + { + rc = gpgsm_agent_readkey (ctrl, 0, s, &public); + if (rc) + { + r = get_parameter (para, pKEYTYPE, 0); + log_error (_("line %d: error getting key by keygrip `%s': %s\n"), + r->lnr, s, gpg_strerror (rc)); + xfree (cardkeyid); + return rc; + } + } + else /* Generate new key. */ + { + sprintf (numbuf, "%u", nbits); + snprintf ((char*)keyparms, DIM (keyparms)-1, + "(6:genkey(3:rsa(5:nbits%d:%s)))", + (int)strlen (numbuf), numbuf); + rc = gpgsm_agent_genkey (ctrl, keyparms, &public); + if (rc) + { + r = get_parameter (para, pKEYTYPE, 0); + log_error (_("line %d: key generation failed: %s <%s>\n"), + r->lnr, gpg_strerror (rc), gpg_strsource (rc)); + xfree (cardkeyid); + return rc; + } + } + + rc = create_request (ctrl, para, cardkeyid, public, outctrl); + xfree (public); + xfree (cardkeyid); + + return rc; +} + + +/* Parameters are checked, the key pair has been created. Now + generate the request and write it out */ +static int +create_request (ctrl_t ctrl, + struct para_data_s *para, + const char *carddirect, + ksba_const_sexp_t public, + struct reqgen_ctrl_s *outctrl) +{ + ksba_certreq_t cr; + gpg_error_t err; + gcry_md_hd_t md; + ksba_stop_reason_t stopreason; + int rc = 0; + const char *s; + unsigned int use; + int seq; + char *buf, *p; + size_t len; + char numbuf[30]; + + err = ksba_certreq_new (&cr); + if (err) + return err; + + rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_start_debug (md, "cr.cri"); + + ksba_certreq_set_hash_function (cr, HASH_FNC, md); + ksba_certreq_set_writer (cr, outctrl->writer); + + err = ksba_certreq_add_subject (cr, get_parameter_value (para, pNAMEDN, 0)); + if (err) + { + log_error ("error setting the subject's name: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + + for (seq=0; (s = get_parameter_value (para, pNAMEEMAIL, seq)); seq++) + { + buf = xtrymalloc (strlen (s) + 3); + if (!buf) + { + rc = out_of_core (); + goto leave; + } + *buf = '<'; + strcpy (buf+1, s); + strcat (buf+1, ">"); + err = ksba_certreq_add_subject (cr, buf); + xfree (buf); + if (err) + { + log_error ("error setting the subject's alternate name: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + + for (seq=0; (s = get_parameter_value (para, pNAMEDNS, seq)); seq++) + { + len = strlen (s); + assert (len); + snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len); + buf = p = xtrymalloc (11 + strlen (numbuf) + len + 3); + if (!buf) + { + rc = out_of_core (); + goto leave; + } + p = stpcpy (p, "(8:dns-name"); + p = stpcpy (p, numbuf); + p = stpcpy (p, s); + strcpy (p, ")"); + + err = ksba_certreq_add_subject (cr, buf); + xfree (buf); + if (err) + { + log_error ("error setting the subject's alternate name: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + + for (seq=0; (s = get_parameter_value (para, pNAMEURI, seq)); seq++) + { + len = strlen (s); + assert (len); + snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len); + buf = p = xtrymalloc (6 + strlen (numbuf) + len + 3); + if (!buf) + { + rc = out_of_core (); + goto leave; + } + p = stpcpy (p, "(3:uri"); + p = stpcpy (p, numbuf); + p = stpcpy (p, s); + strcpy (p, ")"); + + err = ksba_certreq_add_subject (cr, buf); + xfree (buf); + if (err) + { + log_error ("error setting the subject's alternate name: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + + + err = ksba_certreq_set_public_key (cr, public); + if (err) + { + log_error ("error setting the public key: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + + + use = get_parameter_uint (para, pKEYUSAGE); + if (use == GCRY_PK_USAGE_SIGN) + { + /* For signing only we encode the bits: + KSBA_KEYUSAGE_DIGITAL_SIGNATURE + KSBA_KEYUSAGE_NON_REPUDIATION */ + err = ksba_certreq_add_extension (cr, oidstr_keyUsage, 1, + "\x03\x02\x06\xC0", 4); + } + else if (use == GCRY_PK_USAGE_ENCR) + { + /* For encrypt only we encode the bits: + KSBA_KEYUSAGE_KEY_ENCIPHERMENT + KSBA_KEYUSAGE_DATA_ENCIPHERMENT */ + err = ksba_certreq_add_extension (cr, oidstr_keyUsage, 1, + "\x03\x02\x04\x30", 4); + } + else + err = 0; /* Both or none given: don't request one. */ + if (err) + { + log_error ("error setting the key usage: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + + + do + { + err = ksba_certreq_build (cr, &stopreason); + if (err) + { + log_error ("ksba_certreq_build failed: %s\n", gpg_strerror (err)); + rc = err; + goto leave; + } + if (stopreason == KSBA_SR_NEED_SIG) + { + gcry_sexp_t s_pkey; + size_t n; + unsigned char grip[20]; + char hexgrip[41]; + unsigned char *sigval; + size_t siglen; + + n = gcry_sexp_canon_len (public, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + rc = gpg_error (GPG_ERR_BUG); + goto leave; + } + rc = gcry_sexp_sscan (&s_pkey, NULL, (const char*)public, n); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if ( !gcry_pk_get_keygrip (s_pkey, grip) ) + { + rc = gpg_error (GPG_ERR_GENERAL); + log_error ("can't figure out the keygrip\n"); + gcry_sexp_release (s_pkey); + goto leave; + } + gcry_sexp_release (s_pkey); + bin2hex (grip, 20, hexgrip); + + log_info ("about to sign CSR for key: &%s\n", hexgrip); + + if (carddirect) + rc = gpgsm_scd_pksign (ctrl, carddirect, NULL, + gcry_md_read(md, GCRY_MD_SHA1), + gcry_md_get_algo_dlen (GCRY_MD_SHA1), + GCRY_MD_SHA1, + &sigval, &siglen); + else + { + char *orig_codeset; + char *desc; + + orig_codeset = i18n_switchto_utf8 (); + desc = percent_plus_escape + (_("To complete this certificate request please enter" + " the passphrase for the key you just created once" + " more.\n")); + i18n_switchback (orig_codeset); + rc = gpgsm_agent_pksign (ctrl, hexgrip, desc, + gcry_md_read(md, GCRY_MD_SHA1), + gcry_md_get_algo_dlen (GCRY_MD_SHA1), + GCRY_MD_SHA1, + &sigval, &siglen); + xfree (desc); + } + if (rc) + { + log_error ("signing failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + err = ksba_certreq_set_sig_val (cr, sigval); + xfree (sigval); + if (err) + { + log_error ("failed to store the sig_val: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + } + while (stopreason != KSBA_SR_READY); + + + leave: + gcry_md_close (md); + ksba_certreq_release (cr); + return rc; +} + + + +/* Create a new key by reading the parameters from IN_FP. Multiple + keys may be created */ +int +gpgsm_genkey (ctrl_t ctrl, estream_t in_stream, FILE *out_fp) +{ + int rc; + Base64Context b64writer = NULL; + ksba_writer_t writer; + + ctrl->pem_name = "CERTIFICATE REQUEST"; + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, NULL, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + + rc = read_parameters (ctrl, in_stream, writer); + if (rc) + { + log_error ("error creating certificate request: %s <%s>\n", + gpg_strerror (rc), gpg_strsource (rc)); + goto leave; + } + + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + gpgsm_status (ctrl, STATUS_KEY_CREATED, "P"); + log_info ("certificate request created\n"); + + leave: + gpgsm_destroy_writer (b64writer); + return rc; +} + diff --git a/sm/decrypt.c b/sm/decrypt.c new file mode 100644 index 0000000..de02551 --- /dev/null +++ b/sm/decrypt.c @@ -0,0 +1,586 @@ +/* decrypt.c - Decrypt a message + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + +struct decrypt_filter_parm_s { + int algo; + int mode; + int blklen; + gcry_cipher_hd_t hd; + char iv[16]; + size_t ivlen; + int any_data; /* dod we push anything through the filter at all? */ + unsigned char lastblock[16]; /* to strip the padding we have to + keep this one */ + char helpblock[16]; /* needed because there is no block buffering in + libgcrypt (yet) */ + int helpblocklen; +}; + + + +/* Decrypt the session key and fill in the parm structure. The + algo and the IV is expected to be already in PARM. */ +static int +prepare_decryption (ctrl_t ctrl, const char *hexkeygrip, const char *desc, + ksba_const_sexp_t enc_val, + struct decrypt_filter_parm_s *parm) +{ + char *seskey = NULL; + size_t n, seskeylen; + int rc; + + rc = gpgsm_agent_pkdecrypt (ctrl, hexkeygrip, desc, enc_val, + &seskey, &seskeylen); + if (rc) + { + log_error ("error decrypting session key: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (DBG_CRYPTO) + log_printhex ("pkcs1 encoded session key:", seskey, seskeylen); + + n=0; + if (seskeylen == 24) + { + /* Smells like a 3-des key. This might happen because a SC has + already done the unpacking. */ + } + else + { + if (n + 7 > seskeylen ) + { + rc = gpg_error (GPG_ERR_INV_SESSION_KEY); + goto leave; + } + + /* FIXME: Actually the leading zero is required but due to the way + we encode the output in libgcrypt as an MPI we are not able to + encode that leading zero. However, when using a Smartcard we are + doing it the right way and therefore we have to skip the zero. This + should be fixed in gpg-agent of course. */ + if (!seskey[n]) + n++; + + if (seskey[n] != 2 ) /* Wrong block type version. */ + { + rc = gpg_error (GPG_ERR_INV_SESSION_KEY); + goto leave; + } + + for (n++; n < seskeylen && seskey[n]; n++) /* Skip the random bytes. */ + ; + n++; /* and the zero byte */ + if (n >= seskeylen ) + { + rc = gpg_error (GPG_ERR_INV_SESSION_KEY); + goto leave; + } + } + + if (DBG_CRYPTO) + log_printhex ("session key:", seskey+n, seskeylen-n); + + rc = gcry_cipher_open (&parm->hd, parm->algo, parm->mode, 0); + if (rc) + { + log_error ("error creating decryptor: %s\n", gpg_strerror (rc)); + goto leave; + } + + rc = gcry_cipher_setkey (parm->hd, seskey+n, seskeylen-n); + if (gpg_err_code (rc) == GPG_ERR_WEAK_KEY) + { + log_info (_("WARNING: message was encrypted with " + "a weak key in the symmetric cipher.\n")); + rc = 0; + } + if (rc) + { + log_error("key setup failed: %s\n", gpg_strerror(rc) ); + goto leave; + } + + gcry_cipher_setiv (parm->hd, parm->iv, parm->ivlen); + + leave: + xfree (seskey); + return rc; +} + + +/* This function is called by the KSBA writer just before the actual + write is done. The function must take INLEN bytes from INBUF, + decrypt it and store it inoutbuf which has a maximum size of + maxoutlen. The valid bytes in outbuf should be return in outlen. + Due to different buffer sizes or different length of input and + output, it may happen that fewer bytes are processed or fewer bytes + are written. */ +static gpg_error_t +decrypt_filter (void *arg, + const void *inbuf, size_t inlen, size_t *inused, + void *outbuf, size_t maxoutlen, size_t *outlen) +{ + struct decrypt_filter_parm_s *parm = arg; + int blklen = parm->blklen; + size_t orig_inlen = inlen; + + /* fixme: Should we issue an error when we have not seen one full block? */ + if (!inlen) + return gpg_error (GPG_ERR_BUG); + + if (maxoutlen < 2*parm->blklen) + return gpg_error (GPG_ERR_BUG); + /* Make some space because we will later need an extra block at the end. */ + maxoutlen -= blklen; + + if (parm->helpblocklen) + { + int i, j; + + for (i=parm->helpblocklen,j=0; i < blklen && j < inlen; i++, j++) + parm->helpblock[i] = ((const char*)inbuf)[j]; + inlen -= j; + if (blklen > maxoutlen) + return gpg_error (GPG_ERR_BUG); + if (i < blklen) + { + parm->helpblocklen = i; + *outlen = 0; + } + else + { + parm->helpblocklen = 0; + if (parm->any_data) + { + memcpy (outbuf, parm->lastblock, blklen); + *outlen =blklen; + } + else + *outlen = 0; + gcry_cipher_decrypt (parm->hd, parm->lastblock, blklen, + parm->helpblock, blklen); + parm->any_data = 1; + } + *inused = orig_inlen - inlen; + return 0; + } + + + if (inlen > maxoutlen) + inlen = maxoutlen; + if (inlen % blklen) + { /* store the remainder away */ + parm->helpblocklen = inlen%blklen; + inlen = inlen/blklen*blklen; + memcpy (parm->helpblock, (const char*)inbuf+inlen, parm->helpblocklen); + } + + *inused = inlen + parm->helpblocklen; + if (inlen) + { + assert (inlen >= blklen); + if (parm->any_data) + { + gcry_cipher_decrypt (parm->hd, (char*)outbuf+blklen, inlen, + inbuf, inlen); + memcpy (outbuf, parm->lastblock, blklen); + memcpy (parm->lastblock,(char*)outbuf+inlen, blklen); + *outlen = inlen; + } + else + { + gcry_cipher_decrypt (parm->hd, outbuf, inlen, inbuf, inlen); + memcpy (parm->lastblock, (char*)outbuf+inlen-blklen, blklen); + *outlen = inlen - blklen; + parm->any_data = 1; + } + } + else + *outlen = 0; + return 0; +} + + + +/* Perform a decrypt operation. */ +int +gpgsm_decrypt (ctrl_t ctrl, int in_fd, FILE *out_fp) +{ + int rc; + Base64Context b64reader = NULL; + Base64Context b64writer = NULL; + ksba_reader_t reader; + ksba_writer_t writer; + ksba_cms_t cms = NULL; + ksba_stop_reason_t stopreason; + KEYDB_HANDLE kh; + int recp; + FILE *in_fp = NULL; + struct decrypt_filter_parm_s dfparm; + + memset (&dfparm, 0, sizeof dfparm); + + audit_set_type (ctrl->audit, AUDIT_TYPE_DECRYPT); + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + + in_fp = fdopen ( dup (in_fd), "rb"); + if (!in_fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + rc = gpgsm_create_reader (&b64reader, ctrl, in_fp, 0, &reader); + if (rc) + { + log_error ("can't create reader: %s\n", gpg_strerror (rc)); + goto leave; + } + + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, NULL, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + + rc = ksba_cms_new (&cms); + if (rc) + goto leave; + + rc = ksba_cms_set_reader_writer (cms, reader, writer); + if (rc) + { + log_debug ("ksba_cms_set_reader_writer failed: %s\n", + gpg_strerror (rc)); + goto leave; + } + + audit_log (ctrl->audit, AUDIT_SETUP_READY); + + /* Parser loop. */ + do + { + rc = ksba_cms_parse (cms, &stopreason); + if (rc) + { + log_debug ("ksba_cms_parse failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (stopreason == KSBA_SR_BEGIN_DATA + || stopreason == KSBA_SR_DETACHED_DATA) + { + int algo, mode; + const char *algoid; + int any_key = 0; + + audit_log (ctrl->audit, AUDIT_GOT_DATA); + + algoid = ksba_cms_get_content_oid (cms, 2/* encryption algo*/); + algo = gcry_cipher_map_name (algoid); + mode = gcry_cipher_mode_from_oid (algoid); + if (!algo || !mode) + { + rc = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + log_error ("unsupported algorithm `%s'\n", algoid? algoid:"?"); + if (algoid && !strcmp (algoid, "1.2.840.113549.3.2")) + log_info (_("(this is the RC2 algorithm)\n")); + else if (!algoid) + log_info (_("(this does not seem to be an encrypted" + " message)\n")); + { + char numbuf[50]; + sprintf (numbuf, "%d", rc); + gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.algorithm", + numbuf, algoid?algoid:"?", NULL); + audit_log_s (ctrl->audit, AUDIT_BAD_DATA_CIPHER_ALGO, algoid); + } + + /* If it seems that this is not an encrypted message we + return a more sensible error code. */ + if (!algoid) + rc = gpg_error (GPG_ERR_NO_DATA); + + goto leave; + } + + audit_log_i (ctrl->audit, AUDIT_DATA_CIPHER_ALGO, algo); + dfparm.algo = algo; + dfparm.mode = mode; + dfparm.blklen = gcry_cipher_get_algo_blklen (algo); + if (dfparm.blklen > sizeof (dfparm.helpblock)) + return gpg_error (GPG_ERR_BUG); + + rc = ksba_cms_get_content_enc_iv (cms, + dfparm.iv, + sizeof (dfparm.iv), + &dfparm.ivlen); + if (rc) + { + log_error ("error getting IV: %s\n", gpg_strerror (rc)); + goto leave; + } + + for (recp=0; !any_key; recp++) + { + char *issuer; + ksba_sexp_t serial; + ksba_sexp_t enc_val; + char *hexkeygrip = NULL; + char *desc = NULL; + char kidbuf[16+1]; + + *kidbuf = 0; + + rc = ksba_cms_get_issuer_serial (cms, recp, &issuer, &serial); + if (rc == -1 && recp) + break; /* no more recipients */ + audit_log_i (ctrl->audit, AUDIT_NEW_RECP, recp); + if (rc) + log_error ("recp %d - error getting info: %s\n", + recp, gpg_strerror (rc)); + else + { + ksba_cert_t cert = NULL; + + log_debug ("recp %d - issuer: `%s'\n", + recp, issuer? issuer:"[NONE]"); + log_debug ("recp %d - serial: ", recp); + gpgsm_dump_serial (serial); + log_printf ("\n"); + + if (ctrl->audit) + { + char *tmpstr = gpgsm_format_sn_issuer (serial, issuer); + audit_log_s (ctrl->audit, AUDIT_RECP_NAME, tmpstr); + xfree (tmpstr); + } + + keydb_search_reset (kh); + rc = keydb_search_issuer_sn (kh, issuer, serial); + if (rc) + { + log_error ("failed to find the certificate: %s\n", + gpg_strerror(rc)); + goto oops; + } + + rc = keydb_get_cert (kh, &cert); + if (rc) + { + log_error ("failed to get cert: %s\n", gpg_strerror (rc)); + goto oops; + } + + /* Print the ENC_TO status line. Note that we can + do so only if we have the certificate. This is + in contrast to gpg where the keyID is commonly + included in the encrypted messages. It is too + cumbersome to retrieve the used algorithm, thus + we don't print it for now. We also record the + keyid for later use. */ + { + unsigned long kid[2]; + + kid[0] = gpgsm_get_short_fingerprint (cert, kid+1); + snprintf (kidbuf, sizeof kidbuf, "%08lX%08lX", + kid[1], kid[0]); + gpgsm_status2 (ctrl, STATUS_ENC_TO, + kidbuf, "0", "0", NULL); + } + + /* Put the certificate into the audit log. */ + audit_log_cert (ctrl->audit, AUDIT_SAVE_CERT, cert, 0); + + /* Just in case there is a problem with the own + certificate we print this message - should never + happen of course */ + rc = gpgsm_cert_use_decrypt_p (cert); + if (rc) + { + char numbuf[50]; + sprintf (numbuf, "%d", rc); + gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.keyusage", + numbuf, NULL); + rc = 0; + } + + hexkeygrip = gpgsm_get_keygrip_hexstring (cert); + desc = gpgsm_format_keydesc (cert); + + oops: + xfree (issuer); + xfree (serial); + ksba_cert_release (cert); + } + + if (!hexkeygrip) + ; + else if (!(enc_val = ksba_cms_get_enc_val (cms, recp))) + log_error ("recp %d - error getting encrypted session key\n", + recp); + else + { + rc = prepare_decryption (ctrl, + hexkeygrip, desc, enc_val, &dfparm); + xfree (enc_val); + if (rc) + { + log_info ("decrypting session key failed: %s\n", + gpg_strerror (rc)); + if (gpg_err_code (rc) == GPG_ERR_NO_SECKEY && *kidbuf) + gpgsm_status2 (ctrl, STATUS_NO_SECKEY, kidbuf, NULL); + } + else + { /* setup the bulk decrypter */ + any_key = 1; + ksba_writer_set_filter (writer, + decrypt_filter, + &dfparm); + } + audit_log_ok (ctrl->audit, AUDIT_RECP_RESULT, rc); + } + xfree (hexkeygrip); + xfree (desc); + } + + /* If we write an audit log add the unused recipients to the + log as well. */ + if (ctrl->audit && any_key) + { + for (;; recp++) + { + char *issuer; + ksba_sexp_t serial; + int tmp_rc; + + tmp_rc = ksba_cms_get_issuer_serial (cms, recp, + &issuer, &serial); + if (tmp_rc == -1) + break; /* no more recipients */ + audit_log_i (ctrl->audit, AUDIT_NEW_RECP, recp); + if (tmp_rc) + log_error ("recp %d - error getting info: %s\n", + recp, gpg_strerror (rc)); + else + { + char *tmpstr = gpgsm_format_sn_issuer (serial, issuer); + audit_log_s (ctrl->audit, AUDIT_RECP_NAME, tmpstr); + xfree (tmpstr); + xfree (issuer); + xfree (serial); + } + } + } + + if (!any_key) + { + rc = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + } + else if (stopreason == KSBA_SR_END_DATA) + { + ksba_writer_set_filter (writer, NULL, NULL); + if (dfparm.any_data) + { /* write the last block with padding removed */ + int i, npadding = dfparm.lastblock[dfparm.blklen-1]; + if (!npadding || npadding > dfparm.blklen) + { + log_error ("invalid padding with value %d\n", npadding); + rc = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + rc = ksba_writer_write (writer, + dfparm.lastblock, + dfparm.blklen - npadding); + if (rc) + goto leave; + + for (i=dfparm.blklen - npadding; i < dfparm.blklen; i++) + { + if (dfparm.lastblock[i] != npadding) + { + log_error ("inconsistent padding\n"); + rc = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + } + } + } + + } + while (stopreason != KSBA_SR_READY); + + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + gpgsm_status (ctrl, STATUS_DECRYPTION_OKAY, NULL); + + + leave: + audit_log_ok (ctrl->audit, AUDIT_DECRYPTION_RESULT, rc); + if (rc) + { + gpgsm_status (ctrl, STATUS_DECRYPTION_FAILED, NULL); + log_error ("message decryption failed: %s <%s>\n", + gpg_strerror (rc), gpg_strsource (rc)); + } + ksba_cms_release (cms); + gpgsm_destroy_reader (b64reader); + gpgsm_destroy_writer (b64writer); + keydb_release (kh); + if (in_fp) + fclose (in_fp); + if (dfparm.hd) + gcry_cipher_close (dfparm.hd); + return rc; +} + + diff --git a/sm/delete.c b/sm/delete.c new file mode 100644 index 0000000..fd49ebe --- /dev/null +++ b/sm/delete.c @@ -0,0 +1,182 @@ +/* delete.c - Delete certificates from the keybox. + * Copyright (C) 2002, 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + + +/* Delete a certificate or an secret key from a key database. */ +static int +delete_one (ctrl_t ctrl, const char *username) +{ + int rc = 0; + KEYDB_SEARCH_DESC desc; + KEYDB_HANDLE kh = NULL; + ksba_cert_t cert = NULL; + int duplicates = 0; + int is_ephem = 0; + + rc = keydb_classify_name (username, &desc); + if (rc) + { + log_error (_("certificate `%s' not found: %s\n"), + username, gpg_strerror (rc)); + gpgsm_status2 (ctrl, STATUS_DELETE_PROBLEM, "1", NULL); + goto leave; + } + + kh = keydb_new (0); + if (!kh) + { + log_error ("keydb_new failed\n"); + goto leave; + } + + /* If the key is specified in a unique way, include ephemeral keys + in the search. */ + if ( desc.mode == KEYDB_SEARCH_MODE_FPR + || desc.mode == KEYDB_SEARCH_MODE_FPR20 + || desc.mode == KEYDB_SEARCH_MODE_FPR16 + || desc.mode == KEYDB_SEARCH_MODE_KEYGRIP ) + { + is_ephem = 1; + keydb_set_ephemeral (kh, 1); + } + + rc = keydb_search (kh, &desc, 1); + if (!rc) + rc = keydb_get_cert (kh, &cert); + if (!rc && !is_ephem) + { + unsigned char fpr[20]; + + gpgsm_get_fingerprint (cert, 0, fpr, NULL); + + next_ambigious: + rc = keydb_search (kh, &desc, 1); + if (rc == -1) + rc = 0; + else if (!rc) + { + ksba_cert_t cert2 = NULL; + unsigned char fpr2[20]; + + /* We ignore all duplicated certificates which might have + been inserted due to program bugs. */ + if (!keydb_get_cert (kh, &cert2)) + { + gpgsm_get_fingerprint (cert2, 0, fpr2, NULL); + ksba_cert_release (cert2); + if (!memcmp (fpr, fpr2, 20)) + { + duplicates++; + goto next_ambigious; + } + } + rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + } + } + if (rc) + { + if (rc == -1) + rc = gpg_error (GPG_ERR_NO_PUBKEY); + log_error (_("certificate `%s' not found: %s\n"), + username, gpg_strerror (rc)); + gpgsm_status2 (ctrl, STATUS_DELETE_PROBLEM, "3", NULL); + goto leave; + } + + /* We need to search again to get back to the right position. */ + rc = keydb_lock (kh); + if (rc) + { + log_error (_("error locking keybox: %s\n"), gpg_strerror (rc)); + goto leave; + } + + do + { + keydb_search_reset (kh); + rc = keydb_search (kh, &desc, 1); + if (rc) + { + log_error ("problem re-searching certificate: %s\n", + gpg_strerror (rc)); + goto leave; + } + + rc = keydb_delete (kh, duplicates ? 0 : 1); + if (rc) + goto leave; + if (opt.verbose) + { + if (duplicates) + log_info (_("duplicated certificate `%s' deleted\n"), username); + else + log_info (_("certificate `%s' deleted\n"), username); + } + } + while (duplicates--); + + leave: + keydb_release (kh); + ksba_cert_release (cert); + return rc; +} + + + +/* Delete the certificates specified by NAMES. */ +int +gpgsm_delete (ctrl_t ctrl, strlist_t names) +{ + int rc; + + if (!names) + { + log_error ("nothing to delete\n"); + return gpg_error (GPG_ERR_NO_DATA); + } + + for (; names; names=names->next ) + { + rc = delete_one (ctrl, names->d); + if (rc) + { + log_error (_("deleting certificate \"%s\" failed: %s\n"), + names->d, gpg_strerror (rc) ); + return rc; + } + } + + return 0; +} diff --git a/sm/encrypt.c b/sm/encrypt.c new file mode 100644 index 0000000..a526a64 --- /dev/null +++ b/sm/encrypt.c @@ -0,0 +1,513 @@ +/* encrypt.c - Encrypt a message + * Copyright (C) 2001, 2003, 2004, 2007, 2008 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + + +struct dek_s { + const char *algoid; + int algo; + gcry_cipher_hd_t chd; + char key[32]; + int keylen; + char iv[32]; + int ivlen; +}; +typedef struct dek_s *DEK; + +struct encrypt_cb_parm_s { + FILE *fp; + DEK dek; + int eof_seen; + int ready; + int readerror; + int bufsize; + unsigned char *buffer; + int buflen; +}; + + + + + +/* Initialize the data encryption key (session key). */ +static int +init_dek (DEK dek) +{ + int rc=0, mode, i; + + dek->algo = gcry_cipher_map_name (dek->algoid); + mode = gcry_cipher_mode_from_oid (dek->algoid); + if (!dek->algo || !mode) + { + log_error ("unsupported algorithm `%s'\n", dek->algoid); + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + } + + /* Extra check for algorithms we consider to be too weak for + encryption, although we support them for decryption. Note that + there is another check below discriminating on the key length. */ + switch (dek->algo) + { + case GCRY_CIPHER_DES: + case GCRY_CIPHER_RFC2268_40: + log_error ("cipher algorithm `%s' not allowed: too weak\n", + gcry_cipher_algo_name (dek->algo)); + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + default: + break; + } + + dek->keylen = gcry_cipher_get_algo_keylen (dek->algo); + if (!dek->keylen || dek->keylen > sizeof (dek->key)) + return gpg_error (GPG_ERR_BUG); + + dek->ivlen = gcry_cipher_get_algo_blklen (dek->algo); + if (!dek->ivlen || dek->ivlen > sizeof (dek->iv)) + return gpg_error (GPG_ERR_BUG); + + /* Make sure we don't use weak keys. */ + if (dek->keylen < 100/8) + { + log_error ("key length of `%s' too small\n", dek->algoid); + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + } + + rc = gcry_cipher_open (&dek->chd, dek->algo, mode, GCRY_CIPHER_SECURE); + if (rc) + { + log_error ("failed to create cipher context: %s\n", gpg_strerror (rc)); + return rc; + } + + for (i=0; i < 8; i++) + { + gcry_randomize (dek->key, dek->keylen, GCRY_STRONG_RANDOM ); + rc = gcry_cipher_setkey (dek->chd, dek->key, dek->keylen); + if (gpg_err_code (rc) != GPG_ERR_WEAK_KEY) + break; + log_info(_("weak key created - retrying\n") ); + } + if (rc) + { + log_error ("failed to set the key: %s\n", gpg_strerror (rc)); + gcry_cipher_close (dek->chd); + dek->chd = NULL; + return rc; + } + + gcry_create_nonce (dek->iv, dek->ivlen); + rc = gcry_cipher_setiv (dek->chd, dek->iv, dek->ivlen); + if (rc) + { + log_error ("failed to set the IV: %s\n", gpg_strerror (rc)); + gcry_cipher_close (dek->chd); + dek->chd = NULL; + return rc; + } + + return 0; +} + + +static int +encode_session_key (DEK dek, gcry_sexp_t * r_data) +{ + gcry_sexp_t data; + char *p; + int rc; + + p = xtrymalloc (64 + 2 * dek->keylen); + if (!p) + return gpg_error_from_syserror (); + strcpy (p, "(data\n (flags pkcs1)\n (value #"); + bin2hex (dek->key, dek->keylen, p + strlen (p)); + strcat (p, "#))\n"); + rc = gcry_sexp_sscan (&data, NULL, p, strlen (p)); + xfree (p); + *r_data = data; + return rc; +} + + +/* Encrypt the DEK under the key contained in CERT and return it as a + canonical S-Exp in encval. */ +static int +encrypt_dek (const DEK dek, ksba_cert_t cert, unsigned char **encval) +{ + gcry_sexp_t s_ciph, s_data, s_pkey; + int rc; + ksba_sexp_t buf; + size_t len; + + *encval = NULL; + + /* get the key from the cert */ + buf = ksba_cert_get_public_key (cert); + if (!buf) + { + log_error ("no public key for recipient\n"); + return gpg_error (GPG_ERR_NO_PUBKEY); + } + len = gcry_sexp_canon_len (buf, 0, NULL, NULL); + if (!len) + { + log_error ("libksba did not return a proper S-Exp\n"); + return gpg_error (GPG_ERR_BUG); + } + rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)buf, len); + xfree (buf); buf = NULL; + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + return rc; + } + + /* Put the encoded cleartext into a simple list. */ + s_data = NULL; /* (avoid compiler warning) */ + rc = encode_session_key (dek, &s_data); + if (rc) + { + log_error ("encode_session_key failed: %s\n", gpg_strerror (rc)); + return rc; + } + + /* pass it to libgcrypt */ + rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey); + gcry_sexp_release (s_data); + gcry_sexp_release (s_pkey); + + /* Reformat it. */ + rc = make_canon_sexp (s_ciph, encval, NULL); + gcry_sexp_release (s_ciph); + return rc; +} + + + +/* do the actual encryption */ +static int +encrypt_cb (void *cb_value, char *buffer, size_t count, size_t *nread) +{ + struct encrypt_cb_parm_s *parm = cb_value; + int blklen = parm->dek->ivlen; + unsigned char *p; + size_t n; + + *nread = 0; + if (!buffer) + return -1; /* not supported */ + + if (parm->ready) + return -1; + + if (count < blklen) + BUG (); + + if (!parm->eof_seen) + { /* fillup the buffer */ + p = parm->buffer; + for (n=parm->buflen; n < parm->bufsize; n++) + { + int c = getc (parm->fp); + if (c == EOF) + { + if (ferror (parm->fp)) + { + parm->readerror = errno; + return -1; + } + parm->eof_seen = 1; + break; + } + p[n] = c; + } + parm->buflen = n; + } + + n = parm->buflen < count? parm->buflen : count; + n = n/blklen * blklen; + if (n) + { /* encrypt the stuff */ + gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n); + *nread = n; + /* Who cares about cycles, take the easy way and shift the buffer */ + parm->buflen -= n; + memmove (parm->buffer, parm->buffer+n, parm->buflen); + } + else if (parm->eof_seen) + { /* no complete block but eof: add padding */ + /* fixme: we should try to do this also in the above code path */ + int i, npad = blklen - (parm->buflen % blklen); + p = parm->buffer; + for (n=parm->buflen, i=0; n < parm->bufsize && i < npad; n++, i++) + p[n] = npad; + gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n); + *nread = n; + parm->ready = 1; + } + + return 0; +} + + + + +/* Perform an encrypt operation. + + Encrypt the data received on DATA-FD and write it to OUT_FP. The + recipients are take from the certificate given in recplist; if this + is NULL it will be encrypted for a default recipient */ +int +gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, FILE *out_fp) +{ + int rc = 0; + Base64Context b64writer = NULL; + gpg_error_t err; + ksba_writer_t writer; + ksba_reader_t reader = NULL; + ksba_cms_t cms = NULL; + ksba_stop_reason_t stopreason; + KEYDB_HANDLE kh = NULL; + struct encrypt_cb_parm_s encparm; + DEK dek = NULL; + int recpno; + FILE *data_fp = NULL; + certlist_t cl; + int count; + + memset (&encparm, 0, sizeof encparm); + + audit_set_type (ctrl->audit, AUDIT_TYPE_ENCRYPT); + + /* Check that the certificate list is not empty and that at least + one certificate is not flagged as encrypt_to; i.e. is a real + recipient. */ + for (cl = recplist; cl; cl = cl->next) + if (!cl->is_encrypt_to) + break; + if (!cl) + { + log_error(_("no valid recipients given\n")); + gpgsm_status (ctrl, STATUS_NO_RECP, "0"); + audit_log_i (ctrl->audit, AUDIT_GOT_RECIPIENTS, 0); + rc = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + + for (count = 0, cl = recplist; cl; cl = cl->next) + count++; + audit_log_i (ctrl->audit, AUDIT_GOT_RECIPIENTS, count); + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + data_fp = fdopen ( dup (data_fd), "rb"); + if (!data_fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + err = ksba_reader_new (&reader); + if (err) + rc = err; + if (!rc) + rc = ksba_reader_set_cb (reader, encrypt_cb, &encparm); + if (rc) + goto leave; + + encparm.fp = data_fp; + + ctrl->pem_name = "ENCRYPTED MESSAGE"; + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, NULL, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + + err = ksba_cms_new (&cms); + if (err) + { + rc = err; + goto leave; + } + + err = ksba_cms_set_reader_writer (cms, reader, writer); + if (err) + { + log_debug ("ksba_cms_set_reader_writer failed: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + + audit_log (ctrl->audit, AUDIT_GOT_DATA); + + /* We are going to create enveloped data with uninterpreted data as + inner content */ + err = ksba_cms_set_content_type (cms, 0, KSBA_CT_ENVELOPED_DATA); + if (!err) + err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA); + if (err) + { + log_debug ("ksba_cms_set_content_type failed: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + + /* Create a session key */ + dek = xtrycalloc_secure (1, sizeof *dek); + if (!dek) + rc = out_of_core (); + else + { + dek->algoid = opt.def_cipher_algoid; + rc = init_dek (dek); + } + if (rc) + { + log_error ("failed to create the session key: %s\n", + gpg_strerror (rc)); + goto leave; + } + + err = ksba_cms_set_content_enc_algo (cms, dek->algoid, dek->iv, dek->ivlen); + if (err) + { + log_error ("ksba_cms_set_content_enc_algo failed: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + + encparm.dek = dek; + /* Use a ~8k (AES) or ~4k (3DES) buffer */ + encparm.bufsize = 500 * dek->ivlen; + encparm.buffer = xtrymalloc (encparm.bufsize); + if (!encparm.buffer) + { + rc = out_of_core (); + goto leave; + } + + audit_log_s (ctrl->audit, AUDIT_SESSION_KEY, dek->algoid); + + /* Gather certificates of recipients, encrypt the session key for + each and store them in the CMS object */ + for (recpno = 0, cl = recplist; cl; recpno++, cl = cl->next) + { + unsigned char *encval; + + rc = encrypt_dek (dek, cl->cert, &encval); + if (rc) + { + audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, rc); + log_error ("encryption failed for recipient no. %d: %s\n", + recpno, gpg_strerror (rc)); + goto leave; + } + + err = ksba_cms_add_recipient (cms, cl->cert); + if (err) + { + audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, err); + log_error ("ksba_cms_add_recipient failed: %s\n", + gpg_strerror (err)); + rc = err; + xfree (encval); + goto leave; + } + + err = ksba_cms_set_enc_val (cms, recpno, encval); + xfree (encval); + audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, err); + if (err) + { + log_error ("ksba_cms_set_enc_val failed: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + + /* Main control loop for encryption. */ + recpno = 0; + do + { + err = ksba_cms_build (cms, &stopreason); + if (err) + { + log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err)); + rc = err; + goto leave; + } + } + while (stopreason != KSBA_SR_READY); + + if (encparm.readerror) + { + log_error ("error reading input: %s\n", strerror (encparm.readerror)); + rc = gpg_error (gpg_err_code_from_errno (encparm.readerror)); + goto leave; + } + + + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + audit_log (ctrl->audit, AUDIT_ENCRYPTION_DONE); + log_info ("encrypted data created\n"); + + leave: + ksba_cms_release (cms); + gpgsm_destroy_writer (b64writer); + ksba_reader_release (reader); + keydb_release (kh); + xfree (dek); + if (data_fp) + fclose (data_fp); + xfree (encparm.buffer); + return rc; +} diff --git a/sm/export.c b/sm/export.c new file mode 100644 index 0000000..fcf1dcc --- /dev/null +++ b/sm/export.c @@ -0,0 +1,749 @@ +/* export.c - Export certificates and private keys. + * Copyright (C) 2002, 2003, 2004, 2007, 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "exechelp.h" +#include "i18n.h" +#include "sysutils.h" + + + +/* A table to store a fingerprint as used in a duplicates table. We + don't need to hash here because a fingerprint is already a perfect + hash value. This we use the most significant bits to index the + table and then use a linked list for the overflow. Possible + enhancement for very large number of certificates: Add a second + level table and then resort to a linked list. */ +struct duptable_s +{ + struct duptable_s *next; + + /* Note that we only need to store 19 bytes because the first byte + is implictly given by the table index (we require at least 8 + bits). */ + unsigned char fpr[19]; +}; +typedef struct duptable_s *duptable_t; +#define DUPTABLE_BITS 12 +#define DUPTABLE_SIZE (1 << DUPTABLE_BITS) + + +static void print_short_info (ksba_cert_t cert, FILE *fp, estream_t stream); +static gpg_error_t export_p12 (ctrl_t ctrl, + const unsigned char *certimg, size_t certimglen, + const char *prompt, const char *keygrip, + FILE **retfp); + + +/* Create a table used to indetify duplicated certificates. */ +static duptable_t * +create_duptable (void) +{ + return xtrycalloc (DUPTABLE_SIZE, sizeof (duptable_t)); +} + +static void +destroy_duptable (duptable_t *table) +{ + int idx; + duptable_t t, t2; + + if (table) + { + for (idx=0; idx < DUPTABLE_SIZE; idx++) + for (t = table[idx]; t; t = t2) + { + t2 = t->next; + xfree (t); + } + xfree (table); + } +} + +/* Insert the 20 byte fingerprint FPR into TABLE. Sets EXITS to true + if the fingerprint already exists in the table. */ +static gpg_error_t +insert_duptable (duptable_t *table, unsigned char *fpr, int *exists) +{ + size_t idx; + duptable_t t; + + *exists = 0; + idx = fpr[0]; +#if DUPTABLE_BITS > 16 || DUPTABLE_BITS < 8 +#error cannot handle a table larger than 16 bits or smaller than 8 bits +#elif DUPTABLE_BITS > 8 + idx <<= (DUPTABLE_BITS - 8); + idx |= (fpr[1] & ~(~0 << 4)); +#endif + + for (t = table[idx]; t; t = t->next) + if (!memcmp (t->fpr, fpr+1, 19)) + break; + if (t) + { + *exists = 1; + return 0; + } + /* Insert that fingerprint. */ + t = xtrymalloc (sizeof *t); + if (!t) + return gpg_error_from_syserror (); + memcpy (t->fpr, fpr+1, 19); + t->next = table[idx]; + table[idx] = t; + return 0; +} + + + + +/* Export all certificates or just those given in NAMES. If STREAM is + not NULL the output is send to this extended stream. */ +void +gpgsm_export (ctrl_t ctrl, strlist_t names, FILE *fp, estream_t stream) +{ + KEYDB_HANDLE hd = NULL; + KEYDB_SEARCH_DESC *desc = NULL; + int ndesc; + Base64Context b64writer = NULL; + ksba_writer_t writer; + strlist_t sl; + ksba_cert_t cert = NULL; + int rc=0; + int count = 0; + int i; + duptable_t *dtable; + + + dtable = create_duptable (); + if (!dtable) + { + log_error ("creating duplicates table failed: %s\n", strerror (errno)); + goto leave; + } + + hd = keydb_new (0); + if (!hd) + { + log_error ("keydb_new failed\n"); + goto leave; + } + + if (!names) + ndesc = 1; + else + { + for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) + ; + } + + desc = xtrycalloc (ndesc, sizeof *desc); + if (!ndesc) + { + log_error ("allocating memory for export failed: %s\n", + gpg_strerror (out_of_core ())); + goto leave; + } + + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_FIRST; + else + { + for (ndesc=0, sl=names; sl; sl = sl->next) + { + rc = keydb_classify_name (sl->d, desc+ndesc); + if (rc) + { + log_error ("key `%s' not found: %s\n", + sl->d, gpg_strerror (rc)); + rc = 0; + } + else + ndesc++; + } + } + + /* If all specifications are done by fingerprint or keygrip, we + switch to ephemeral mode so that _all_ currently available and + matching certificates are exported. */ + if (names && ndesc) + { + for (i=0; (i < ndesc + && (desc[i].mode == KEYDB_SEARCH_MODE_FPR + || desc[i].mode == KEYDB_SEARCH_MODE_FPR20 + || desc[i].mode == KEYDB_SEARCH_MODE_FPR16 + || desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++) + ; + if (i == ndesc) + keydb_set_ephemeral (hd, 1); + } + + while (!(rc = keydb_search (hd, desc, ndesc))) + { + unsigned char fpr[20]; + int exists; + + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_NEXT; + + rc = keydb_get_cert (hd, &cert); + if (rc) + { + log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + gpgsm_get_fingerprint (cert, 0, fpr, NULL); + rc = insert_duptable (dtable, fpr, &exists); + if (rc) + { + log_error ("inserting into duplicates table failed: %s\n", + gpg_strerror (rc)); + goto leave; + } + + if (!exists && count && !ctrl->create_pem) + { + log_info ("exporting more than one certificate " + "is not possible in binary mode\n"); + log_info ("ignoring other certificates\n"); + break; + } + + if (!exists) + { + const unsigned char *image; + size_t imagelen; + + image = ksba_cert_get_image (cert, &imagelen); + if (!image) + { + log_error ("ksba_cert_get_image failed\n"); + goto leave; + } + + + if (ctrl->create_pem) + { + if (count) + { + if (stream) + es_putc ('\n', stream); + else + putc ('\n', fp); + } + print_short_info (cert, fp, stream); + if (stream) + es_putc ('\n', stream); + else + putc ('\n', fp); + } + count++; + + if (!b64writer) + { + ctrl->pem_name = "CERTIFICATE"; + rc = gpgsm_create_writer (&b64writer, ctrl, fp, stream, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + } + + rc = ksba_writer_write (writer, image, imagelen); + if (rc) + { + log_error ("write error: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (ctrl->create_pem) + { + /* We want one certificate per PEM block */ + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + gpgsm_destroy_writer (b64writer); + b64writer = NULL; + } + } + + ksba_cert_release (cert); + cert = NULL; + } + if (rc && rc != -1) + log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); + else if (b64writer) + { + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + } + + leave: + gpgsm_destroy_writer (b64writer); + ksba_cert_release (cert); + xfree (desc); + keydb_release (hd); + destroy_duptable (dtable); +} + + +/* Export a certificates and its private key. */ +void +gpgsm_p12_export (ctrl_t ctrl, const char *name, FILE *fp) +{ + KEYDB_HANDLE hd; + KEYDB_SEARCH_DESC *desc = NULL; + Base64Context b64writer = NULL; + ksba_writer_t writer; + ksba_cert_t cert = NULL; + int rc=0; + const unsigned char *image; + size_t imagelen; + char *keygrip = NULL; + char *prompt; + char buffer[1024]; + int nread; + FILE *datafp = NULL; + + + hd = keydb_new (0); + if (!hd) + { + log_error ("keydb_new failed\n"); + goto leave; + } + + desc = xtrycalloc (1, sizeof *desc); + if (!desc) + { + log_error ("allocating memory for export failed: %s\n", + gpg_strerror (out_of_core ())); + goto leave; + } + + rc = keydb_classify_name (name, desc); + if (rc) + { + log_error ("key `%s' not found: %s\n", + name, gpg_strerror (rc)); + goto leave; + } + + /* Lookup the certificate and make sure that it is unique. */ + rc = keydb_search (hd, desc, 1); + if (!rc) + { + rc = keydb_get_cert (hd, &cert); + if (rc) + { + log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + next_ambiguous: + rc = keydb_search (hd, desc, 1); + if (!rc) + { + ksba_cert_t cert2 = NULL; + + if (!keydb_get_cert (hd, &cert2)) + { + if (gpgsm_certs_identical_p (cert, cert2)) + { + ksba_cert_release (cert2); + goto next_ambiguous; + } + ksba_cert_release (cert2); + } + rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + } + else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) + rc = 0; + if (rc) + { + log_error ("key `%s' not found: %s\n", + name, gpg_strerror (rc)); + goto leave; + } + } + + keygrip = gpgsm_get_keygrip_hexstring (cert); + if (!keygrip || gpgsm_agent_havekey (ctrl, keygrip)) + { + /* Note, that the !keygrip case indicates a bad certificate. */ + rc = gpg_error (GPG_ERR_NO_SECKEY); + log_error ("can't export key `%s': %s\n", name, gpg_strerror (rc)); + goto leave; + } + + image = ksba_cert_get_image (cert, &imagelen); + if (!image) + { + log_error ("ksba_cert_get_image failed\n"); + goto leave; + } + + if (ctrl->create_pem) + { + print_short_info (cert, fp, NULL); + putc ('\n', fp); + } + + if (opt.p12_charset && ctrl->create_pem) + { + fprintf (fp, "The passphrase is %s encoded.\n\n", + opt.p12_charset); + } + + ctrl->pem_name = "PKCS12"; + rc = gpgsm_create_writer (&b64writer, ctrl, fp, NULL, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + + + prompt = gpgsm_format_keydesc (cert); + rc = export_p12 (ctrl, image, imagelen, prompt, keygrip, &datafp); + xfree (prompt); + if (rc) + goto leave; + rewind (datafp); + while ( (nread = fread (buffer, 1, sizeof buffer, datafp)) > 0 ) + if ((rc = ksba_writer_write (writer, buffer, nread))) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (ferror (datafp)) + { + rc = gpg_error_from_errno (rc); + log_error ("error reading temporary file: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (ctrl->create_pem) + { + /* We want one certificate per PEM block */ + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + gpgsm_destroy_writer (b64writer); + b64writer = NULL; + } + + ksba_cert_release (cert); + cert = NULL; + + leave: + if (datafp) + fclose (datafp); + gpgsm_destroy_writer (b64writer); + ksba_cert_release (cert); + xfree (desc); + keydb_release (hd); +} + + +/* Call either es_putc or the plain putc. */ +static void +do_putc (int value, FILE *fp, estream_t stream) +{ + if (stream) + es_putc (value, stream); + else + putc (value, fp); +} + +/* Call either es_fputs or the plain fputs. */ +static void +do_fputs (const char *string, FILE *fp, estream_t stream) +{ + if (stream) + es_fputs (string, stream); + else + fputs (string, fp); +} + + +/* Print some info about the certifciate CERT to FP or STREAM */ +static void +print_short_info (ksba_cert_t cert, FILE *fp, estream_t stream) +{ + char *p; + ksba_sexp_t sexp; + int idx; + + for (idx=0; (p = ksba_cert_get_issuer (cert, idx)); idx++) + { + do_fputs ((!idx + ? "Issuer ...: " + : "\n aka ...: "), fp, stream); + if (stream) + gpgsm_es_print_name (stream, p); + else + gpgsm_print_name (fp, p); + xfree (p); + } + do_putc ('\n', fp, stream); + + do_fputs ("Serial ...: ", fp, stream); + sexp = ksba_cert_get_serial (cert); + if (sexp) + { + int len; + const unsigned char *s = sexp; + + if (*s == '(') + { + s++; + for (len=0; *s && *s != ':' && digitp (s); s++) + len = len*10 + atoi_1 (s); + if (*s == ':') + { + if (stream) + es_write_hexstring (stream, s+1, len, 0, NULL); + else + print_hexstring (fp, s+1, len, 0); + } + } + xfree (sexp); + } + do_putc ('\n', fp, stream); + + for (idx=0; (p = ksba_cert_get_subject (cert, idx)); idx++) + { + do_fputs ((!idx + ? "Subject ..: " + : "\n aka ..: "), fp, stream); + if (stream) + gpgsm_es_print_name (stream, p); + else + gpgsm_print_name (fp, p); + xfree (p); + } + do_putc ('\n', fp, stream); +} + + +static gpg_error_t +popen_protect_tool (ctrl_t ctrl, const char *pgmname, + FILE *infile, FILE *outfile, FILE **statusfile, + const char *prompt, const char *keygrip, + pid_t *pid) +{ + const char *argv[22]; + int i=0; + + /* Make sure that the agent is running so that the protect tool is + able to ask for a passphrase. This has only an effect under W32 + where the agent is started on demand; sending a NOP does not harm + on other platforms. This is not really necessary anymore because + the protect tool does this now by itself; it does not harm either.*/ + gpgsm_agent_send_nop (ctrl); + + argv[i++] = "--homedir"; + argv[i++] = opt.homedir; + argv[i++] = "--p12-export"; + argv[i++] = "--have-cert"; + argv[i++] = "--prompt"; + argv[i++] = prompt?prompt:""; + argv[i++] = "--enable-status-msg"; + if (opt.p12_charset) + { + argv[i++] = "--p12-charset"; + argv[i++] = opt.p12_charset; + } + if (opt.agent_program) + { + argv[i++] = "--agent-program"; + argv[i++] = opt.agent_program; + } + argv[i++] = "--", + argv[i++] = keygrip, + argv[i] = NULL; + assert (i < sizeof argv); + + return gnupg_spawn_process (pgmname, argv, infile, outfile, + setup_pinentry_env, (128|64), + statusfile, pid); +} + + +static gpg_error_t +export_p12 (ctrl_t ctrl, const unsigned char *certimg, size_t certimglen, + const char *prompt, const char *keygrip, + FILE **retfp) +{ + const char *pgmname; + gpg_error_t err = 0, child_err = 0; + int c, cont_line; + unsigned int pos; + FILE *infp = NULL, *outfp = NULL, *fp = NULL; + char buffer[1024]; + pid_t pid = -1; + int bad_pass = 0; + + if (!opt.protect_tool_program || !*opt.protect_tool_program) + pgmname = gnupg_module_name (GNUPG_MODULE_NAME_PROTECT_TOOL); + else + pgmname = opt.protect_tool_program; + + infp = gnupg_tmpfile (); + if (!infp) + { + err = gpg_error_from_syserror (); + log_error (_("error creating temporary file: %s\n"), strerror (errno)); + goto cleanup; + } + + if (fwrite (certimg, certimglen, 1, infp) != 1) + { + err = gpg_error_from_syserror (); + log_error (_("error writing to temporary file: %s\n"), + strerror (errno)); + goto cleanup; + } + + outfp = gnupg_tmpfile (); + if (!outfp) + { + err = gpg_error_from_syserror (); + log_error (_("error creating temporary file: %s\n"), strerror (errno)); + goto cleanup; + } + + err = popen_protect_tool (ctrl, + pgmname, infp, outfp, &fp, prompt, keygrip, &pid); + if (err) + { + pid = -1; + goto cleanup; + } + fclose (infp); + infp = NULL; + + /* Read stderr of the protect tool. */ + pos = 0; + cont_line = 0; + while ((c=getc (fp)) != EOF) + { + /* fixme: We could here grep for status information of the + protect tool to figure out better error codes for + CHILD_ERR. */ + buffer[pos++] = c; + if (pos >= sizeof buffer - 5 || c == '\n') + { + buffer[pos - (c == '\n')] = 0; + if (cont_line) + log_printf ("%s", buffer); + else + { + if (!strncmp (buffer, "gpg-protect-tool: [PROTECT-TOOL:] ",34)) + { + char *p, *pend; + + p = buffer + 34; + pend = strchr (p, ' '); + if (pend) + *pend = 0; + if ( !strcmp (p, "bad-passphrase")) + bad_pass++; + } + else + log_info ("%s", buffer); + } + pos = 0; + cont_line = (c != '\n'); + } + } + + if (pos) + { + buffer[pos] = 0; + if (cont_line) + log_printf ("%s\n", buffer); + else + log_info ("%s\n", buffer); + } + else if (cont_line) + log_printf ("\n"); + + /* If we found no error in the output of the child, setup a suitable + error code, which will later be reset if the exit status of the + child is 0. */ + if (!child_err) + child_err = gpg_error (GPG_ERR_DECRYPT_FAILED); + + cleanup: + if (infp) + fclose (infp); + if (fp) + fclose (fp); + if (pid != -1) + { + if (!gnupg_wait_process (pgmname, pid, NULL)) + child_err = 0; + } + if (!err) + err = child_err; + if (err) + { + if (outfp) + fclose (outfp); + } + else + *retfp = outfp; + if (bad_pass) + { + /* During export this is the passphrase used to unprotect the + key and not the pkcs#12 thing as in export. Therefore we can + issue the regular passphrase status. FIXME: replace the all + zero keyid by a regular one. */ + gpgsm_status (ctrl, STATUS_BAD_PASSPHRASE, "0000000000000000"); + } + return err; +} + diff --git a/sm/fingerprint.c b/sm/fingerprint.c new file mode 100644 index 0000000..4704f59 --- /dev/null +++ b/sm/fingerprint.c @@ -0,0 +1,347 @@ +/* fingerprint.c - Get the fingerprint + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +/* Return the fingerprint of the certificate (we can't put this into + libksba because we need libgcrypt support). The caller must + provide an array of sufficient length or NULL so that the function + allocates the array. If r_len is not NULL, the length of the + digest is returned; well, this can also be done by using + gcry_md_get_algo_dlen(). If algo is 0, a SHA-1 will be used. + + If there is a problem , the function does never return NULL but a + digest of all 0xff. + */ +unsigned char * +gpgsm_get_fingerprint (ksba_cert_t cert, int algo, + unsigned char *array, int *r_len) +{ + gcry_md_hd_t md; + int rc, len; + + if (!algo) + algo = GCRY_MD_SHA1; + + len = gcry_md_get_algo_dlen (algo); + assert (len); + if (!array) + array = xmalloc (len); + + if (r_len) + *r_len = len; + + /* Fist check whether we have cached the fingerprint. */ + if (algo == GCRY_MD_SHA1) + { + size_t buflen; + + assert (len >= 20); + if (!ksba_cert_get_user_data (cert, "sha1-fingerprint", + array, len, &buflen) + && buflen == 20) + return array; + } + + /* No, need to compute it. */ + rc = gcry_md_open (&md, algo, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + memset (array, 0xff, len); /* better return an invalid fpr than NULL */ + return array; + } + + rc = ksba_cert_hash (cert, 0, HASH_FNC, md); + if (rc) + { + log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (rc)); + gcry_md_close (md); + memset (array, 0xff, len); /* better return an invalid fpr than NULL */ + return array; + } + gcry_md_final (md); + memcpy (array, gcry_md_read(md, algo), len ); + gcry_md_close (md); + + /* Cache an SHA-1 fingerprint. */ + if ( algo == GCRY_MD_SHA1 ) + ksba_cert_set_user_data (cert, "sha1-fingerprint", array, 20); + + return array; +} + + +/* Return an allocated buffer with the formatted fingerprint */ +char * +gpgsm_get_fingerprint_string (ksba_cert_t cert, int algo) +{ + unsigned char digest[MAX_DIGEST_LEN]; + char *buf; + int len; + + if (!algo) + algo = GCRY_MD_SHA1; + + len = gcry_md_get_algo_dlen (algo); + assert (len <= MAX_DIGEST_LEN ); + gpgsm_get_fingerprint (cert, algo, digest, NULL); + buf = xmalloc (len*3+1); + bin2hexcolon (digest, len, buf); + return buf; +} + +/* Return an allocated buffer with the formatted fingerprint as one + large hexnumber */ +char * +gpgsm_get_fingerprint_hexstring (ksba_cert_t cert, int algo) +{ + unsigned char digest[MAX_DIGEST_LEN]; + char *buf; + int len; + + if (!algo) + algo = GCRY_MD_SHA1; + + len = gcry_md_get_algo_dlen (algo); + assert (len <= MAX_DIGEST_LEN ); + gpgsm_get_fingerprint (cert, algo, digest, NULL); + buf = xmalloc (len*2+1); + bin2hex (digest, len, buf); + return buf; +} + +/* Return a certificate ID. These are the last 4 bytes of the SHA-1 + fingerprint. If R_HIGH is not NULL the next 4 bytes are stored + there. */ +unsigned long +gpgsm_get_short_fingerprint (ksba_cert_t cert, unsigned long *r_high) +{ + unsigned char digest[20]; + + gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); + if (r_high) + *r_high = ((digest[12]<<24)|(digest[13]<<16)|(digest[14]<< 8)|digest[15]); + return ((digest[16]<<24)|(digest[17]<<16)|(digest[18]<< 8)|digest[19]); +} + + +/* Return the so called KEYGRIP which is the SHA-1 hash of the public + key parameters expressed as an canoncial encoded S-Exp. ARRAY must + be 20 bytes long. Returns ARRAY or a newly allocated buffer if ARRAY was + given as NULL. May return NULL on error. */ +unsigned char * +gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array) +{ + gcry_sexp_t s_pkey; + int rc; + ksba_sexp_t p; + size_t n; + + p = ksba_cert_get_public_key (cert); + if (!p) + return NULL; /* oops */ + + if (DBG_X509) + log_debug ("get_keygrip for public key\n"); + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + return NULL; + } + rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n); + xfree (p); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + return NULL; + } + array = gcry_pk_get_keygrip (s_pkey, array); + gcry_sexp_release (s_pkey); + if (!array) + { + rc = gpg_error (GPG_ERR_GENERAL); + log_error ("can't calculate keygrip\n"); + return NULL; + } + if (DBG_X509) + log_printhex ("keygrip=", array, 20); + + return array; +} + +/* Return an allocated buffer with the keygrip of CERT encoded as a + hexstring. NULL is returned in case of error. */ +char * +gpgsm_get_keygrip_hexstring (ksba_cert_t cert) +{ + unsigned char grip[20]; + char *buf; + + if (!gpgsm_get_keygrip (cert, grip)) + return NULL; + buf = xtrymalloc (20*2+1); + if (buf) + bin2hex (grip, 20, buf); + return buf; +} + + +/* Return the PK algorithm used by CERT as well as the length in bits + of the public key at NBITS. */ +int +gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits) +{ + gcry_sexp_t s_pkey; + int rc; + ksba_sexp_t p; + size_t n; + gcry_sexp_t l1, l2; + const char *name; + char namebuf[128]; + + if (nbits) + *nbits = 0; + + p = ksba_cert_get_public_key (cert); + if (!p) + return 0; + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + { + xfree (p); + return 0; + } + rc = gcry_sexp_sscan (&s_pkey, NULL, (char *)p, n); + xfree (p); + if (rc) + return 0; + + if (nbits) + *nbits = gcry_pk_get_nbits (s_pkey); + + /* Breaking the algorithm out of the S-exp is a bit of a challenge ... */ + l1 = gcry_sexp_find_token (s_pkey, "public-key", 0); + if (!l1) + { + gcry_sexp_release (s_pkey); + return 0; + } + l2 = gcry_sexp_cadr (l1); + gcry_sexp_release (l1); + l1 = l2; + name = gcry_sexp_nth_data (l1, 0, &n); + if (name) + { + if (n > sizeof namebuf -1) + n = sizeof namebuf -1; + memcpy (namebuf, name, n); + namebuf[n] = 0; + } + else + *namebuf = 0; + gcry_sexp_release (l1); + gcry_sexp_release (s_pkey); + return gcry_pk_map_name (namebuf); +} + + + + +/* For certain purposes we need a certificate id which has an upper + limit of the size. We use the hash of the issuer name and the + serial number for this. In most cases the serial number is not + that large and the resulting string can be passed on an assuan + command line. Everything is hexencoded with the serialnumber + delimited from the hash by a dot. + + The caller must free the string. +*/ +char * +gpgsm_get_certid (ksba_cert_t cert) +{ + ksba_sexp_t serial; + char *p; + char *endp; + unsigned char hash[20]; + unsigned long n; + char *certid; + int i; + + p = ksba_cert_get_issuer (cert, 0); + if (!p) + return NULL; /* Ooops: No issuer */ + gcry_md_hash_buffer (GCRY_MD_SHA1, hash, p, strlen (p)); + xfree (p); + + serial = ksba_cert_get_serial (cert); + if (!serial) + return NULL; /* oops: no serial number */ + p = (char *)serial; + if (*p != '(') + { + log_error ("Ooops: invalid serial number\n"); + xfree (serial); + return NULL; + } + p++; + n = strtoul (p, &endp, 10); + p = endp; + if (*p != ':') + { + log_error ("Ooops: invalid serial number (no colon)\n"); + xfree (serial); + return NULL; + } + p++; + + certid = xtrymalloc ( 40 + 1 + n*2 + 1); + if (!certid) + { + xfree (serial); + return NULL; /* out of core */ + } + + for (i=0, endp = certid; i < 20; i++, endp += 2 ) + sprintf (endp, "%02X", hash[i]); + *endp++ = '.'; + for (i=0; i < n; i++, endp += 2) + sprintf (endp, "%02X", ((unsigned char*)p)[i]); + *endp = 0; + + xfree (serial); + return certid; +} + + + + diff --git a/sm/gpgsm.c b/sm/gpgsm.c new file mode 100644 index 0000000..484ce9d --- /dev/null +++ b/sm/gpgsm.c @@ -0,0 +1,2181 @@ +/* gpgsm.c - GnuPG for S/MIME + * Copyright (C) 2001, 2002, 2003, 2004, 2005, + * 2006, 2007, 2008 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 <http://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 <fcntl.h> +/*#include <mcheck.h>*/ + +#include "gpgsm.h" +#include <gcrypt.h> +#include <assuan.h> /* malloc hooks */ + +#include "../kbx/keybox.h" /* malloc hooks */ +#include "i18n.h" +#include "keydb.h" +#include "sysutils.h" +#include "gc-opt-flags.h" + + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +enum cmd_and_opt_values { + aNull = 0, + oArmor = 'a', + aDetachedSign = 'b', + aSym = 'c', + aDecrypt = 'd', + aEncr = 'e', + aListKeys = 'k', + aListSecretKeys = 'K', + oDryRun = 'n', + oOutput = 'o', + oQuiet = 'q', + oRecipient = 'r', + aSign = 's', + oUser = 'u', + oVerbose = 'v', + oBatch = 500, + aClearsign, + aKeygen, + aSignEncr, + aDeleteKey, + aImport, + aVerify, + aListExternalKeys, + aListChain, + aSendKeys, + aRecvKeys, + aExport, + aExportSecretKeyP12, + aServer, + aLearnCard, + aCallDirmngr, + aCallProtectTool, + aPasswd, + aGPGConfList, + aGPGConfTest, + aDumpKeys, + aDumpChain, + aDumpSecretKeys, + aDumpExternalKeys, + aKeydbClearSomeCertFlags, + aFingerprint, + + oOptions, + oDebug, + oDebugLevel, + oDebugAll, + oDebugNone, + oDebugWait, + oDebugAllowCoreDump, + oDebugNoChainValidation, + oDebugIgnoreExpiration, + oFixedPassphrase, + oLogFile, + oNoLogFile, + oAuditLog, + oHtmlAuditLog, + + oEnableSpecialFilenames, + + oAgentProgram, + oDisplay, + oTTYname, + oTTYtype, + oLCctype, + oLCmessages, + oXauthority, + + oPreferSystemDirmngr, + oDirmngrProgram, + oDisableDirmngr, + oProtectToolProgram, + oFakedSystemTime, + + + oAssumeArmor, + oAssumeBase64, + oAssumeBinary, + + oBase64, + oNoArmor, + oP12Charset, + + oDisableCRLChecks, + oEnableCRLChecks, + oDisableTrustedCertCRLCheck, + oEnableTrustedCertCRLCheck, + oForceCRLRefresh, + + oDisableOCSP, + oEnableOCSP, + + oIncludeCerts, + oPolicyFile, + oDisablePolicyChecks, + oEnablePolicyChecks, + oAutoIssuerKeyRetrieve, + + oWithFingerprint, + oWithMD5Fingerprint, + oAnswerYes, + oAnswerNo, + oKeyring, + oDefaultKey, + oDefRecipient, + oDefRecipientSelf, + oNoDefRecipient, + oStatusFD, + oCipherAlgo, + oDigestAlgo, + oExtraDigestAlgo, + oNoVerbose, + oNoSecmemWarn, + oNoDefKeyring, + oNoGreeting, + oNoTTY, + oNoOptions, + oNoBatch, + oHomedir, + oWithColons, + oWithKeyData, + oWithValidation, + oWithEphemeralKeys, + oSkipVerify, + oValidationModel, + oKeyServer, + oEncryptTo, + oNoEncryptTo, + oLoggerFD, + oDisableCipherAlgo, + oDisablePubkeyAlgo, + oIgnoreTimeConflict, + oNoRandomSeedFile, + oNoCommonCertsImport, + oIgnoreCertExtension + }; + + +static ARGPARSE_OPTS opts[] = { + + ARGPARSE_group (300, N_("@Commands:\n ")), + + ARGPARSE_c (aSign, "sign", N_("make a signature")), + ARGPARSE_c (aClearsign, "clearsign", N_("make a clear text signature") ), + ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")), + ARGPARSE_c (aEncr, "encrypt", N_("encrypt data")), + ARGPARSE_c (aSym, "symmetric", N_("encryption only with symmetric cipher")), + ARGPARSE_c (aDecrypt, "decrypt", N_("decrypt data (default)")), + ARGPARSE_c (aVerify, "verify", N_("verify a signature")), + ARGPARSE_c (aListKeys, "list-keys", N_("list keys")), + ARGPARSE_c (aListExternalKeys, "list-external-keys", + N_("list external keys")), + ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")), + ARGPARSE_c (aListChain, "list-chain", N_("list certificate chain")), + ARGPARSE_c (aFingerprint, "fingerprint", N_("list keys and fingerprints")), + ARGPARSE_c (aKeygen, "gen-key", N_("generate a new key pair")), + ARGPARSE_c (aDeleteKey, "delete-keys", + N_("remove keys from the public keyring")), + ARGPARSE_c (aSendKeys, "send-keys", N_("export keys to a key server")), + ARGPARSE_c (aRecvKeys, "recv-keys", N_("import keys from a key server")), + ARGPARSE_c (aImport, "import", N_("import certificates")), + ARGPARSE_c (aExport, "export", N_("export certificates")), + ARGPARSE_c (aExportSecretKeyP12, "export-secret-key-p12", "@"), + ARGPARSE_c (aLearnCard, "learn-card", N_("register a smartcard")), + ARGPARSE_c (aServer, "server", N_("run in server mode")), + ARGPARSE_c (aCallDirmngr, "call-dirmngr", + N_("pass a command to the dirmngr")), + ARGPARSE_c (aCallProtectTool, "call-protect-tool", + N_("invoke gpg-protect-tool")), + ARGPARSE_c (aPasswd, "passwd", N_("change a passphrase")), + ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), + ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), + + ARGPARSE_c (aDumpKeys, "dump-cert", "@"), + ARGPARSE_c (aDumpKeys, "dump-keys", "@"), + ARGPARSE_c (aDumpChain, "dump-chain", "@"), + ARGPARSE_c (aDumpExternalKeys, "dump-external-keys", "@"), + ARGPARSE_c (aDumpSecretKeys, "dump-secret-keys", "@"), + ARGPARSE_c (aKeydbClearSomeCertFlags, "keydb-clear-some-cert-flags", "@"), + + ARGPARSE_group (301, N_("@\nOptions:\n ")), + + ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")), + ARGPARSE_s_n (oArmor, "armour", "@"), + ARGPARSE_s_n (oBase64, "base64", N_("create base-64 encoded output")), + + ARGPARSE_s_s (oP12Charset, "p12-charset", "@"), + + ARGPARSE_s_n (oAssumeArmor, "assume-armor", + N_("assume input is in PEM format")), + ARGPARSE_s_n (oAssumeBase64, "assume-base64", + N_("assume input is in base-64 format")), + ARGPARSE_s_n (oAssumeBinary, "assume-binary", + N_("assume input is in binary format")), + + ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), + + ARGPARSE_s_n (oPreferSystemDirmngr,"prefer-system-dirmngr", + N_("use system's dirmngr if available")), + + ARGPARSE_s_n (oDisableCRLChecks, "disable-crl-checks", + N_("never consult a CRL")), + ARGPARSE_s_n (oEnableCRLChecks, "enable-crl-checks", "@"), + ARGPARSE_s_n (oDisableTrustedCertCRLCheck, + "disable-trusted-cert-crl-check", "@"), + ARGPARSE_s_n (oEnableTrustedCertCRLCheck, + "enable-trusted-cert-crl-check", "@"), + + ARGPARSE_s_n (oForceCRLRefresh, "force-crl-refresh", "@"), + + ARGPARSE_s_n (oDisableOCSP, "disable-ocsp", "@"), + ARGPARSE_s_n (oEnableOCSP, "enable-ocsp", N_("check validity using OCSP")), + + ARGPARSE_s_s (oValidationModel, "validation-model", "@"), + + ARGPARSE_s_i (oIncludeCerts, "include-certs", + N_("|N|number of certificates to include") ), + + ARGPARSE_s_s (oPolicyFile, "policy-file", + N_("|FILE|take policy information from FILE")), + + ARGPARSE_s_n (oDisablePolicyChecks, "disable-policy-checks", + N_("do not check certificate policies")), + ARGPARSE_s_n (oEnablePolicyChecks, "enable-policy-checks", "@"), + + ARGPARSE_s_n (oAutoIssuerKeyRetrieve, "auto-issuer-key-retrieve", + N_("fetch missing issuer certificates")), + + ARGPARSE_s_s (oEncryptTo, "encrypt-to", "@"), + ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"), + + ARGPARSE_s_s (oUser, "local-user", + N_("|USER-ID|use USER-ID to sign or decrypt")), + + ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), + ARGPARSE_s_n (oNoTTY, "no-tty", N_("don't use the terminal at all")), + ARGPARSE_s_s (oLogFile, "log-file", + N_("|FILE|write a server mode log to FILE")), + ARGPARSE_s_n (oNoLogFile, "no-log-file", "@"), + ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), + + ARGPARSE_s_s (oAuditLog, "audit-log", + N_("|FILE|write an audit log to FILE")), + ARGPARSE_s_s (oHtmlAuditLog, "html-audit-log", "@"), + ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), + ARGPARSE_s_n (oBatch, "batch", N_("batch mode: never ask")), + ARGPARSE_s_n (oAnswerYes, "yes", N_("assume yes on most questions")), + ARGPARSE_s_n (oAnswerNo, "no", N_("assume no on most questions")), + + ARGPARSE_s_s (oKeyring, "keyring", + N_("|FILE|add keyring to the list of keyrings")), + + ARGPARSE_s_s (oDefaultKey, "default-key", + N_("|USER-ID|use USER-ID as default secret key")), + + /* Not yet used: */ + /* ARGPARSE_s_s (oDefRecipient, "default-recipient", */ + /* N_("|NAME|use NAME as default recipient")), */ + /* ARGPARSE_s_n (oDefRecipientSelf, "default-recipient-self", */ + /* N_("use the default key as default recipient")), */ + /* ARGPARSE_s_n (oNoDefRecipient, "no-default-recipient", "@"), */ + + ARGPARSE_s_s (oKeyServer, "keyserver", + N_("|SPEC|use this keyserver to lookup keys")), + ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")), + + ARGPARSE_p_u (oDebug, "debug", "@"), + ARGPARSE_s_s (oDebugLevel, "debug-level", + N_("|LEVEL|set the debugging level to LEVEL")), + ARGPARSE_s_n (oDebugAll, "debug-all", "@"), + ARGPARSE_s_n (oDebugNone, "debug-none", "@"), + ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), + ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"), + ARGPARSE_s_n (oDebugNoChainValidation, "debug-no-chain-validation", "@"), + ARGPARSE_s_n (oDebugIgnoreExpiration, "debug-ignore-expiration", "@"), + ARGPARSE_s_s (oFixedPassphrase, "fixed-passphrase", "@"), + + ARGPARSE_s_i (oStatusFD, "status-fd", + N_("|FD|write status info to this FD")), + + ARGPARSE_s_s (oCipherAlgo, "cipher-algo", + N_("|NAME|use cipher algorithm NAME")), + ARGPARSE_s_s (oDigestAlgo, "digest-algo", + N_("|NAME|use message digest algorithm NAME")), + ARGPARSE_s_s (oExtraDigestAlgo, "extra-digest-algo", "@"), + + + ARGPARSE_group (302, N_( + "@\n(See the man page for a complete listing of all commands and options)\n" + )), + + ARGPARSE_group (303, N_("@\nExamples:\n\n" + " -se -r Bob [file] sign and encrypt for user Bob\n" + " --clearsign [file] make a clear text signature\n" + " --detach-sign [file] make a detached signature\n" + " --list-keys [names] show keys\n" + " --fingerprint [names] show fingerprints\n" )), + + /* Hidden options. */ + ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"), + ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"), + ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"), + ARGPARSE_s_n (oNoArmor, "no-armor", "@"), + ARGPARSE_s_n (oNoArmor, "no-armour", "@"), + ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"), + ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"), + ARGPARSE_s_n (oNoOptions, "no-options", "@"), + ARGPARSE_s_s (oHomedir, "homedir", "@"), + ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), + 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 (oDirmngrProgram, "dirmngr-program", "@"), + ARGPARSE_s_n (oDisableDirmngr, "disable-dirmngr", "@"), + ARGPARSE_s_s (oProtectToolProgram, "protect-tool-program", "@"), + ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), + ARGPARSE_s_n (oNoBatch, "no-batch", "@"), + ARGPARSE_s_n (oWithColons, "with-colons", "@"), + ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"), + ARGPARSE_s_n (oWithValidation, "with-validation", "@"), + ARGPARSE_s_n (oWithMD5Fingerprint, "with-md5-fingerprint", "@"), + ARGPARSE_s_n (oWithEphemeralKeys, "with-ephemeral-keys", "@"), + ARGPARSE_s_n (oSkipVerify, "skip-verify", "@"), + ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"), + ARGPARSE_s_s (oDisableCipherAlgo, "disable-cipher-algo", "@"), + ARGPARSE_s_s (oDisablePubkeyAlgo, "disable-pubkey-algo", "@"), + ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"), + ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"), + ARGPARSE_s_n (oNoCommonCertsImport, "no-common-certs-import", "@"), + ARGPARSE_s_s (oIgnoreCertExtension, "ignore-cert-extension", "@"), + + /* Command aliases. */ + ARGPARSE_c (aListKeys, "list-key", "@"), + ARGPARSE_c (aListChain, "list-sig", "@"), + ARGPARSE_c (aListChain, "list-sigs", "@"), + ARGPARSE_c (aListChain, "check-sig", "@"), + ARGPARSE_c (aListChain, "check-sigs", "@"), + ARGPARSE_c (aDeleteKey, "delete-key", "@"), + + ARGPARSE_end () +}; + + + + +/* Global variable to keep an error count. */ +int gpgsm_errors_seen = 0; + +/* It is possible that we are currentlu running under setuid permissions */ +static int maybe_setuid = 1; + +/* Helper to implement --debug-level and --debug*/ +static const char *debug_level; +static unsigned int debug_value; + +/* Option --enable-special-filenames */ +static int allow_special_filenames; + +/* Default value for include-certs. We need an extra macro for + gpgconf-list because the variable will be changed by the command + line option. */ +#define DEFAULT_INCLUDE_CERTS -2 /* Include all certs but root. */ +static int default_include_certs = DEFAULT_INCLUDE_CERTS; + +/* Whether the chain mode shall be used for validation. */ +static int default_validation_model; + + +static char *build_list (const char *text, + const char *(*mapf)(int), int (*chkf)(int)); +static void set_cmd (enum cmd_and_opt_values *ret_cmd, + enum cmd_and_opt_values new_cmd ); + +static void emergency_cleanup (void); +static int check_special_filename (const char *fname, int for_write); +static int open_read (const char *filename); +static estream_t open_es_fread (const char *filename); +static FILE *open_fwrite (const char *filename); +static estream_t open_es_fwrite (const char *filename); +static void run_protect_tool (int argc, char **argv); + +static int +our_pk_test_algo (int algo) +{ + switch (algo) + { + case GCRY_PK_RSA: + case GCRY_PK_ECDSA: + return gcry_pk_test_algo (algo); + default: + return 1; + } +} + +static int +our_cipher_test_algo (int algo) +{ + switch (algo) + { + case GCRY_CIPHER_3DES: + case GCRY_CIPHER_AES128: + case GCRY_CIPHER_AES192: + case GCRY_CIPHER_AES256: + case GCRY_CIPHER_SERPENT128: + case GCRY_CIPHER_SERPENT192: + case GCRY_CIPHER_SERPENT256: + case GCRY_CIPHER_SEED: + case GCRY_CIPHER_CAMELLIA128: + case GCRY_CIPHER_CAMELLIA192: + case GCRY_CIPHER_CAMELLIA256: + return gcry_cipher_test_algo (algo); + default: + return 1; + } +} + + +static int +our_md_test_algo (int algo) +{ + switch (algo) + { + case GCRY_MD_MD5: + case GCRY_MD_SHA1: + case GCRY_MD_RMD160: + case GCRY_MD_SHA224: + case GCRY_MD_SHA256: + case GCRY_MD_SHA384: + case GCRY_MD_SHA512: + case GCRY_MD_WHIRLPOOL: + return gcry_md_test_algo (algo); + default: + return 1; + } +} + + +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; +} + + +static const char * +my_strusage( int level ) +{ + static char *digests, *pubkeys, *ciphers; + static char *ver_gcry, *ver_ksba; + const char *p; + + switch (level) + { + case 11: p = "gpgsm (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: p = _("Usage: gpgsm [options] [files] (-h for help)"); + break; + case 41: + p = _("Syntax: gpgsm [options] [files]\n" + "sign, check, encrypt or decrypt using the S/MIME protocol\n" + "default operation depends on the input data\n"); + break; + + case 20: + if (!ver_gcry) + ver_gcry = make_libversion ("libgcrypt", gcry_check_version); + p = ver_gcry; + break; + case 21: + if (!ver_ksba) + ver_ksba = make_libversion ("libksba", ksba_check_version); + p = ver_ksba; + break; + + case 31: p = "\nHome: "; break; + case 32: p = opt.homedir; break; + case 33: p = _("\nSupported algorithms:\n"); break; + case 34: + if (!ciphers) + ciphers = build_list ("Cipher: ", gcry_cipher_algo_name, + our_cipher_test_algo ); + p = ciphers; + break; + case 35: + if (!pubkeys) + pubkeys = build_list ("Pubkey: ", gcry_pk_algo_name, + our_pk_test_algo ); + p = pubkeys; + break; + case 36: + if (!digests) + digests = build_list("Hash: ", gcry_md_algo_name, our_md_test_algo ); + p = digests; + break; + + default: p = NULL; break; + } + return p; +} + + +static char * +build_list (const char *text, const char * (*mapf)(int), int (*chkf)(int)) +{ + int i; + size_t n=strlen(text)+2; + char *list, *p; + + if (maybe_setuid) { + gcry_control (GCRYCTL_DROP_PRIVS); /* drop setuid */ + } + + for (i=1; i < 400; i++ ) + if (!chkf(i)) + n += strlen(mapf(i)) + 2; + list = xmalloc (21 + n); + *list = 0; + for (p=NULL, i=1; i < 400; i++) + { + if (!chkf(i)) + { + if( !p ) + p = stpcpy (list, text ); + else + p = stpcpy (p, ", "); + p = stpcpy (p, mapf(i) ); + } + } + if (p) + p = stpcpy(p, "\n" ); + return list; +} + + +/* Set the file pointer into binary mode if required. */ +static void +set_binary (FILE *fp) +{ +#ifdef HAVE_DOSISH_SYSTEM + setmode (fileno (fp), O_BINARY); +#else + (void)fp; +#endif +} + + + +static void +wrong_args (const char *text) +{ + fputs (_("usage: gpgsm [options] "), stderr); + fputs (text, stderr); + putc ('\n', stderr); + gpgsm_exit (2); +} + + +static void +set_opt_session_env (const char *name, const char *value) +{ + gpg_error_t err; + + err = session_env_setenv (opt.session_env, name, value); + if (err) + log_fatal ("error setting session environment: %s\n", + gpg_strerror (err)); +} + + +/* Setup the debugging. With a DEBUG_LEVEL of NULL only the active + debug flags are propagated to the subsystems. With DEBUG_LEVEL + set, a specific set of debug flags is set; and individual debugging + flags will be added on top. */ +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_ASSUAN_VALUE; + else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) + opt.debug = DBG_ASSUAN_VALUE|DBG_X509_VALUE; + else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) + opt.debug = (DBG_ASSUAN_VALUE|DBG_X509_VALUE + |DBG_CACHE_VALUE|DBG_CRYPTO_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); + gpgsm_exit (2); + } + + opt.debug |= debug_value; + + if (opt.debug && !opt.verbose) + opt.verbose = 1; + if (opt.debug) + 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) + log_info ("enabled debug flags:%s%s%s%s%s%s%s%s\n", + (opt.debug & DBG_X509_VALUE )? " x509":"", + (opt.debug & DBG_MPI_VALUE )? " mpi":"", + (opt.debug & DBG_CRYPTO_VALUE )? " crypto":"", + (opt.debug & DBG_MEMORY_VALUE )? " memory":"", + (opt.debug & DBG_CACHE_VALUE )? " cache":"", + (opt.debug & DBG_MEMSTAT_VALUE)? " memstat":"", + (opt.debug & DBG_HASHING_VALUE)? " hashing":"", + (opt.debug & DBG_ASSUAN_VALUE )? " assuan":"" ); +} + + + +static void +set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd) +{ + enum cmd_and_opt_values cmd = *ret_cmd; + + if (!cmd || cmd == new_cmd) + cmd = new_cmd; + else if ( cmd == aSign && new_cmd == aEncr ) + cmd = aSignEncr; + else if ( cmd == aEncr && new_cmd == aSign ) + cmd = aSignEncr; + else if ( (cmd == aSign && new_cmd == aClearsign) + || (cmd == aClearsign && new_cmd == aSign) ) + cmd = aClearsign; + else + { + log_error(_("conflicting commands\n")); + gpgsm_exit(2); + } + + *ret_cmd = cmd; +} + + +/* Helper to add recipients to a list. */ +static void +do_add_recipient (ctrl_t ctrl, const char *name, + certlist_t *recplist, int is_encrypt_to, int recp_required) +{ + int rc = gpgsm_add_to_certlist (ctrl, name, 0, recplist, is_encrypt_to); + if (rc) + { + if (recp_required) + { + log_error ("can't encrypt to `%s': %s\n", name, gpg_strerror (rc)); + gpgsm_status2 (ctrl, STATUS_INV_RECP, + get_inv_recpsgnr_code (rc), name, NULL); + } + else + log_info (_("NOTE: won't be able to encrypt to `%s': %s\n"), + name, gpg_strerror (rc)); + } +} + + +static void +parse_validation_model (const char *model) +{ + int i = gpgsm_parse_validation_model (model); + if (i == -1) + log_error (_("unknown validation model `%s'\n"), model); + else + default_validation_model = i; +} + + +/* Release the list of SERVERS. As usual it is okay to call this + function with SERVERS passed as NULL. */ +void +keyserver_list_free (struct keyserver_spec *servers) +{ + while (servers) + { + struct keyserver_spec *tmp = servers->next; + xfree (servers->host); + xfree (servers->user); + if (servers->pass) + memset (servers->pass, 0, strlen (servers->pass)); + xfree (servers->pass); + xfree (servers->base); + xfree (servers); + servers = tmp; + } +} + +/* See also dirmngr ldapserver_parse_one(). */ +struct keyserver_spec * +parse_keyserver_line (char *line, + const char *filename, unsigned int lineno) +{ + char *p; + char *endp; + struct keyserver_spec *server; + int fieldno; + int fail = 0; + + /* Parse the colon separated fields. */ + server = xcalloc (1, sizeof *server); + for (fieldno = 1, p = line; p; p = endp, fieldno++ ) + { + endp = strchr (p, ':'); + if (endp) + *endp++ = '\0'; + trim_spaces (p); + switch (fieldno) + { + case 1: + if (*p) + server->host = xstrdup (p); + else + { + log_error (_("%s:%u: no hostname given\n"), + filename, lineno); + fail = 1; + } + break; + + case 2: + if (*p) + server->port = atoi (p); + break; + + case 3: + if (*p) + server->user = xstrdup (p); + break; + + case 4: + if (*p && !server->user) + { + log_error (_("%s:%u: password given without user\n"), + filename, lineno); + fail = 1; + } + else if (*p) + server->pass = xstrdup (p); + break; + + case 5: + if (*p) + server->base = xstrdup (p); + break; + + default: + /* (We silently ignore extra fields.) */ + break; + } + } + + if (fail) + { + log_info (_("%s:%u: skipping this line\n"), filename, lineno); + keyserver_list_free (server); + } + + return server; +} + + +int +main ( int argc, char **argv) +{ + ARGPARSE_ARGS pargs; + int orig_argc; + char **orig_argv; + /* char *username;*/ + int may_coredump; + strlist_t sl, remusr= NULL, locusr=NULL; + strlist_t nrings=NULL; + int detached_sig = 0; + FILE *configfp = NULL; + char *configname = NULL; + unsigned configlineno; + int parse_debug = 0; + int no_more_options = 0; + int default_config =1; + int default_keyring = 1; + char *logfile = NULL; + char *auditlog = NULL; + char *htmlauditlog = NULL; + int greeting = 0; + int nogreeting = 0; + int debug_wait = 0; + int use_random_seed = 1; + int no_common_certs_import = 0; + int with_fpr = 0; + const char *forced_digest_algo = NULL; + const char *extra_digest_algo = NULL; + enum cmd_and_opt_values cmd = 0; + struct server_control_s ctrl; + certlist_t recplist = NULL; + certlist_t signerlist = NULL; + int do_not_setup_keys = 0; + int recp_required = 0; + estream_t auditfp = NULL; + estream_t htmlauditfp = NULL; + struct assuan_malloc_hooks malloc_hooks; + + /*mtrace();*/ + + gnupg_reopen_std ("gpgsm"); + /* trap_unaligned ();*/ + gnupg_rl_initialize (); + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + /* We don't need any locking in libgcrypt unless we use any kind of + threading. */ + gcry_control (GCRYCTL_DISABLE_INTERNAL_LOCKING); + + /* Please note that we may running SUID(ROOT), so be very CAREFUL + when adding any stuff between here and the call to secmem_init() + somewhere after the option parsing */ + log_set_prefix ("gpgsm", 1); + + /* Make sure that our subsystems are ready. */ + i18n_init(); + init_common_subsystems (); + + /* Check that the libraries are suitable. Do it here because the + option parse may need services of the library */ + if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) + log_fatal (_("%s is too old (need %s, have %s)\n"), "libgcrypt", + NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) ); + if (!ksba_check_version (NEED_KSBA_VERSION) ) + log_fatal (_("%s is too old (need %s, have %s)\n"), "libksba", + NEED_KSBA_VERSION, ksba_check_version (NULL) ); + + + gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); + + may_coredump = disable_core_dumps (); + + gnupg_init_signals (0, emergency_cleanup); + + create_dotlock (NULL); /* register locking cleanup */ + + opt.session_env = session_env_new (); + if (!opt.session_env) + log_fatal ("error allocating session environment block: %s\n", + strerror (errno)); + + /* Note: If you change this default cipher algorithm , please + remember to update the Gpgconflist entry as well. */ + opt.def_cipher_algoid = "3DES"; /*des-EDE3-CBC*/ + + opt.homedir = default_homedir (); + + /* First check whether we have a config file on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ + while (arg_parse( &pargs, opts)) + { + if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) + parse_debug++; + else if (pargs.r_opt == oOptions) + { /* yes there is one, so we do not try the default one but + read the config file when it is encountered at the + commandline */ + default_config = 0; + } + else if (pargs.r_opt == oNoOptions) + default_config = 0; /* --no-options */ + else if (pargs.r_opt == oHomedir) + opt.homedir = pargs.r.ret_str; + else if (pargs.r_opt == aCallProtectTool) + break; /* This break makes sure that --version and --help are + passed to the protect-tool. */ + } + + + /* Initialize the secure memory. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + maybe_setuid = 0; + + /* + Now we are now working under our real uid + */ + + ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free ); + + malloc_hooks.malloc = gcry_malloc; + malloc_hooks.realloc = gcry_realloc; + malloc_hooks.free = gcry_free; + assuan_set_malloc_hooks (&malloc_hooks); + assuan_set_assuan_log_prefix (log_get_prefix (NULL)); + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + + keybox_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); + + /* Setup a default control structure for command line mode */ + memset (&ctrl, 0, sizeof ctrl); + gpgsm_init_default_ctrl (&ctrl); + ctrl.no_server = 1; + ctrl.status_fd = -1; /* No status output. */ + ctrl.autodetect_encoding = 1; + + /* Set the default option file */ + if (default_config ) + configname = make_filename (opt.homedir, "gpgsm.conf", NULL); + /* Set the default policy file */ + opt.policy_file = make_filename (opt.homedir, "policies.txt", NULL); + + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags = 1; /* do not remove the args */ + + next_pass: + if (configname) { + configlineno = 0; + configfp = fopen (configname, "r"); + if (!configfp) + { + if (default_config) + { + if (parse_debug) + log_info (_("NOTE: no default option file `%s'\n"), configname); + } + else + { + log_error (_("option file `%s': %s\n"), configname, strerror(errno)); + gpgsm_exit(2); + } + xfree(configname); + configname = NULL; + } + if (parse_debug && configname) + log_info (_("reading options from `%s'\n"), configname); + default_config = 0; + } + + while (!no_more_options + && optfile_parse (configfp, configname, &configlineno, &pargs, opts)) + { + switch (pargs.r_opt) + { + case aGPGConfList: + case aGPGConfTest: + set_cmd (&cmd, pargs.r_opt); + do_not_setup_keys = 1; + nogreeting = 1; + break; + + case aServer: + opt.batch = 1; + set_cmd (&cmd, aServer); + break; + + case aCallDirmngr: + opt.batch = 1; + set_cmd (&cmd, aCallDirmngr); + do_not_setup_keys = 1; + break; + + case aCallProtectTool: + opt.batch = 1; + set_cmd (&cmd, aCallProtectTool); + no_more_options = 1; /* Stop parsing. */ + do_not_setup_keys = 1; + break; + + case aDeleteKey: + set_cmd (&cmd, aDeleteKey); + /*greeting=1;*/ + do_not_setup_keys = 1; + break; + + case aDetachedSign: + detached_sig = 1; + set_cmd (&cmd, aSign ); + break; + + case aKeygen: + set_cmd (&cmd, aKeygen); + greeting=1; + do_not_setup_keys = 1; + break; + + case aImport: + case aSendKeys: + case aRecvKeys: + case aExport: + case aExportSecretKeyP12: + case aDumpKeys: + case aDumpChain: + case aDumpExternalKeys: + case aDumpSecretKeys: + case aListKeys: + case aListExternalKeys: + case aListSecretKeys: + case aListChain: + case aLearnCard: + case aPasswd: + case aKeydbClearSomeCertFlags: + do_not_setup_keys = 1; + set_cmd (&cmd, pargs.r_opt); + break; + + case aEncr: + recp_required = 1; + set_cmd (&cmd, pargs.r_opt); + break; + + case aSym: + case aDecrypt: + case aSign: + case aClearsign: + case aVerify: + set_cmd (&cmd, pargs.r_opt); + break; + + /* Output encoding selection. */ + case oArmor: + ctrl.create_pem = 1; + break; + case oBase64: + ctrl.create_pem = 0; + ctrl.create_base64 = 1; + break; + case oNoArmor: + ctrl.create_pem = 0; + ctrl.create_base64 = 0; + break; + + case oP12Charset: + opt.p12_charset = pargs.r.ret_str; + break; + + /* Input encoding selection. */ + case oAssumeArmor: + ctrl.autodetect_encoding = 0; + ctrl.is_pem = 1; + ctrl.is_base64 = 0; + break; + case oAssumeBase64: + ctrl.autodetect_encoding = 0; + ctrl.is_pem = 0; + ctrl.is_base64 = 1; + break; + case oAssumeBinary: + ctrl.autodetect_encoding = 0; + ctrl.is_pem = 0; + ctrl.is_base64 = 0; + break; + + case oDisableCRLChecks: + opt.no_crl_check = 1; + break; + case oEnableCRLChecks: + opt.no_crl_check = 0; + break; + case oDisableTrustedCertCRLCheck: + opt.no_trusted_cert_crl_check = 1; + break; + case oEnableTrustedCertCRLCheck: + opt.no_trusted_cert_crl_check = 0; + break; + case oForceCRLRefresh: + opt.force_crl_refresh = 1; + break; + + case oDisableOCSP: + ctrl.use_ocsp = opt.enable_ocsp = 0; + break; + case oEnableOCSP: + ctrl.use_ocsp = opt.enable_ocsp = 1; + break; + + case oIncludeCerts: + ctrl.include_certs = default_include_certs = pargs.r.ret_int; + break; + + case oPolicyFile: + xfree (opt.policy_file); + if (*pargs.r.ret_str) + opt.policy_file = xstrdup (pargs.r.ret_str); + else + opt.policy_file = NULL; + break; + + case oDisablePolicyChecks: + opt.no_policy_check = 1; + break; + case oEnablePolicyChecks: + opt.no_policy_check = 0; + break; + + case oAutoIssuerKeyRetrieve: + opt.auto_issuer_key_retrieve = 1; + break; + + case oOutput: opt.outfile = pargs.r.ret_str; break; + + + case oQuiet: opt.quiet = 1; break; + case oNoTTY: /* fixme:tty_no_terminal(1);*/ break; + case oDryRun: opt.dry_run = 1; break; + + case oVerbose: + opt.verbose++; + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + break; + case oNoVerbose: + opt.verbose = 0; + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + break; + + case oLogFile: logfile = pargs.r.ret_str; break; + case oNoLogFile: logfile = NULL; break; + + case oAuditLog: auditlog = pargs.r.ret_str; break; + case oHtmlAuditLog: htmlauditlog = pargs.r.ret_str; break; + + case oBatch: + opt.batch = 1; + greeting = 0; + break; + case oNoBatch: opt.batch = 0; break; + + case oAnswerYes: opt.answer_yes = 1; break; + case oAnswerNo: opt.answer_no = 1; break; + + case oKeyring: append_to_strlist (&nrings, pargs.r.ret_str); break; + + case oDebug: debug_value |= pargs.r.ret_ulong; break; + case oDebugAll: debug_value = ~0; break; + case oDebugNone: debug_value = 0; break; + case oDebugLevel: debug_level = pargs.r.ret_str; break; + case oDebugWait: debug_wait = pargs.r.ret_int; break; + case oDebugAllowCoreDump: + may_coredump = enable_core_dumps (); + break; + case oDebugNoChainValidation: opt.no_chain_validation = 1; break; + case oDebugIgnoreExpiration: opt.ignore_expiration = 1; break; + case oFixedPassphrase: opt.fixed_passphrase = pargs.r.ret_str; break; + + case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break; + case oLoggerFD: log_set_fd (pargs.r.ret_int ); break; + case oWithMD5Fingerprint: + opt.with_md5_fingerprint=1; /*fall thru*/ + case oWithFingerprint: + with_fpr=1; /*fall thru*/ + case aFingerprint: + opt.fingerprint++; + break; + + case oOptions: + /* config files may not be nested (silently ignore them) */ + if (!configfp) + { + xfree(configname); + configname = xstrdup (pargs.r.ret_str); + goto next_pass; + } + break; + case oNoOptions: break; /* no-options */ + case oHomedir: opt.homedir = pargs.r.ret_str; break; + case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; + + case oDisplay: + set_opt_session_env ("DISPLAY", pargs.r.ret_str); + break; + case oTTYname: + set_opt_session_env ("GPG_TTY", pargs.r.ret_str); + break; + case oTTYtype: + set_opt_session_env ("TERM", pargs.r.ret_str); + break; + case oXauthority: + set_opt_session_env ("XAUTHORITY", pargs.r.ret_str); + break; + + case oLCctype: opt.lc_ctype = xstrdup (pargs.r.ret_str); break; + case oLCmessages: opt.lc_messages = xstrdup (pargs.r.ret_str); break; + + case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; + case oDisableDirmngr: opt.disable_dirmngr = 1; break; + case oPreferSystemDirmngr: opt.prefer_system_dirmngr = 1; break; + case oProtectToolProgram: + opt.protect_tool_program = pargs.r.ret_str; + 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 oNoDefKeyring: default_keyring = 0; break; + case oNoGreeting: nogreeting = 1; break; + + case oDefaultKey: + if (*pargs.r.ret_str) + { + xfree (opt.local_user); + opt.local_user = xstrdup (pargs.r.ret_str); + } + break; + case oDefRecipient: + if (*pargs.r.ret_str) + opt.def_recipient = xstrdup (pargs.r.ret_str); + break; + case oDefRecipientSelf: + xfree (opt.def_recipient); + opt.def_recipient = NULL; + opt.def_recipient_self = 1; + break; + case oNoDefRecipient: + xfree (opt.def_recipient); + opt.def_recipient = NULL; + opt.def_recipient_self = 0; + break; + + case oWithKeyData: opt.with_key_data=1; /* fall thru */ + case oWithColons: ctrl.with_colons = 1; break; + case oWithValidation: ctrl.with_validation=1; break; + case oWithEphemeralKeys: ctrl.with_ephemeral_keys=1; break; + + case oSkipVerify: opt.skip_verify=1; break; + + case oNoEncryptTo: opt.no_encrypt_to = 1; break; + case oEncryptTo: /* Store the recipient in the second list */ + sl = add_to_strlist (&remusr, pargs.r.ret_str); + sl->flags = 1; + break; + + case oRecipient: /* store the recipient */ + add_to_strlist ( &remusr, pargs.r.ret_str); + break; + + case oUser: /* Store the local users, the first one is the default */ + if (!opt.local_user) + opt.local_user = xstrdup (pargs.r.ret_str); + add_to_strlist (&locusr, pargs.r.ret_str); + break; + + case oNoSecmemWarn: + gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); + break; + + case oCipherAlgo: + opt.def_cipher_algoid = pargs.r.ret_str; + break; + + case oDisableCipherAlgo: + { + int algo = gcry_cipher_map_name (pargs.r.ret_str); + gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); + } + break; + case oDisablePubkeyAlgo: + { + int algo = gcry_pk_map_name (pargs.r.ret_str); + gcry_pk_ctl (GCRYCTL_DISABLE_ALGO,&algo, sizeof algo ); + } + break; + + case oDigestAlgo: + forced_digest_algo = pargs.r.ret_str; + break; + + case oExtraDigestAlgo: + extra_digest_algo = pargs.r.ret_str; + break; + + case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; + case oNoRandomSeedFile: use_random_seed = 0; break; + case oNoCommonCertsImport: no_common_certs_import = 1; break; + + case oEnableSpecialFilenames: allow_special_filenames =1; break; + + case oValidationModel: parse_validation_model (pargs.r.ret_str); break; + + case oKeyServer: + { + struct keyserver_spec *keyserver; + keyserver = parse_keyserver_line (pargs.r.ret_str, + configname, configlineno); + if (! keyserver) + log_error (_("could not parse keyserver\n")); + else + { + /* FIXME: Keep last next pointer. */ + struct keyserver_spec **next_p = &opt.keyserver; + while (*next_p) + next_p = &(*next_p)->next; + *next_p = keyserver; + } + } + break; + + case oIgnoreCertExtension: + add_to_strlist (&opt.ignored_cert_extensions, pargs.r.ret_str); + break; + + default: + pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR; + break; + } + } + + if (configfp) + { + fclose (configfp); + configfp = NULL; + /* Keep a copy of the config filename. */ + opt.config_filename = configname; + configname = NULL; + goto next_pass; + } + xfree (configname); + configname = NULL; + + if (!opt.config_filename) + opt.config_filename = make_filename (opt.homedir, "gpgsm.conf", NULL); + + if (log_get_errorcount(0)) + gpgsm_exit(2); + + /* Now that we have the options parsed we need to update the default + control structure. */ + gpgsm_init_default_ctrl (&ctrl); + + if (nogreeting) + greeting = 0; + + if (greeting) + { + fprintf(stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + fprintf(stderr, "%s\n", strusage(15) ); + } +# ifdef IS_DEVELOPMENT_VERSION + if (!opt.batch) + { + log_info ("NOTE: THIS IS A DEVELOPMENT VERSION!\n"); + log_info ("It is only intended for test purposes and should NOT be\n"); + log_info ("used in a production environment or with production keys!\n"); + } +# endif + + if (may_coredump && !opt.quiet) + log_info (_("WARNING: program may create a core file!\n")); + +/* if (opt.qualsig_approval && !opt.quiet) */ +/* log_info (_("This software has offically been approved to " */ +/* "create and verify\n" */ +/* "qualified signatures according to German law.\n")); */ + + if (logfile && cmd == aServer) + { + log_set_file (logfile); + log_set_prefix (NULL, 1|2|4); + } + + if (gnupg_faked_time_p ()) + { + gnupg_isotime_t tbuf; + + log_info (_("WARNING: running with faked system time: ")); + gnupg_get_isotime (tbuf); + dump_isotime (tbuf); + log_printf ("\n"); + } + +/*FIXME if (opt.batch) */ +/* tty_batchmode (1); */ + + gcry_control (GCRYCTL_RESUME_SECMEM_WARN); + + set_debug (); + + /* Although we alwasy use gpgsm_exit, we better install a regualr + exit handler so that at least the secure memory gets wiped + out. */ + if (atexit (emergency_cleanup)) + { + log_error ("atexit failed\n"); + gpgsm_exit (2); + } + + /* Must do this after dropping setuid, because the mapping functions + may try to load an module and we may have disabled an algorithm. + We remap the commonly used algorithms to the OIDs for + convenience. We need to work with the OIDs because they are used + to check whether the encryption mode is actually available. */ + if (!strcmp (opt.def_cipher_algoid, "3DES") ) + opt.def_cipher_algoid = "1.2.840.113549.3.7"; + else if (!strcmp (opt.def_cipher_algoid, "AES") + || !strcmp (opt.def_cipher_algoid, "AES128")) + opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.2"; + else if (!strcmp (opt.def_cipher_algoid, "AES256") ) + opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.42"; + else if (!strcmp (opt.def_cipher_algoid, "SERPENT") + || !strcmp (opt.def_cipher_algoid, "SERPENT128") ) + opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.2"; + else if (!strcmp (opt.def_cipher_algoid, "SERPENT192") ) + opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.22"; + else if (!strcmp (opt.def_cipher_algoid, "SERPENT192") ) + opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.42"; + else if (!strcmp (opt.def_cipher_algoid, "SEED") ) + opt.def_cipher_algoid = "1.2.410.200004.1.4"; + else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA") + || !strcmp (opt.def_cipher_algoid, "CAMELLIA128") ) + opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.2"; + else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA192") ) + opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.3"; + else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA256") ) + opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.4"; + + if (cmd != aGPGConfList) + { + if ( !gcry_cipher_map_name (opt.def_cipher_algoid) + || !gcry_cipher_mode_from_oid (opt.def_cipher_algoid)) + log_error (_("selected cipher algorithm is invalid\n")); + + if (forced_digest_algo) + { + opt.forced_digest_algo = gcry_md_map_name (forced_digest_algo); + if (our_md_test_algo(opt.forced_digest_algo) ) + log_error (_("selected digest algorithm is invalid\n")); + } + if (extra_digest_algo) + { + opt.extra_digest_algo = gcry_md_map_name (extra_digest_algo); + if (our_md_test_algo (opt.extra_digest_algo) ) + log_error (_("selected digest algorithm is invalid\n")); + } + } + + if (log_get_errorcount(0)) + gpgsm_exit(2); + + /* Set the random seed file. */ + if (use_random_seed) + { + char *p = make_filename (opt.homedir, "random_seed", NULL); + gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p); + xfree(p); + } + + if (!cmd && opt.fingerprint && !with_fpr) + set_cmd (&cmd, aListKeys); + + /* Add default keybox. */ + if (!nrings && default_keyring) + { + int created; + + keydb_add_resource ("pubring.kbx", 0, 0, &created); + if (created && !no_common_certs_import) + { + /* Import the standard certificates for a new default keybox. */ + char *filelist[2]; + + filelist[0] = make_filename (gnupg_datadir (),"com-certs.pem", NULL); + filelist[1] = NULL; + if (!access (filelist[0], F_OK)) + { + log_info (_("importing common certificates `%s'\n"), + filelist[0]); + gpgsm_import_files (&ctrl, 1, filelist, open_read); + } + xfree (filelist[0]); + } + } + for (sl = nrings; sl; sl = sl->next) + keydb_add_resource (sl->d, 0, 0, NULL); + FREE_STRLIST(nrings); + + + /* Prepare the audit log feature for certain commands. */ + if (auditlog || htmlauditlog) + { + switch (cmd) + { + case aEncr: + case aSign: + case aDecrypt: + case aVerify: + audit_release (ctrl.audit); + ctrl.audit = audit_new (); + if (auditlog) + auditfp = open_es_fwrite (auditlog); + if (htmlauditlog) + htmlauditfp = open_es_fwrite (htmlauditlog); + break; + default: + break; + } + } + + + if (!do_not_setup_keys) + { + for (sl = locusr; sl ; sl = sl->next) + { + int rc = gpgsm_add_to_certlist (&ctrl, sl->d, 1, &signerlist, 0); + if (rc) + { + log_error (_("can't sign using `%s': %s\n"), + sl->d, gpg_strerror (rc)); + gpgsm_status2 (&ctrl, STATUS_INV_SGNR, + get_inv_recpsgnr_code (rc), sl->d, NULL); + gpgsm_status2 (&ctrl, STATUS_INV_RECP, + get_inv_recpsgnr_code (rc), sl->d, NULL); + } + } + + /* Build the recipient list. We first add the regular ones and then + the encrypt-to ones because the underlying function will silently + ignore duplicates and we can't allow to keep a duplicate which is + flagged as encrypt-to as the actually encrypt function would then + complain about no (regular) recipients. */ + for (sl = remusr; sl; sl = sl->next) + if (!(sl->flags & 1)) + do_add_recipient (&ctrl, sl->d, &recplist, 0, recp_required); + if (!opt.no_encrypt_to) + { + for (sl = remusr; sl; sl = sl->next) + if ((sl->flags & 1)) + do_add_recipient (&ctrl, sl->d, &recplist, 1, recp_required); + } + } + + if (log_get_errorcount(0)) + gpgsm_exit(1); /* Must stop for invalid recipients. */ + + /* Dispatch command. */ + switch (cmd) + { + case aGPGConfList: + { /* List options and default values in the GPG Conf format. */ + char *config_filename_esc = percent_escape (opt.config_filename, NULL); + + printf ("gpgconf-gpgsm.conf:%lu:\"%s\n", + GC_OPT_FLAG_DEFAULT, config_filename_esc); + xfree (config_filename_esc); + + printf ("verbose:%lu:\n", GC_OPT_FLAG_NONE); + printf ("quiet:%lu:\n", GC_OPT_FLAG_NONE); + printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT); + printf ("log-file:%lu:\n", GC_OPT_FLAG_NONE); + printf ("disable-crl-checks:%lu:\n", GC_OPT_FLAG_NONE); + printf ("disable-trusted-cert-crl-check:%lu:\n", GC_OPT_FLAG_NONE); + printf ("enable-ocsp:%lu:\n", GC_OPT_FLAG_NONE); + printf ("include-certs:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, + DEFAULT_INCLUDE_CERTS); + printf ("disable-policy-checks:%lu:\n", GC_OPT_FLAG_NONE); + printf ("auto-issuer-key-retrieve:%lu:\n", GC_OPT_FLAG_NONE); + printf ("disable-dirmngr:%lu:\n", GC_OPT_FLAG_NONE); +#ifndef HAVE_W32_SYSTEM + printf ("prefer-system-dirmngr:%lu:\n", GC_OPT_FLAG_NONE); +#endif + printf ("cipher-algo:%lu:\"3DES:\n", GC_OPT_FLAG_DEFAULT); + printf ("p12-charset:%lu:\n", GC_OPT_FLAG_DEFAULT); + printf ("default-key:%lu:\n", GC_OPT_FLAG_DEFAULT); + printf ("encrypt-to:%lu:\n", GC_OPT_FLAG_DEFAULT); + printf ("keyserver:%lu:\n", GC_OPT_FLAG_NONE); + + /* The next one is an info only item and should match what + proc_parameters actually implements. */ + printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, + "RSA-2048"); + } + break; + case aGPGConfTest: + /* This is merely a dummy command to test whether the + configuration file is valid. */ + break; + + case aServer: + if (debug_wait) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + gnupg_sleep (debug_wait); + log_debug ("... okay\n"); + } + gpgsm_server (recplist); + break; + + case aCallDirmngr: + if (!argc) + wrong_args ("--call-dirmngr <command> {args}"); + else + if (gpgsm_dirmngr_run_command (&ctrl, *argv, argc-1, argv+1)) + gpgsm_exit (1); + break; + + case aCallProtectTool: + run_protect_tool (argc, argv); + break; + + case aEncr: /* Encrypt the given file. */ + { + FILE *fp = open_fwrite (opt.outfile?opt.outfile:"-"); + + set_binary (stdin); + + if (!argc) /* Source is stdin. */ + gpgsm_encrypt (&ctrl, recplist, 0, fp); + else if (argc == 1) /* Source is the given file. */ + gpgsm_encrypt (&ctrl, recplist, open_read (*argv), fp); + else + wrong_args ("--encrypt [datafile]"); + + if (fp != stdout) + fclose (fp); + } + break; + + case aSign: /* Sign the given file. */ + { + FILE *fp = open_fwrite (opt.outfile?opt.outfile:"-"); + + /* Fixme: We should also allow to concatenate multiple files for + signing because that is what gpg does.*/ + set_binary (stdin); + if (!argc) /* Create from stdin. */ + gpgsm_sign (&ctrl, signerlist, 0, detached_sig, fp); + else if (argc == 1) /* From file. */ + gpgsm_sign (&ctrl, signerlist, + open_read (*argv), detached_sig, fp); + else + wrong_args ("--sign [datafile]"); + + if (fp != stdout) + fclose (fp); + } + break; + + case aSignEncr: /* sign and encrypt the given file */ + log_error ("this command has not yet been implemented\n"); + break; + + case aClearsign: /* make a clearsig */ + log_error ("this command has not yet been implemented\n"); + break; + + case aVerify: + { + FILE *fp = NULL; + + set_binary (stdin); + if (argc == 2 && opt.outfile) + log_info ("option --output ignored for a detached signature\n"); + else if (opt.outfile) + fp = open_fwrite (opt.outfile); + + if (!argc) + gpgsm_verify (&ctrl, 0, -1, fp); /* normal signature from stdin */ + else if (argc == 1) + gpgsm_verify (&ctrl, open_read (*argv), -1, fp); /* std signature */ + else if (argc == 2) /* detached signature (sig, detached) */ + gpgsm_verify (&ctrl, open_read (*argv), open_read (argv[1]), NULL); + else + wrong_args ("--verify [signature [detached_data]]"); + + if (fp && fp != stdout) + fclose (fp); + } + break; + + case aDecrypt: + { + FILE *fp = open_fwrite (opt.outfile?opt.outfile:"-"); + + set_binary (stdin); + if (!argc) + gpgsm_decrypt (&ctrl, 0, fp); /* from stdin */ + else if (argc == 1) + gpgsm_decrypt (&ctrl, open_read (*argv), fp); /* from file */ + else + wrong_args ("--decrypt [filename]"); + if (fp != stdout) + fclose (fp); + } + break; + + case aDeleteKey: + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_delete (&ctrl, sl); + free_strlist(sl); + break; + + case aListChain: + case aDumpChain: + ctrl.with_chain = 1; + case aListKeys: + case aDumpKeys: + case aListExternalKeys: + case aDumpExternalKeys: + case aListSecretKeys: + case aDumpSecretKeys: + { + unsigned int mode; + estream_t fp; + + switch (cmd) + { + case aListChain: + case aListKeys: mode = (0 | 0 | (1<<6)); break; + case aDumpChain: + case aDumpKeys: mode = (256 | 0 | (1<<6)); break; + case aListExternalKeys: mode = (0 | 0 | (1<<7)); break; + case aDumpExternalKeys: mode = (256 | 0 | (1<<7)); break; + case aListSecretKeys: mode = (0 | 2 | (1<<6)); break; + case aDumpSecretKeys: mode = (256 | 2 | (1<<6)); break; + default: BUG(); + } + + fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_list_keys (&ctrl, sl, fp, mode); + free_strlist(sl); + es_fclose (fp); + } + break; + + + case aKeygen: /* Generate a key; well kind of. */ + { + estream_t fpin = NULL; + FILE *fpout; + + if (opt.batch) + { + if (!argc) /* Create from stdin. */ + fpin = open_es_fread ("-"); + else if (argc == 1) /* From file. */ + fpin = open_es_fread (*argv); + else + wrong_args ("--gen-key --batch [parmfile]"); + } + + fpout = open_fwrite (opt.outfile?opt.outfile:"-"); + + if (fpin) + gpgsm_genkey (&ctrl, fpin, fpout); + else + gpgsm_gencertreq_tty (&ctrl, fpout); + + if (fpout != stdout) + fclose (fpout); + } + break; + + + case aImport: + gpgsm_import_files (&ctrl, argc, argv, open_read); + break; + + case aExport: + { + FILE *fp = open_fwrite (opt.outfile?opt.outfile:"-"); + + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_export (&ctrl, sl, fp, NULL); + free_strlist(sl); + if (fp != stdout) + fclose (fp); + } + break; + + case aExportSecretKeyP12: + { + FILE *fp = open_fwrite (opt.outfile?opt.outfile:"-"); + + if (argc == 1) + gpgsm_p12_export (&ctrl, *argv, fp); + else + wrong_args ("--export-secret-key-p12 KEY-ID"); + if (fp != stdout) + fclose (fp); + } + break; + + case aSendKeys: + case aRecvKeys: + log_error ("this command has not yet been implemented\n"); + break; + + + case aLearnCard: + if (argc) + wrong_args ("--learn-card"); + else + { + int rc = gpgsm_agent_learn (&ctrl); + if (rc) + log_error ("error learning card: %s\n", gpg_strerror (rc)); + } + break; + + case aPasswd: + if (argc != 1) + wrong_args ("--passwd <key-Id>"); + else + { + int rc; + ksba_cert_t cert = NULL; + char *grip = NULL; + + rc = gpgsm_find_cert (*argv, NULL, &cert); + if (rc) + ; + else if (!(grip = gpgsm_get_keygrip_hexstring (cert))) + rc = gpg_error (GPG_ERR_BUG); + else + { + char *desc = gpgsm_format_keydesc (cert); + rc = gpgsm_agent_passwd (&ctrl, grip, desc); + xfree (desc); + } + if (rc) + log_error ("error changing passphrase: %s\n", gpg_strerror (rc)); + xfree (grip); + ksba_cert_release (cert); + } + break; + + case aKeydbClearSomeCertFlags: + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + keydb_clear_some_cert_flags (&ctrl, sl); + free_strlist(sl); + break; + + + default: + log_error (_("invalid command (there is no implicit command)\n")); + break; + } + + /* Print the audit result if needed. */ + if ((auditlog && auditfp) || (htmlauditlog && htmlauditfp)) + { + if (auditlog && auditfp) + audit_print_result (ctrl.audit, auditfp, 0); + if (htmlauditlog && htmlauditfp) + audit_print_result (ctrl.audit, htmlauditfp, 1); + audit_release (ctrl.audit); + ctrl.audit = NULL; + es_fclose (auditfp); + es_fclose (htmlauditfp); + } + + /* cleanup */ + keyserver_list_free (opt.keyserver); + opt.keyserver = NULL; + gpgsm_release_certlist (recplist); + gpgsm_release_certlist (signerlist); + FREE_STRLIST (remusr); + FREE_STRLIST (locusr); + gpgsm_exit(0); + return 8; /*NOTREACHED*/ +} + +/* Note: This function is used by signal handlers!. */ +static void +emergency_cleanup (void) +{ + gcry_control (GCRYCTL_TERM_SECMEM ); +} + + +void +gpgsm_exit (int rc) +{ + gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); + 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 ); + emergency_cleanup (); + rc = rc? rc : log_get_errorcount(0)? 2 : gpgsm_errors_seen? 1 : 0; + exit (rc); +} + + +void +gpgsm_init_default_ctrl (struct server_control_s *ctrl) +{ + ctrl->include_certs = default_include_certs; + ctrl->use_ocsp = opt.enable_ocsp; + ctrl->validation_model = default_validation_model; +} + + +int +gpgsm_parse_validation_model (const char *model) +{ + if (!ascii_strcasecmp (model, "shell") ) + return 0; + else if ( !ascii_strcasecmp (model, "chain") ) + return 1; + else + return -1; +} + + +/* Check whether the filename has the form "-&nnnn", where n is a + non-zero number. Returns this number or -1 if it is not the case. */ +static int +check_special_filename (const char *fname, int for_write) +{ + if (allow_special_filenames + && fname && *fname == '-' && fname[1] == '&' ) { + int i; + + fname += 2; + for (i=0; isdigit (fname[i]); i++ ) + ; + if ( !fname[i] ) + return translate_sys2libc_fd_int (atoi (fname), for_write); + } + return -1; +} + + + +/* Open the FILENAME for read and return the filedescriptor. Stop + with an error message in case of problems. "-" denotes stdin and + if special filenames are allowed the given fd is opened instead. */ +static int +open_read (const char *filename) +{ + int fd; + + if (filename[0] == '-' && !filename[1]) + { + set_binary (stdin); + return 0; /* stdin */ + } + fd = check_special_filename (filename, 0); + if (fd != -1) + return fd; + fd = open (filename, O_RDONLY | O_BINARY); + if (fd == -1) + { + log_error (_("can't open `%s': %s\n"), filename, strerror (errno)); + gpgsm_exit (2); + } + return fd; +} + +/* Same as open_read but return an estream_t. */ +static estream_t +open_es_fread (const char *filename) +{ + int fd; + estream_t fp; + + if (filename[0] == '-' && !filename[1]) + fd = fileno (stdin); + else + fd = check_special_filename (filename, 0); + if (fd != -1) + { + fp = es_fdopen_nc (fd, "rb"); + if (!fp) + { + log_error ("es_fdopen(%d) failed: %s\n", fd, strerror (errno)); + gpgsm_exit (2); + } + return fp; + } + fp = es_fopen (filename, "rb"); + if (!fp) + { + log_error (_("can't open `%s': %s\n"), filename, strerror (errno)); + gpgsm_exit (2); + } + return fp; +} + + +/* Open FILENAME for fwrite and return the stream. Stop with an error + message in case of problems. "-" denotes stdout and if special + filenames are allowed the given fd is opened instead. Caller must + close the returned stream unless it is stdout. */ +static FILE * +open_fwrite (const char *filename) +{ + int fd; + FILE *fp; + + if (filename[0] == '-' && !filename[1]) + { + set_binary (stdout); + return stdout; + } + + fd = check_special_filename (filename, 1); + if (fd != -1) + { + fp = fdopen (dup (fd), "wb"); + if (!fp) + { + log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); + gpgsm_exit (2); + } + set_binary (fp); + return fp; + } + fp = fopen (filename, "wb"); + if (!fp) + { + log_error (_("can't open `%s': %s\n"), filename, strerror (errno)); + gpgsm_exit (2); + } + return fp; +} + + +/* Open FILENAME for fwrite and return an extended stream. Stop with + an error message in case of problems. "-" denotes stdout and if + special filenames are allowed the given fd is opened instead. + Caller must close the returned stream. */ +static estream_t +open_es_fwrite (const char *filename) +{ + int fd; + estream_t fp; + + if (filename[0] == '-' && !filename[1]) + { + fflush (stdout); + fp = es_fdopen_nc (fileno(stdout), "wb"); + return fp; + } + + fd = check_special_filename (filename, 1); + if (fd != -1) + { + fp = es_fdopen_nc (fd, "wb"); + if (!fp) + { + log_error ("es_fdopen(%d) failed: %s\n", fd, strerror (errno)); + gpgsm_exit (2); + } + return fp; + } + fp = es_fopen (filename, "wb"); + if (!fp) + { + log_error (_("can't open `%s': %s\n"), filename, strerror (errno)); + gpgsm_exit (2); + } + return fp; +} + + +static void +run_protect_tool (int argc, char **argv) +{ +#ifndef HAVE_W32_SYSTEM + const char *pgm; + char **av; + int i; + + if (!opt.protect_tool_program || !*opt.protect_tool_program) + pgm = gnupg_module_name (GNUPG_MODULE_NAME_PROTECT_TOOL); + else + pgm = opt.protect_tool_program; + + av = xcalloc (argc+2, sizeof *av); + av[0] = strrchr (pgm, '/'); + if (!av[0]) + av[0] = xstrdup (pgm); + for (i=1; argc; i++, argc--, argv++) + av[i] = *argv; + av[i] = NULL; + execv (pgm, av); + log_error ("error executing `%s': %s\n", pgm, strerror (errno)); +#endif /*HAVE_W32_SYSTEM*/ + gpgsm_exit (2); +} diff --git a/sm/gpgsm.h b/sm/gpgsm.h new file mode 100644 index 0000000..c4a261b --- /dev/null +++ b/sm/gpgsm.h @@ -0,0 +1,423 @@ +/* gpgsm.h - Global definitions for GpgSM + * Copyright (C) 2001, 2003, 2004, 2007, 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef GPGSM_H +#define GPGSM_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGSM +#include <gpg-error.h> + + +#include <ksba.h> +#include "../common/util.h" +#include "../common/status.h" +#include "../common/estream.h" +#include "../common/audit.h" +#include "../common/session-env.h" + + +#define MAX_DIGEST_LEN 64 + +struct keyserver_spec +{ + struct keyserver_spec *next; + + char *host; + int port; + char *user; + char *pass; + char *base; +}; + + +/* A large struct named "opt" to keep global flags. */ +struct +{ + unsigned int debug; /* debug flags (DBG_foo_VALUE) */ + int verbose; /* verbosity level */ + int quiet; /* be as quiet as possible */ + int batch; /* run in batch mode, i.e w/o any user interaction */ + int answer_yes; /* assume yes on most questions */ + int answer_no; /* assume no on most questions */ + int dry_run; /* don't change any persistent data */ + + const char *homedir; /* Configuration directory name */ + const char *config_filename; /* Name of the used config file. */ + const char *agent_program; + + session_env_t session_env; + char *lc_ctype; + char *lc_messages; + + const char *dirmngr_program; + int prefer_system_dirmngr; /* Prefer using a system wide drimngr. */ + int disable_dirmngr; /* Do not do any dirmngr calls. */ + const char *protect_tool_program; + char *outfile; /* name of output file */ + + int with_key_data;/* include raw key in the column delimted output */ + + int fingerprint; /* list fingerprints in all key listings */ + + int with_md5_fingerprint; /* Also print an MD5 fingerprint for + standard key listings. */ + + int armor; /* force base64 armoring (see also ctrl.with_base64) */ + int no_armor; /* don't try to figure out whether data is base64 armored*/ + + const char *p12_charset; /* Use this charset for encoding the + pkcs#12 passphrase. */ + + + const char *def_cipher_algoid; /* cipher algorithm to use if + nothing else is specified */ + + int def_compress_algo; /* Ditto for compress algorithm */ + + int forced_digest_algo; /* User forced hash algorithm. */ + + char *def_recipient; /* userID of the default recipient */ + int def_recipient_self; /* The default recipient is the default key */ + + int no_encrypt_to; /* Ignore all as encrypt to marked recipients. */ + + char *local_user; /* NULL or argument to -u */ + + int extra_digest_algo; /* A digest algorithm also used for + verification of signatures. */ + + int always_trust; /* Trust the given keys even if there is no + valid certification chain */ + int skip_verify; /* do not check signatures on data */ + + int lock_once; /* Keep lock once they are set */ + + int ignore_time_conflict; /* Ignore certain time conflicts */ + + int no_crl_check; /* Don't do a CRL check */ + int no_trusted_cert_crl_check; /* Don't run a CRL check for trusted certs. */ + int force_crl_refresh; /* Force refreshing the CRL. */ + int enable_ocsp; /* Default to use OCSP checks. */ + + char *policy_file; /* full pathname of policy file */ + int no_policy_check; /* ignore certificate policies */ + int no_chain_validation; /* Bypass all cert chain validity tests */ + int ignore_expiration; /* Ignore the notAfter validity checks. */ + char *fixed_passphrase; /* Passphrase used by regression tests. */ + + int auto_issuer_key_retrieve; /* try to retrieve a missing issuer key. */ + + int qualsig_approval; /* Set to true if this software has + officially been approved to create an + verify qualified signatures. This is a + runtime option in case we want to check + the integrity of the software at + runtime. */ + + struct keyserver_spec *keyserver; + + /* A list of certificate extension OIDs which are ignored so that + one can claim that a critical extension has been handled. One + OID per string. */ + strlist_t ignored_cert_extensions; + +} opt; + +/* Debug values and macros. */ +#define DBG_X509_VALUE 1 /* debug x.509 data reading/writing */ +#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_ASSUAN_VALUE 1024 /* debug assuan communication */ + +#define DBG_X509 (opt.debug & DBG_X509_VALUE) +#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_ASSUAN (opt.debug & DBG_ASSUAN_VALUE) + +/* Forward declaration for an object defined in server.c */ +struct server_local_s; + +/* Session control object. This object is passed down to most + functions. Note that the default values for it are set by + gpgsm_init_default_ctrl(). */ +struct server_control_s +{ + int no_server; /* We are not running under server control */ + int status_fd; /* Only for non-server mode */ + struct server_local_s *server_local; + + audit_ctx_t audit; /* NULL or a context for the audit subsystem. */ + int agent_seen; /* Flag indicating that the gpg-agent has been + accessed. */ + + int with_colons; /* Use column delimited output format */ + int with_chain; /* Include the certifying certs in a listing */ + int with_validation;/* Validate each key while listing. */ + int with_ephemeral_keys; /* Include ephemeral flagged keys in the + keylisting. */ + + int autodetect_encoding; /* Try to detect the input encoding */ + int is_pem; /* Is in PEM format */ + int is_base64; /* is in plain base-64 format */ + + int create_base64; /* Create base64 encoded output */ + int create_pem; /* create PEM output */ + const char *pem_name; /* PEM name to use */ + + int include_certs; /* -1 to send all certificates in the chain + along with a signature or the number of + certificates up the chain (0 = none, 1 = only + signer) */ + int use_ocsp; /* Set to true if OCSP should be used. */ + int validation_model; /* Set to 1 for the chain model. */ +}; + + +/* Data structure used in base64.c. */ +typedef struct base64_context_s *Base64Context; + + +/* An object to keep a list of certificates. */ +struct certlist_s +{ + struct certlist_s *next; + ksba_cert_t cert; + int is_encrypt_to; /* True if the certificate has been set through + the --encrypto-to option. */ + int hash_algo; /* Used to track the hash algorithm to use. */ + const char *hash_algo_oid; /* And the corresponding OID. */ +}; +typedef struct certlist_s *certlist_t; + + +/* A structure carrying information about trusted root certificates. */ +struct rootca_flags_s +{ + unsigned int valid:1; /* The rest of the structure has valid + information. */ + unsigned int relax:1; /* Relax checking of root certificates. */ + unsigned int chain_model:1; /* Root requires the use of the chain model. */ +}; + + + +/*-- gpgsm.c --*/ +void gpgsm_exit (int rc); +void gpgsm_init_default_ctrl (struct server_control_s *ctrl); +int gpgsm_parse_validation_model (const char *model); + +/*-- server.c --*/ +void gpgsm_server (certlist_t default_recplist); +gpg_error_t gpgsm_status (ctrl_t ctrl, int no, const char *text); +gpg_error_t gpgsm_status2 (ctrl_t ctrl, int no, ...) GNUPG_GCC_A_SENTINEL(0); +gpg_error_t gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, + gpg_err_code_t ec); +gpg_error_t gpgsm_proxy_pinentry_notify (ctrl_t ctrl, + const unsigned char *line); + +/*-- fingerprint --*/ +unsigned char *gpgsm_get_fingerprint (ksba_cert_t cert, int algo, + unsigned char *array, int *r_len); +char *gpgsm_get_fingerprint_string (ksba_cert_t cert, int algo); +char *gpgsm_get_fingerprint_hexstring (ksba_cert_t cert, int algo); +unsigned long gpgsm_get_short_fingerprint (ksba_cert_t cert, + unsigned long *r_high); +unsigned char *gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array); +char *gpgsm_get_keygrip_hexstring (ksba_cert_t cert); +int gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits); +char *gpgsm_get_certid (ksba_cert_t cert); + + +/*-- base64.c --*/ +int gpgsm_create_reader (Base64Context *ctx, + ctrl_t ctrl, FILE *fp, int allow_multi_pem, + ksba_reader_t *r_reader); +int gpgsm_reader_eof_seen (Base64Context ctx); +void gpgsm_destroy_reader (Base64Context ctx); +int gpgsm_create_writer (Base64Context *ctx, + ctrl_t ctrl, FILE *fp, estream_t stream, + ksba_writer_t *r_writer); +int gpgsm_finish_writer (Base64Context ctx); +void gpgsm_destroy_writer (Base64Context ctx); + + +/*-- certdump.c --*/ +void gpgsm_print_serial (estream_t fp, ksba_const_sexp_t p); +void gpgsm_print_time (estream_t fp, ksba_isotime_t t); +void gpgsm_print_name2 (FILE *fp, const char *string, int translate); +void gpgsm_print_name (FILE *fp, const char *string); +void gpgsm_es_print_name (estream_t fp, const char *string); +void gpgsm_es_print_name2 (estream_t fp, const char *string, int translate); + +void gpgsm_cert_log_name (const char *text, ksba_cert_t cert); + +void gpgsm_dump_cert (const char *text, ksba_cert_t cert); +void gpgsm_dump_serial (ksba_const_sexp_t p); +void gpgsm_dump_time (ksba_isotime_t t); +void gpgsm_dump_string (const char *string); + +char *gpgsm_format_serial (ksba_const_sexp_t p); +char *gpgsm_format_name2 (const char *name, int translate); +char *gpgsm_format_name (const char *name); +char *gpgsm_format_sn_issuer (ksba_sexp_t sn, const char *issuer); + +char *gpgsm_fpr_and_name_for_status (ksba_cert_t cert); + +char *gpgsm_format_keydesc (ksba_cert_t cert); + + +/*-- certcheck.c --*/ +int gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert); +int gpgsm_check_cms_signature (ksba_cert_t cert, ksba_const_sexp_t sigval, + gcry_md_hd_t md, int hash_algo, int *r_pkalgo); +/* fixme: move create functions to another file */ +int gpgsm_create_cms_signature (ctrl_t ctrl, + ksba_cert_t cert, gcry_md_hd_t md, int mdalgo, + unsigned char **r_sigval); + + +/*-- certchain.c --*/ + +/* Flags used with gpgsm_validate_chain. */ +#define VALIDATE_FLAG_NO_DIRMNGR 1 +#define VALIDATE_FLAG_CHAIN_MODEL 2 + + +int gpgsm_walk_cert_chain (ctrl_t ctrl, + ksba_cert_t start, ksba_cert_t *r_next); +int gpgsm_is_root_cert (ksba_cert_t cert); +int gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, + ksba_isotime_t checktime, + ksba_isotime_t r_exptime, + int listmode, estream_t listfp, + unsigned int flags, unsigned int *retflags); +int gpgsm_basic_cert_check (ctrl_t ctrl, ksba_cert_t cert); + +/*-- certlist.c --*/ +int gpgsm_cert_use_sign_p (ksba_cert_t cert); +int gpgsm_cert_use_encrypt_p (ksba_cert_t cert); +int gpgsm_cert_use_verify_p (ksba_cert_t cert); +int gpgsm_cert_use_decrypt_p (ksba_cert_t cert); +int gpgsm_cert_use_cert_p (ksba_cert_t cert); +int gpgsm_cert_use_ocsp_p (ksba_cert_t cert); +int gpgsm_certs_identical_p (ksba_cert_t cert_a, ksba_cert_t cert_b); +int gpgsm_add_cert_to_certlist (ctrl_t ctrl, ksba_cert_t cert, + certlist_t *listaddr, int is_encrypt_to); +int gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret, + certlist_t *listaddr, int is_encrypt_to); +void gpgsm_release_certlist (certlist_t list); +int gpgsm_find_cert (const char *name, ksba_sexp_t keyid, ksba_cert_t *r_cert); + +/*-- keylist.c --*/ +gpg_error_t gpgsm_list_keys (ctrl_t ctrl, strlist_t names, + estream_t fp, unsigned int mode); + +/*-- import.c --*/ +int gpgsm_import (ctrl_t ctrl, int in_fd, int reimport_mode); +int gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files, + int (*of)(const char *fname)); + +/*-- export.c --*/ +void gpgsm_export (ctrl_t ctrl, strlist_t names, FILE *fp, estream_t stream); +void gpgsm_p12_export (ctrl_t ctrl, const char *name, FILE *fp); + +/*-- delete.c --*/ +int gpgsm_delete (ctrl_t ctrl, strlist_t names); + +/*-- verify.c --*/ +int gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, FILE *out_fp); + +/*-- sign.c --*/ +int gpgsm_get_default_cert (ctrl_t ctrl, ksba_cert_t *r_cert); +int gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, + int data_fd, int detached, FILE *out_fp); + +/*-- encrypt.c --*/ +int gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int in_fd, FILE *out_fp); + +/*-- decrypt.c --*/ +int gpgsm_decrypt (ctrl_t ctrl, int in_fd, FILE *out_fp); + +/*-- certreqgen.c --*/ +int gpgsm_genkey (ctrl_t ctrl, estream_t in_stream, FILE *out_fp); + +/*-- certreqgen-ui.c --*/ +void gpgsm_gencertreq_tty (ctrl_t ctrl, FILE *out_fp); + + +/*-- qualified.c --*/ +gpg_error_t gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert, + char *country); +gpg_error_t gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert); +gpg_error_t gpgsm_not_qualified_warning (ctrl_t ctrl, ksba_cert_t cert); + +/*-- call-agent.c --*/ +int gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc, + unsigned char *digest, + size_t digestlen, + int digestalgo, + unsigned char **r_buf, size_t *r_buflen); +int gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, + unsigned char *digest, size_t digestlen, int digestalgo, + unsigned char **r_buf, size_t *r_buflen); +int gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, + ksba_const_sexp_t ciphertext, + char **r_buf, size_t *r_buflen); +int gpgsm_agent_genkey (ctrl_t ctrl, + ksba_const_sexp_t keyparms, ksba_sexp_t *r_pubkey); +int gpgsm_agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip, + ksba_sexp_t *r_pubkey); +int gpgsm_agent_scd_serialno (ctrl_t ctrl, char **r_serialno); +int gpgsm_agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list); +int gpgsm_agent_istrusted (ctrl_t ctrl, ksba_cert_t cert, const char *hexfpr, + struct rootca_flags_s *rootca_flags); +int gpgsm_agent_havekey (ctrl_t ctrl, const char *hexkeygrip); +int gpgsm_agent_marktrusted (ctrl_t ctrl, ksba_cert_t cert); +int gpgsm_agent_learn (ctrl_t ctrl); +int gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc); +gpg_error_t gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc); +gpg_error_t gpgsm_agent_send_nop (ctrl_t ctrl); +gpg_error_t gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip, + char **r_serialno); + +/*-- call-dirmngr.c --*/ +int gpgsm_dirmngr_isvalid (ctrl_t ctrl, + ksba_cert_t cert, ksba_cert_t issuer_cert, + int use_ocsp); +int gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names, int cache_only, + void (*cb)(void*, ksba_cert_t), void *cb_value); +int gpgsm_dirmngr_run_command (ctrl_t ctrl, const char *command, + int argc, char **argv); + + +/*-- misc.c --*/ +void setup_pinentry_env (void); + + + +#endif /*GPGSM_H*/ diff --git a/sm/import.c b/sm/import.c new file mode 100644 index 0000000..287e723 --- /dev/null +++ b/sm/import.c @@ -0,0 +1,807 @@ +/* import.c - Import certificates + * Copyright (C) 2001, 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <assert.h> +#include <unistd.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "exechelp.h" +#include "i18n.h" +#include "sysutils.h" +#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */ + + +struct stats_s { + unsigned long count; + unsigned long imported; + unsigned long unchanged; + unsigned long not_imported; + unsigned long secret_read; + unsigned long secret_imported; + unsigned long secret_dups; + }; + + +static gpg_error_t parse_p12 (ctrl_t ctrl, ksba_reader_t reader, FILE **retfp, + struct stats_s *stats); + + + +static void +print_imported_status (ctrl_t ctrl, ksba_cert_t cert, int new_cert) +{ + char *fpr; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + if (new_cert) + gpgsm_status2 (ctrl, STATUS_IMPORTED, fpr, "[X.509]", NULL); + + gpgsm_status2 (ctrl, STATUS_IMPORT_OK, + new_cert? "1":"0", fpr, NULL); + + xfree (fpr); +} + + +/* Print an IMPORT_PROBLEM status. REASON is one of: + 0 := "No specific reason given". + 1 := "Invalid Certificate". + 2 := "Issuer Certificate missing". + 3 := "Certificate Chain too long". + 4 := "Error storing certificate". +*/ +static void +print_import_problem (ctrl_t ctrl, ksba_cert_t cert, int reason) +{ + char *fpr = NULL; + char buf[25]; + int i; + + sprintf (buf, "%d", reason); + if (cert) + { + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + /* detetect an error (all high) value */ + for (i=0; fpr[i] == 'F'; i++) + ; + if (!fpr[i]) + { + xfree (fpr); + fpr = NULL; + } + } + gpgsm_status2 (ctrl, STATUS_IMPORT_PROBLEM, buf, fpr, NULL); + xfree (fpr); +} + + +void +print_imported_summary (ctrl_t ctrl, struct stats_s *stats) +{ + char buf[14*25]; + + if (!opt.quiet) + { + log_info (_("total number processed: %lu\n"), stats->count); + if (stats->imported) + { + log_info (_(" imported: %lu"), stats->imported ); + log_printf ("\n"); + } + if (stats->unchanged) + log_info (_(" unchanged: %lu\n"), stats->unchanged); + if (stats->secret_read) + log_info (_(" secret keys read: %lu\n"), stats->secret_read ); + if (stats->secret_imported) + log_info (_(" secret keys imported: %lu\n"), stats->secret_imported ); + if (stats->secret_dups) + log_info (_(" secret keys unchanged: %lu\n"), stats->secret_dups ); + if (stats->not_imported) + log_info (_(" not imported: %lu\n"), stats->not_imported); + } + + sprintf(buf, "%lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", + stats->count, + 0l /*stats->no_user_id*/, + stats->imported, + 0l /*stats->imported_rsa*/, + stats->unchanged, + 0l /*stats->n_uids*/, + 0l /*stats->n_subk*/, + 0l /*stats->n_sigs*/, + 0l /*stats->n_revoc*/, + stats->secret_read, + stats->secret_imported, + stats->secret_dups, + 0l /*stats->skipped_new_keys*/, + stats->not_imported + ); + gpgsm_status (ctrl, STATUS_IMPORT_RES, buf); +} + + + +static void +check_and_store (ctrl_t ctrl, struct stats_s *stats, + ksba_cert_t cert, int depth) +{ + int rc; + + if (stats) + stats->count++; + if ( depth >= 50 ) + { + log_error (_("certificate chain too long\n")); + if (stats) + stats->not_imported++; + print_import_problem (ctrl, cert, 3); + return; + } + + /* Some basic checks, but don't care about missing certificates; + this is so that we are able to import entire certificate chains + w/o requiring a special order (i.e. root-CA first). This used + to be different but because gpgsm_verify even imports + certificates without any checks, it doesn't matter much and the + code gets much cleaner. A housekeeping function to remove + certificates w/o an anchor would be nice, though. + + Optionally we do a full validation in addition to the basic test. + */ + rc = gpgsm_basic_cert_check (ctrl, cert); + if (!rc && ctrl->with_validation) + rc = gpgsm_validate_chain (ctrl, cert, "", NULL, 0, NULL, 0, NULL); + if (!rc || (!ctrl->with_validation + && (gpg_err_code (rc) == GPG_ERR_MISSING_CERT + || gpg_err_code (rc) == GPG_ERR_MISSING_ISSUER_CERT))) + { + int existed; + + if (!keydb_store_cert (cert, 0, &existed)) + { + ksba_cert_t next = NULL; + + if (!existed) + { + print_imported_status (ctrl, cert, 1); + if (stats) + stats->imported++; + } + else + { + print_imported_status (ctrl, cert, 0); + if (stats) + stats->unchanged++; + } + + if (opt.verbose > 1 && existed) + { + if (depth) + log_info ("issuer certificate already in DB\n"); + else + log_info ("certificate already in DB\n"); + } + else if (opt.verbose && !existed) + { + if (depth) + log_info ("issuer certificate imported\n"); + else + log_info ("certificate imported\n"); + } + + /* Now lets walk up the chain and import all certificates up + the chain. This is required in case we already stored + parent certificates in the ephemeral keybox. Do not + update the statistics, though. */ + if (!gpgsm_walk_cert_chain (ctrl, cert, &next)) + { + check_and_store (ctrl, NULL, next, depth+1); + ksba_cert_release (next); + } + } + else + { + log_error (_("error storing certificate\n")); + if (stats) + stats->not_imported++; + print_import_problem (ctrl, cert, 4); + } + } + else + { + log_error (_("basic certificate checks failed - not imported\n")); + if (stats) + stats->not_imported++; + /* We keep the test for GPG_ERR_MISSING_CERT only in case + GPG_ERR_MISSING_CERT has been used instead of the newer + GPG_ERR_MISSING_ISSUER_CERT. */ + print_import_problem + (ctrl, cert, + gpg_err_code (rc) == GPG_ERR_MISSING_ISSUER_CERT? 2 : + gpg_err_code (rc) == GPG_ERR_MISSING_CERT? 2 : + gpg_err_code (rc) == GPG_ERR_BAD_CERT? 1 : 0); + } +} + + + + +static int +import_one (ctrl_t ctrl, struct stats_s *stats, int in_fd) +{ + int rc; + Base64Context b64reader = NULL; + ksba_reader_t reader; + ksba_cert_t cert = NULL; + ksba_cms_t cms = NULL; + FILE *fp = NULL; + ksba_content_type_t ct; + int any = 0; + + fp = fdopen ( dup (in_fd), "rb"); + if (!fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + rc = gpgsm_create_reader (&b64reader, ctrl, fp, 1, &reader); + if (rc) + { + log_error ("can't create reader: %s\n", gpg_strerror (rc)); + goto leave; + } + + + /* We need to loop here to handle multiple PEM objects in one + file. */ + do + { + ksba_cms_release (cms); cms = NULL; + ksba_cert_release (cert); cert = NULL; + + ct = ksba_cms_identify (reader); + if (ct == KSBA_CT_SIGNED_DATA) + { /* This is probably a signed-only message - import the certs */ + ksba_stop_reason_t stopreason; + int i; + + rc = ksba_cms_new (&cms); + if (rc) + goto leave; + + rc = ksba_cms_set_reader_writer (cms, reader, NULL); + if (rc) + { + log_error ("ksba_cms_set_reader_writer failed: %s\n", + gpg_strerror (rc)); + goto leave; + } + + do + { + rc = ksba_cms_parse (cms, &stopreason); + if (rc) + { + log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (stopreason == KSBA_SR_BEGIN_DATA) + log_info ("not a certs-only message\n"); + } + while (stopreason != KSBA_SR_READY); + + for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++) + { + check_and_store (ctrl, stats, cert, 0); + ksba_cert_release (cert); + cert = NULL; + } + if (!i) + log_error ("no certificate found\n"); + else + any = 1; + } + else if (ct == KSBA_CT_PKCS12) + { /* This seems to be a pkcs12 message. We use an external + tool to parse the message and to store the private keys. + We need to use a another reader here to parse the + certificate we included in the p12 file; then we continue + to look for other pkcs12 files (works only if they are in + PEM format. */ + FILE *certfp; + Base64Context b64p12rdr; + ksba_reader_t p12rdr; + + rc = parse_p12 (ctrl, reader, &certfp, stats); + if (!rc) + { + any = 1; + + rewind (certfp); + rc = gpgsm_create_reader (&b64p12rdr, ctrl, certfp, 1, &p12rdr); + if (rc) + { + log_error ("can't create reader: %s\n", gpg_strerror (rc)); + fclose (certfp); + goto leave; + } + + do + { + ksba_cert_release (cert); cert = NULL; + rc = ksba_cert_new (&cert); + if (!rc) + { + rc = ksba_cert_read_der (cert, p12rdr); + if (!rc) + check_and_store (ctrl, stats, cert, 0); + } + ksba_reader_clear (p12rdr, NULL, NULL); + } + while (!rc && !gpgsm_reader_eof_seen (b64p12rdr)); + + if (gpg_err_code (rc) == GPG_ERR_EOF) + rc = 0; + gpgsm_destroy_reader (b64p12rdr); + fclose (certfp); + if (rc) + goto leave; + } + } + else if (ct == KSBA_CT_NONE) + { /* Failed to identify this message - assume a certificate */ + + rc = ksba_cert_new (&cert); + if (rc) + goto leave; + + rc = ksba_cert_read_der (cert, reader); + if (rc) + goto leave; + + check_and_store (ctrl, stats, cert, 0); + any = 1; + } + else + { + log_error ("can't extract certificates from input\n"); + rc = gpg_error (GPG_ERR_NO_DATA); + } + + ksba_reader_clear (reader, NULL, NULL); + } + while (!gpgsm_reader_eof_seen (b64reader)); + + leave: + if (any && gpg_err_code (rc) == GPG_ERR_EOF) + rc = 0; + ksba_cms_release (cms); + ksba_cert_release (cert); + gpgsm_destroy_reader (b64reader); + if (fp) + fclose (fp); + return rc; +} + + + +/* Re-import certifciates. IN_FD is a list of linefeed delimited + fingerprints t re-import. The actual re-import is done by clearing + the ephemeral flag. */ +static int +reimport_one (ctrl_t ctrl, struct stats_s *stats, int in_fd) +{ + gpg_error_t err = 0; + estream_t fp = NULL; + char line[100]; /* Sufficient for a fingerprint. */ + KEYDB_HANDLE kh; + KEYDB_SEARCH_DESC desc; + ksba_cert_t cert = NULL; + unsigned int flags; + + kh = keydb_new (0); + if (!kh) + { + err = gpg_error (GPG_ERR_ENOMEM);; + log_error (_("failed to allocate keyDB handle\n")); + goto leave; + } + keydb_set_ephemeral (kh, 1); + + fp = es_fdopen_nc (in_fd, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("es_fdopen(%d) failed: %s\n", in_fd, gpg_strerror (err)); + goto leave; + } + + while (es_fgets (line, DIM(line)-1, fp) ) + { + if (*line && line[strlen(line)-1] != '\n') + { + err = gpg_error (GPG_ERR_LINE_TOO_LONG); + goto leave; + } + trim_spaces (line); + if (!*line) + continue; + + stats->count++; + + err = keydb_classify_name (line, &desc); + if (err) + { + print_import_problem (ctrl, NULL, 0); + stats->not_imported++; + continue; + } + + keydb_search_reset (kh); + err = keydb_search (kh, &desc, 1); + if (err) + { + print_import_problem (ctrl, NULL, 0); + stats->not_imported++; + continue; + } + + ksba_cert_release (cert); + cert = NULL; + err = keydb_get_cert (kh, &cert); + if (err) + { + log_error ("keydb_get_cert() failed: %s\n", gpg_strerror (err)); + print_import_problem (ctrl, NULL, 1); + stats->not_imported++; + continue; + } + + err = keydb_get_flags (kh, KEYBOX_FLAG_BLOB, 0, &flags); + if (err) + { + log_error (_("error getting stored flags: %s\n"), gpg_strerror (err)); + print_imported_status (ctrl, cert, 0); + stats->not_imported++; + continue; + } + if ( !(flags & KEYBOX_FLAG_BLOB_EPHEMERAL) ) + { + print_imported_status (ctrl, cert, 0); + stats->unchanged++; + continue; + } + + err = keydb_set_cert_flags (cert, 1, KEYBOX_FLAG_BLOB, 0, + KEYBOX_FLAG_BLOB_EPHEMERAL, 0); + if (err) + { + log_error ("clearing ephemeral flag failed: %s\n", + gpg_strerror (err)); + print_import_problem (ctrl, cert, 0); + stats->not_imported++; + continue; + } + + print_imported_status (ctrl, cert, 1); + stats->imported++; + } + err = 0; + if (es_ferror (fp)) + { + err = gpg_error_from_syserror (); + log_error ("error reading fd %d: %s\n", in_fd, gpg_strerror (err)); + goto leave; + } + + leave: + ksba_cert_release (cert); + keydb_release (kh); + es_fclose (fp); + return err; +} + + + +int +gpgsm_import (ctrl_t ctrl, int in_fd, int reimport_mode) +{ + int rc; + struct stats_s stats; + + memset (&stats, 0, sizeof stats); + if (reimport_mode) + rc = reimport_one (ctrl, &stats, in_fd); + else + rc = import_one (ctrl, &stats, in_fd); + print_imported_summary (ctrl, &stats); + /* If we never printed an error message do it now so that a command + line invocation will return with an error (log_error keeps a + global errorcount) */ + if (rc && !log_get_errorcount (0)) + log_error (_("error importing certificate: %s\n"), gpg_strerror (rc)); + return rc; +} + + +int +gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files, + int (*of)(const char *fname)) +{ + int rc = 0; + struct stats_s stats; + + memset (&stats, 0, sizeof stats); + + if (!nfiles) + rc = import_one (ctrl, &stats, 0); + else + { + for (; nfiles && !rc ; nfiles--, files++) + { + int fd = of (*files); + rc = import_one (ctrl, &stats, fd); + close (fd); + if (rc == -1) + rc = 0; + } + } + print_imported_summary (ctrl, &stats); + /* If we never printed an error message do it now so that a command + line invocation will return with an error (log_error keeps a + global errorcount) */ + if (rc && !log_get_errorcount (0)) + log_error (_("error importing certificate: %s\n"), gpg_strerror (rc)); + return rc; +} + + +/* Fork and exec the protecttool, connect the file descriptor of + INFILE to stdin, return a new stream in STATUSFILE, write the + output to OUTFILE and the pid of the process in PID. Returns 0 on + success or an error code. */ +static gpg_error_t +popen_protect_tool (ctrl_t ctrl, const char *pgmname, + FILE *infile, FILE *outfile, FILE **statusfile, pid_t *pid) +{ + const char *argv[22]; + int i=0; + + /* Make sure that the agent is running so that the protect tool is + able to ask for a passphrase. This has only an effect under W32 + where the agent is started on demand; sending a NOP does not harm + on other platforms. This is not really necessary anymore because + the protect tool does this now by itself; it does not harm either. */ + gpgsm_agent_send_nop (ctrl); + + argv[i++] = "--homedir"; + argv[i++] = opt.homedir; + argv[i++] = "--p12-import"; + argv[i++] = "--store"; + argv[i++] = "--no-fail-on-exist"; + argv[i++] = "--enable-status-msg"; + if (opt.fixed_passphrase) + { + argv[i++] = "--passphrase"; + argv[i++] = opt.fixed_passphrase; + } + if (opt.agent_program) + { + argv[i++] = "--agent-program"; + argv[i++] = opt.agent_program; + } + argv[i++] = "--", + argv[i] = NULL; + assert (i < sizeof argv); + + return gnupg_spawn_process (pgmname, argv, infile, outfile, + setup_pinentry_env, (128 | 64), + statusfile, pid); +} + + +/* Assume that the reader is at a pkcs#12 message and try to import + certificates from that stupid format. We will also store secret + keys. All of the pkcs#12 parsing and key storing is handled by the + gpg-protect-tool, we merely have to take care of receiving the + certificates. On success RETFP returns a temporary file with + certificates. */ +static gpg_error_t +parse_p12 (ctrl_t ctrl, ksba_reader_t reader, + FILE **retfp, struct stats_s *stats) +{ + const char *pgmname; + gpg_error_t err = 0, child_err = 0; + int c, cont_line; + unsigned int pos; + FILE *tmpfp, *certfp = NULL, *fp = NULL; + char buffer[1024]; + size_t nread; + pid_t pid = -1; + int bad_pass = 0; + + if (!opt.protect_tool_program || !*opt.protect_tool_program) + pgmname = gnupg_module_name (GNUPG_MODULE_NAME_PROTECT_TOOL); + else + pgmname = opt.protect_tool_program; + + *retfp = NULL; + + /* To avoid an extra feeder process or doing selects and because + gpg-protect-tool will anyway parse the entire pkcs#12 message in + memory, we simply use tempfiles here and pass them to + the gpg-protect-tool. */ + tmpfp = gnupg_tmpfile (); + if (!tmpfp) + { + err = gpg_error_from_syserror (); + log_error (_("error creating temporary file: %s\n"), strerror (errno)); + goto cleanup; + } + while (!(err = ksba_reader_read (reader, buffer, sizeof buffer, &nread))) + { + if (nread && fwrite (buffer, nread, 1, tmpfp) != 1) + { + err = gpg_error_from_syserror (); + log_error (_("error writing to temporary file: %s\n"), + strerror (errno)); + goto cleanup; + } + } + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; + if (err) + { + log_error (_("error reading input: %s\n"), gpg_strerror (err)); + goto cleanup; + } + + certfp = gnupg_tmpfile (); + if (!certfp) + { + err = gpg_error_from_syserror (); + log_error (_("error creating temporary file: %s\n"), strerror (errno)); + goto cleanup; + } + + err = popen_protect_tool (ctrl, pgmname, tmpfp, certfp, &fp, &pid); + if (err) + { + pid = -1; + goto cleanup; + } + fclose (tmpfp); + tmpfp = NULL; + + /* Read stderr of the protect tool. */ + pos = 0; + cont_line = 0; + while ((c=getc (fp)) != EOF) + { + /* fixme: We could here grep for status information of the + protect tool to figure out better error codes for + CHILD_ERR. */ + buffer[pos++] = c; + if (pos >= sizeof buffer - 5 || c == '\n') + { + buffer[pos - (c == '\n')] = 0; + if (cont_line) + log_printf ("%s", buffer); + else + { + if (!strncmp (buffer, "gpg-protect-tool: [PROTECT-TOOL:] ",34)) + { + char *p, *pend; + + p = buffer + 34; + pend = strchr (p, ' '); + if (pend) + *pend = 0; + if ( !strcmp (p, "secretkey-stored")) + { + stats->count++; + stats->secret_read++; + stats->secret_imported++; + } + else if ( !strcmp (p, "secretkey-exists")) + { + stats->count++; + stats->secret_read++; + stats->secret_dups++; + } + else if ( !strcmp (p, "bad-passphrase")) + { + + } + } + else + { + log_info ("%s", buffer); + if (!strncmp (buffer, "gpg-protect-tool: " + "possibly bad passphrase given",46)) + bad_pass++; + } + } + pos = 0; + cont_line = (c != '\n'); + } + } + + if (pos) + { + buffer[pos] = 0; + if (cont_line) + log_printf ("%s\n", buffer); + else + log_info ("%s\n", buffer); + } + + + /* If we found no error in the output of the child, setup a suitable + error code, which will later be reset if the exit status of the + child is 0. */ + if (!child_err) + child_err = gpg_error (GPG_ERR_DECRYPT_FAILED); + + cleanup: + if (tmpfp) + fclose (tmpfp); + if (fp) + fclose (fp); + if (pid != -1) + { + if (!gnupg_wait_process (pgmname, pid, NULL)) + child_err = 0; + } + if (!err) + err = child_err; + if (err) + { + if (certfp) + fclose (certfp); + } + else + *retfp = certfp; + + if (bad_pass) + { + /* We only write a plain error code and not direct + BAD_PASSPHRASE because the pkcs12 parser might issue this + message multiple times, BAD_PASSPHRASE in general requires a + keyID and parts of the import might actually succeed so that + IMPORT_PROBLEM is also not appropriate. */ + gpgsm_status_with_err_code (ctrl, STATUS_ERROR, + "import.parsep12", GPG_ERR_BAD_PASSPHRASE); + } + + return err; +} diff --git a/sm/keydb.c b/sm/keydb.c new file mode 100644 index 0000000..37f791e --- /dev/null +++ b/sm/keydb.c @@ -0,0 +1,1535 @@ +/* keydb.c - key database dispatcher + * Copyright (C) 2001, 2003, 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "gpgsm.h" +#include "../kbx/keybox.h" +#include "keydb.h" +#include "i18n.h" + +static int active_handles; + +typedef enum { + KEYDB_RESOURCE_TYPE_NONE = 0, + KEYDB_RESOURCE_TYPE_KEYBOX +} KeydbResourceType; +#define MAX_KEYDB_RESOURCES 20 + +struct resource_item { + KeydbResourceType type; + union { + KEYBOX_HANDLE kr; + } u; + void *token; + int secret; + DOTLOCK lockhandle; +}; + +static struct resource_item all_resources[MAX_KEYDB_RESOURCES]; +static int used_resources; + +struct keydb_handle { + int locked; + int found; + int current; + int is_ephemeral; + int used; /* items in active */ + struct resource_item active[MAX_KEYDB_RESOURCES]; +}; + + +static int lock_all (KEYDB_HANDLE hd); +static void unlock_all (KEYDB_HANDLE hd); + + +/* + * Register a resource (which currently may only be a keybox file). + * The first keybox which is added by this function is created if it + * does not exist. If AUTO_CREATED is not NULL it will be set to true + * if the function has created a a new keybox. + */ +int +keydb_add_resource (const char *url, int force, int secret, int *auto_created) +{ + static int any_secret, any_public; + const char *resname = url; + char *filename = NULL; + int rc = 0; + FILE *fp; + KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE; + + if (auto_created) + *auto_created = 0; + + /* Do we have an URL? + gnupg-kbx:filename := this is a plain keybox + filename := See what is is, but create as plain keybox. + */ + if (strlen (resname) > 10) + { + if (!strncmp (resname, "gnupg-kbx:", 10) ) + { + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + resname += 10; + } +#if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__) + else if (strchr (resname, ':')) + { + log_error ("invalid key resource URL `%s'\n", url ); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } +#endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */ + } + + if (*resname != DIRSEP_C ) + { /* do tilde expansion etc */ + if (strchr(resname, DIRSEP_C) ) + filename = make_filename (resname, NULL); + else + filename = make_filename (opt.homedir, resname, NULL); + } + else + filename = xstrdup (resname); + + if (!force) + force = secret? !any_secret : !any_public; + + /* see whether we can determine the filetype */ + if (rt == KEYDB_RESOURCE_TYPE_NONE) + { + FILE *fp2 = fopen( filename, "rb" ); + + if (fp2) { + u32 magic; + + /* FIXME: check for the keybox magic */ + if (fread( &magic, 4, 1, fp2) == 1 ) + { + if (magic == 0x13579ace || magic == 0xce9a5713) + ; /* GDBM magic - no more support */ + else + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + } + else /* maybe empty: assume ring */ + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + fclose (fp2); + } + else /* no file yet: create ring */ + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + } + + switch (rt) + { + case KEYDB_RESOURCE_TYPE_NONE: + log_error ("unknown type of key resource `%s'\n", url ); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + + case KEYDB_RESOURCE_TYPE_KEYBOX: + fp = fopen (filename, "rb"); + if (!fp && !force) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + goto leave; + } + + if (!fp) + { /* no file */ +#if 0 /* no autocreate of the homedirectory yet */ + { + char *last_slash_in_filename; + + last_slash_in_filename = strrchr (filename, DIRSEP_C); + *last_slash_in_filename = 0; + if (access (filename, F_OK)) + { /* on the first time we try to create the default + homedir and in this case the process will be + terminated, so that on the next invocation can + read the options file in on startup */ + try_make_homedir (filename); + rc = gpg_error (GPG_ERR_FILE_OPEN_ERROR); + *last_slash_in_filename = DIRSEP_C; + goto leave; + } + *last_slash_in_filename = DIRSEP_C; + } +#endif + fp = fopen (filename, "w"); + if (!fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error (_("error creating keybox `%s': %s\n"), + filename, strerror(errno)); + if (errno == ENOENT) + log_info (_("you may want to start the gpg-agent first\n")); + goto leave; + } + + if (!opt.quiet) + log_info (_("keybox `%s' created\n"), filename); + if (auto_created) + *auto_created = 1; + } + fclose (fp); + fp = NULL; + /* now register the file */ + { + + void *token = keybox_register_file (filename, secret); + if (!token) + ; /* already registered - ignore it */ + else if (used_resources >= MAX_KEYDB_RESOURCES) + rc = gpg_error (GPG_ERR_RESOURCE_LIMIT); + else + { + all_resources[used_resources].type = rt; + all_resources[used_resources].u.kr = NULL; /* Not used here */ + all_resources[used_resources].token = token; + all_resources[used_resources].secret = secret; + + all_resources[used_resources].lockhandle + = create_dotlock (filename); + if (!all_resources[used_resources].lockhandle) + log_fatal ( _("can't create lock for `%s'\n"), filename); + + /* Do a compress run if needed and the file is not locked. */ + if (!make_dotlock (all_resources[used_resources].lockhandle, 0)) + { + KEYBOX_HANDLE kbxhd = keybox_new (token, secret); + + if (kbxhd) + { + keybox_compress (kbxhd); + keybox_release (kbxhd); + } + release_dotlock (all_resources[used_resources].lockhandle); + } + + used_resources++; + } + } + + + break; + default: + log_error ("resource type of `%s' not supported\n", url); + rc = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + /* fixme: check directory permissions and print a warning */ + + leave: + if (rc) + log_error ("keyblock resource `%s': %s\n", filename, gpg_strerror(rc)); + else if (secret) + any_secret = 1; + else + any_public = 1; + xfree (filename); + return rc; +} + + +KEYDB_HANDLE +keydb_new (int secret) +{ + KEYDB_HANDLE hd; + int i, j; + + hd = xcalloc (1, sizeof *hd); + hd->found = -1; + + assert (used_resources <= MAX_KEYDB_RESOURCES); + for (i=j=0; i < used_resources; i++) + { + if (!all_resources[i].secret != !secret) + continue; + switch (all_resources[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + hd->active[j].type = all_resources[i].type; + hd->active[j].token = all_resources[i].token; + hd->active[j].secret = all_resources[i].secret; + hd->active[j].lockhandle = all_resources[i].lockhandle; + hd->active[j].u.kr = keybox_new (all_resources[i].token, secret); + if (!hd->active[j].u.kr) + { + xfree (hd); + return NULL; /* fixme: release all previously allocated handles*/ + } + j++; + break; + } + } + hd->used = j; + + active_handles++; + return hd; +} + +void +keydb_release (KEYDB_HANDLE hd) +{ + int i; + + if (!hd) + return; + assert (active_handles > 0); + active_handles--; + + unlock_all (hd); + for (i=0; i < hd->used; i++) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + keybox_release (hd->active[i].u.kr); + break; + } + } + + xfree (hd); +} + + +/* Return the name of the current resource. This is function first + looks for the last found found, then for the current search + position, and last returns the first available resource. The + returned string is only valid as long as the handle exists. This + function does only return NULL if no handle is specified, in all + other error cases an empty string is returned. */ +const char * +keydb_get_resource_name (KEYDB_HANDLE hd) +{ + int idx; + const char *s = NULL; + + if (!hd) + return NULL; + + if ( hd->found >= 0 && hd->found < hd->used) + idx = hd->found; + else if ( hd->current >= 0 && hd->current < hd->used) + idx = hd->current; + else + idx = 0; + + switch (hd->active[idx].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + s = NULL; + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + s = keybox_get_resource_name (hd->active[idx].u.kr); + break; + } + + return s? s: ""; +} + +/* Switch the handle into ephemeral mode and return the orginal value. */ +int +keydb_set_ephemeral (KEYDB_HANDLE hd, int yes) +{ + int i; + + if (!hd) + return 0; + + yes = !!yes; + if (hd->is_ephemeral != yes) + { + for (i=0; i < hd->used; i++) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + keybox_set_ephemeral (hd->active[i].u.kr, yes); + break; + } + } + } + + i = hd->is_ephemeral; + hd->is_ephemeral = yes; + return i; +} + + +/* If the keyring has not yet been locked, lock it now. This + operation is required before any update operation; it is optional + for an insert operation. The lock is released with + keydb_released. */ +gpg_error_t +keydb_lock (KEYDB_HANDLE hd) +{ + if (!hd) + return gpg_error (GPG_ERR_INV_HANDLE); + if (hd->locked) + return 0; /* Already locked. */ + return lock_all (hd); +} + + + +static int +lock_all (KEYDB_HANDLE hd) +{ + int i, rc = 0; + + /* Fixme: This locking scheme may lead to deadlock if the resources + are not added in the same order by all processes. We are + currently only allowing one resource so it is not a problem. */ + for (i=0; i < hd->used; i++) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + if (hd->active[i].lockhandle) + rc = make_dotlock (hd->active[i].lockhandle, -1); + break; + } + if (rc) + break; + } + + if (rc) + { + /* revert the already set locks */ + for (i--; i >= 0; i--) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + if (hd->active[i].lockhandle) + release_dotlock (hd->active[i].lockhandle); + break; + } + } + } + else + hd->locked = 1; + + /* make_dotlock () does not yet guarantee that errno is set, thus + we can't rely on the error reason and will simply use + EACCES. */ + return rc? gpg_error (GPG_ERR_EACCES) : 0; +} + +static void +unlock_all (KEYDB_HANDLE hd) +{ + int i; + + if (!hd->locked) + return; + + for (i=hd->used-1; i >= 0; i--) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + if (hd->active[i].lockhandle) + release_dotlock (hd->active[i].lockhandle); + break; + } + } + hd->locked = 0; +} + + +#if 0 +/* + * Return the last found keybox. Caller must free it. + * The returned keyblock has the kbode flag bit 0 set for the node with + * the public key used to locate the keyblock or flag bit 1 set for + * the user ID node. + */ +int +keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb) +{ + int rc = 0; + + if (!hd) + return G10ERR_INV_ARG; + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + switch (hd->active[hd->found].type) { + case KEYDB_RESOURCE_TYPE_NONE: + rc = G10ERR_GENERAL; /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_get_keyblock (hd->active[hd->found].u.kr, ret_kb); + break; + } + + return rc; +} + +/* + * update the current keyblock with KB + */ +int +keydb_update_keyblock (KEYDB_HANDLE hd, KBNODE kb) +{ + int rc = 0; + + if (!hd) + return G10ERR_INV_ARG; + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + if( opt.dry_run ) + return 0; + + if (!hd->locked) + return gpg_error (GPG_ERR_NOT_LOCKED); + + switch (hd->active[hd->found].type) { + case KEYDB_RESOURCE_TYPE_NONE: + rc = G10ERR_GENERAL; /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_update_keyblock (hd->active[hd->found].u.kr, kb); + break; + } + + unlock_all (hd); + return rc; +} + + +/* + * Insert a new KB into one of the resources. + */ +int +keydb_insert_keyblock (KEYDB_HANDLE hd, KBNODE kb) +{ + int rc = -1; + int idx; + + if (!hd) + return G10ERR_INV_ARG; + + if( opt.dry_run ) + return 0; + + if ( hd->found >= 0 && hd->found < hd->used) + idx = hd->found; + else if ( hd->current >= 0 && hd->current < hd->used) + idx = hd->current; + else + return G10ERR_GENERAL; + + rc = lock_all (hd); + if (rc) + return rc; + + switch (hd->active[idx].type) { + case KEYDB_RESOURCE_TYPE_NONE: + rc = G10ERR_GENERAL; /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_insert_keyblock (hd->active[idx].u.kr, kb); + break; + } + + unlock_all (hd); + return rc; +} + +#endif /*disabled code*/ + + + +/* + Return the last found object. Caller must free it. The returned + keyblock has the kbode flag bit 0 set for the node with the public + key used to locate the keyblock or flag bit 1 set for the user ID + node. */ +int +keydb_get_cert (KEYDB_HANDLE hd, ksba_cert_t *r_cert) +{ + int rc = 0; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + rc = gpg_error (GPG_ERR_GENERAL); /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_get_cert (hd->active[hd->found].u.kr, r_cert); + break; + } + + return rc; +} + +/* Return a flag of the last found object. WHICH is the flag requested; + it should be one of the KEYBOX_FLAG_ values. If the operation is + successful, the flag value will be stored at the address given by + VALUE. Return 0 on success or an error code. */ +gpg_error_t +keydb_get_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int *value) +{ + int err = 0; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if ( hd->found < 0 || hd->found >= hd->used) + return gpg_error (GPG_ERR_NOTHING_FOUND); + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + err = gpg_error (GPG_ERR_GENERAL); /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + err = keybox_get_flags (hd->active[hd->found].u.kr, which, idx, value); + break; + } + + return err; +} + +/* Set a flag of the last found object. WHICH is the flag to be set; it + should be one of the KEYBOX_FLAG_ values. If the operation is + successful, the flag value will be stored in the keybox. Note, + that some flag values can't be updated and thus may return an + error, some other flag values may be masked out before an update. + Returns 0 on success or an error code. */ +gpg_error_t +keydb_set_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int value) +{ + int err = 0; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if ( hd->found < 0 || hd->found >= hd->used) + return gpg_error (GPG_ERR_NOTHING_FOUND); + + if (!hd->locked) + return gpg_error (GPG_ERR_NOT_LOCKED); + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + err = gpg_error (GPG_ERR_GENERAL); /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + err = keybox_set_flags (hd->active[hd->found].u.kr, which, idx, value); + break; + } + + return err; +} + +/* + * Insert a new Certificate into one of the resources. + */ +int +keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert) +{ + int rc = -1; + int idx; + unsigned char digest[20]; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if (opt.dry_run) + return 0; + + if ( hd->found >= 0 && hd->found < hd->used) + idx = hd->found; + else if ( hd->current >= 0 && hd->current < hd->used) + idx = hd->current; + else + return gpg_error (GPG_ERR_GENERAL); + + if (!hd->locked) + return gpg_error (GPG_ERR_NOT_LOCKED); + + gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/ + + switch (hd->active[idx].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + rc = gpg_error (GPG_ERR_GENERAL); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_insert_cert (hd->active[idx].u.kr, cert, digest); + break; + } + + unlock_all (hd); + return rc; +} + + + +/* Update the current keyblock with KB. */ +int +keydb_update_cert (KEYDB_HANDLE hd, ksba_cert_t cert) +{ + int rc = 0; + unsigned char digest[20]; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + if (opt.dry_run) + return 0; + + rc = lock_all (hd); + if (rc) + return rc; + + gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/ + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + rc = gpg_error (GPG_ERR_GENERAL); /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_update_cert (hd->active[hd->found].u.kr, cert, digest); + break; + } + + unlock_all (hd); + return rc; +} + + +/* + * The current keyblock or cert will be deleted. + */ +int +keydb_delete (KEYDB_HANDLE hd, int unlock) +{ + int rc = -1; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + if( opt.dry_run ) + return 0; + + if (!hd->locked) + return gpg_error (GPG_ERR_NOT_LOCKED); + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + rc = gpg_error (GPG_ERR_GENERAL); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_delete (hd->active[hd->found].u.kr); + break; + } + + if (unlock) + unlock_all (hd); + return rc; +} + + + +/* + * Locate the default writable key resource, so that the next + * operation (which is only relevant for inserts) will be done on this + * resource. + */ +int +keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved) +{ + int rc; + + (void)reserved; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + rc = keydb_search_reset (hd); /* this does reset hd->current */ + if (rc) + return rc; + + for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++) + { + switch (hd->active[hd->current].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + BUG(); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + if (keybox_is_writable (hd->active[hd->current].token)) + return 0; /* found (hd->current is set to it) */ + break; + } + } + + return -1; +} + +/* + * Rebuild the caches of all key resources. + */ +void +keydb_rebuild_caches (void) +{ + int i; + + for (i=0; i < used_resources; i++) + { + if (all_resources[i].secret) + continue; + switch (all_resources[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: +/* rc = keybox_rebuild_cache (all_resources[i].token); */ +/* if (rc) */ +/* log_error (_("failed to rebuild keybox cache: %s\n"), */ +/* g10_errstr (rc)); */ + break; + } + } +} + + + +/* + * Start the next search on this handle right at the beginning + */ +int +keydb_search_reset (KEYDB_HANDLE hd) +{ + int i, rc = 0; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + hd->current = 0; + hd->found = -1; + /* and reset all resources */ + for (i=0; !rc && i < hd->used; i++) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_search_reset (hd->active[i].u.kr); + break; + } + } + return rc; /* fixme: we need to map error codes or share them with + all modules*/ +} + +/* + * Search through all keydb resources, starting at the current position, + * for a keyblock which contains one of the keys described in the DESC array. + */ +int +keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc) +{ + int rc = -1; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + while (rc == -1 && hd->current >= 0 && hd->current < hd->used) + { + switch (hd->active[hd->current].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + BUG(); /* we should never see it here */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_search (hd->active[hd->current].u.kr, desc, ndesc); + break; + } + if (rc == -1) /* EOF -> switch to next resource */ + hd->current++; + else if (!rc) + hd->found = hd->current; + } + + return rc; +} + + +int +keydb_search_first (KEYDB_HANDLE hd) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_FIRST; + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_next (KEYDB_HANDLE hd) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_NEXT; + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_kid (KEYDB_HANDLE hd, u32 *kid) +{ + KEYDB_SEARCH_DESC desc; + + (void)kid; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_LONG_KID; +/* desc.u.kid[0] = kid[0]; */ +/* desc.u.kid[1] = kid[1]; */ + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_FPR; + memcpy (desc.u.fpr, fpr, 20); + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_issuer (KEYDB_HANDLE hd, const char *issuer) +{ + KEYDB_SEARCH_DESC desc; + int rc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_ISSUER; + desc.u.name = issuer; + rc = keydb_search (hd, &desc, 1); + return rc; +} + +int +keydb_search_issuer_sn (KEYDB_HANDLE hd, + const char *issuer, ksba_const_sexp_t serial) +{ + KEYDB_SEARCH_DESC desc; + int rc; + const unsigned char *s; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_ISSUER_SN; + s = serial; + if (*s !='(') + return gpg_error (GPG_ERR_INV_VALUE); + s++; + for (desc.snlen = 0; digitp (s); s++) + desc.snlen = 10*desc.snlen + atoi_1 (s); + if (*s !=':') + return gpg_error (GPG_ERR_INV_VALUE); + desc.sn = s+1; + desc.u.name = issuer; + rc = keydb_search (hd, &desc, 1); + return rc; +} + +int +keydb_search_subject (KEYDB_HANDLE hd, const char *name) +{ + KEYDB_SEARCH_DESC desc; + int rc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_SUBJECT; + desc.u.name = name; + rc = keydb_search (hd, &desc, 1); + return rc; +} + + +static int +classify_user_id (const char *name, + KEYDB_SEARCH_DESC *desc, + int *force_exact ) +{ + const char *s; + int hexprefix = 0; + int hexlength; + int mode = 0; + + /* clear the structure so that the mode field is set to zero unless + * we set it to the correct value right at the end of this function */ + memset (desc, 0, sizeof *desc); + *force_exact = 0; + /* Skip leading spaces. Fixme: what about trailing white space? */ + for(s = name; *s && spacep (s); s++ ) + ; + + switch (*s) + { + case 0: /* empty string is an error */ + return 0; + + case '.': /* an email address, compare from end */ + mode = KEYDB_SEARCH_MODE_MAILEND; + s++; + desc->u.name = s; + break; + + case '<': /* an email address */ + mode = KEYDB_SEARCH_MODE_MAIL; + s++; + desc->u.name = s; + break; + + case '@': /* part of an email address */ + mode = KEYDB_SEARCH_MODE_MAILSUB; + s++; + desc->u.name = s; + break; + + case '=': /* exact compare */ + mode = KEYDB_SEARCH_MODE_EXACT; + s++; + desc->u.name = s; + break; + + case '*': /* case insensitive substring search */ + mode = KEYDB_SEARCH_MODE_SUBSTR; + s++; + desc->u.name = s; + break; + + case '+': /* compare individual words */ + mode = KEYDB_SEARCH_MODE_WORDS; + s++; + desc->u.name = s; + break; + + case '/': /* subject's DN */ + s++; + if (!*s || spacep (s)) + return 0; /* no DN or prefixed with a space */ + desc->u.name = s; + mode = KEYDB_SEARCH_MODE_SUBJECT; + break; + + case '#': + { + const char *si; + + s++; + if ( *s == '/') + { /* "#/" indicates an issuer's DN */ + s++; + if (!*s || spacep (s)) + return 0; /* no DN or prefixed with a space */ + desc->u.name = s; + mode = KEYDB_SEARCH_MODE_ISSUER; + } + else + { /* serialnumber + optional issuer ID */ + for (si=s; *si && *si != '/'; si++) + { + if (!strchr("01234567890abcdefABCDEF", *si)) + return 0; /* invalid digit in serial number*/ + } + desc->sn = (const unsigned char*)s; + desc->snlen = -1; + if (!*si) + mode = KEYDB_SEARCH_MODE_SN; + else + { + s = si+1; + if (!*s || spacep (s)) + return 0; /* no DN or prefixed with a space */ + desc->u.name = s; + mode = KEYDB_SEARCH_MODE_ISSUER_SN; + } + } + } + break; + + case ':': /*Unified fingerprint */ + { + const char *se, *si; + int i; + + se = strchr (++s,':'); + if (!se) + return 0; + for (i=0,si=s; si < se; si++, i++ ) + { + if (!strchr("01234567890abcdefABCDEF", *si)) + return 0; /* invalid digit */ + } + if (i != 32 && i != 40) + return 0; /* invalid length of fpr*/ + for (i=0,si=s; si < se; i++, si +=2) + desc->u.fpr[i] = hextobyte(si); + for (; i < 20; i++) + desc->u.fpr[i]= 0; + s = se + 1; + mode = KEYDB_SEARCH_MODE_FPR; + } + break; + + case '&': /* Keygrip*/ + { + if (hex2bin (s+1, desc->u.grip, 20) < 0) + return 0; /* Invalid. */ + mode = KEYDB_SEARCH_MODE_KEYGRIP; + } + break; + + default: + if (s[0] == '0' && s[1] == 'x') + { + hexprefix = 1; + s += 2; + } + + hexlength = strspn(s, "0123456789abcdefABCDEF"); + if (hexlength >= 8 && s[hexlength] =='!') + { + *force_exact = 1; + hexlength++; /* just for the following check */ + } + + /* check if a hexadecimal number is terminated by EOS or blank */ + if (hexlength && s[hexlength] && !spacep (s+hexlength)) + { + if (hexprefix) /* a "0x" prefix without correct */ + return 0; /* termination is an error */ + /* The first chars looked like a hex number, but really is + not */ + hexlength = 0; + } + + if (*force_exact) + hexlength--; /* remove the bang */ + + if (hexlength == 8 + || (!hexprefix && hexlength == 9 && *s == '0')) + { /* short keyid */ + unsigned long kid; + if (hexlength == 9) + s++; + kid = strtoul( s, NULL, 16 ); + desc->u.kid[4] = kid >> 24; + desc->u.kid[5] = kid >> 16; + desc->u.kid[6] = kid >> 8; + desc->u.kid[7] = kid; + mode = KEYDB_SEARCH_MODE_SHORT_KID; + } + else if (hexlength == 16 + || (!hexprefix && hexlength == 17 && *s == '0')) + { /* complete keyid */ + unsigned long kid0, kid1; + char buf[9]; + if (hexlength == 17) + s++; + mem2str(buf, s, 9 ); + kid0 = strtoul (buf, NULL, 16); + kid1 = strtoul (s+8, NULL, 16); + desc->u.kid[0] = kid0 >> 24; + desc->u.kid[1] = kid0 >> 16; + desc->u.kid[2] = kid0 >> 8; + desc->u.kid[3] = kid0; + desc->u.kid[4] = kid1 >> 24; + desc->u.kid[5] = kid1 >> 16; + desc->u.kid[6] = kid1 >> 8; + desc->u.kid[7] = kid1; + mode = KEYDB_SEARCH_MODE_LONG_KID; + } + else if (hexlength == 32 + || (!hexprefix && hexlength == 33 && *s == '0')) + { /* md5 fingerprint */ + int i; + if (hexlength == 33) + s++; + memset(desc->u.fpr+16, 0, 4); + for (i=0; i < 16; i++, s+=2) + { + int c = hextobyte(s); + if (c == -1) + return 0; + desc->u.fpr[i] = c; + } + mode = KEYDB_SEARCH_MODE_FPR16; + } + else if (hexlength == 40 + || (!hexprefix && hexlength == 41 && *s == '0')) + { /* sha1/rmd160 fingerprint */ + int i; + if (hexlength == 41) + s++; + for (i=0; i < 20; i++, s+=2) + { + int c = hextobyte(s); + if (c == -1) + return 0; + desc->u.fpr[i] = c; + } + mode = KEYDB_SEARCH_MODE_FPR20; + } + else if (!hexprefix) + { + /* The fingerprint in an X.509 listing is often delimited by + colons, so we try to single this case out. */ + mode = 0; + hexlength = strspn (s, ":0123456789abcdefABCDEF"); + if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength))) + { + int i; + + for (i=0; i < 20; i++, s += 3) + { + int c = hextobyte(s); + if (c == -1 || (i < 19 && s[2] != ':')) + break; + desc->u.fpr[i] = c; + } + if (i == 20) + mode = KEYDB_SEARCH_MODE_FPR20; + } + if (!mode) /* default is substring search */ + { + *force_exact = 0; + desc->u.name = s; + mode = KEYDB_SEARCH_MODE_SUBSTR; + } + } + else + { /* hex number with a prefix but a wrong length */ + return 0; + } + } + + desc->mode = mode; + return mode; +} + + +int +keydb_classify_name (const char *name, KEYDB_SEARCH_DESC *desc) +{ + int dummy; + KEYDB_SEARCH_DESC dummy_desc; + + if (!desc) + desc = &dummy_desc; + + if (!classify_user_id (name, desc, &dummy)) + return gpg_error (GPG_ERR_INV_NAME); + return 0; +} + + +/* Store the certificate in the key DB but make sure that it does not + already exists. We do this simply by comparing the fingerprint. + If EXISTED is not NULL it will be set to true if the certificate + was already in the DB. */ +int +keydb_store_cert (ksba_cert_t cert, int ephemeral, int *existed) +{ + KEYDB_HANDLE kh; + int rc; + unsigned char fpr[20]; + + if (existed) + *existed = 0; + + if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL)) + { + log_error (_("failed to get the fingerprint\n")); + return gpg_error (GPG_ERR_GENERAL); + } + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocate keyDB handle\n")); + return gpg_error (GPG_ERR_ENOMEM);; + } + + if (ephemeral) + keydb_set_ephemeral (kh, 1); + + rc = lock_all (kh); + if (rc) + return rc; + + rc = keydb_search_fpr (kh, fpr); + if (rc != -1) + { + keydb_release (kh); + if (!rc) + { + if (existed) + *existed = 1; + return 0; /* okay */ + } + log_error (_("problem looking for existing certificate: %s\n"), + gpg_strerror (rc)); + return rc; + } + + rc = keydb_locate_writable (kh, 0); + if (rc) + { + log_error (_("error finding writable keyDB: %s\n"), gpg_strerror (rc)); + keydb_release (kh); + return rc; + } + + rc = keydb_insert_cert (kh, cert); + if (rc) + { + log_error (_("error storing certificate: %s\n"), gpg_strerror (rc)); + keydb_release (kh); + return rc; + } + keydb_release (kh); + return 0; +} + + +/* This is basically keydb_set_flags but it implements a complete + transaction by locating the certificate in the DB and updating the + flags. */ +gpg_error_t +keydb_set_cert_flags (ksba_cert_t cert, int ephemeral, + int which, int idx, + unsigned int mask, unsigned int value) +{ + KEYDB_HANDLE kh; + gpg_error_t err; + unsigned char fpr[20]; + unsigned int old_value; + + if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL)) + { + log_error (_("failed to get the fingerprint\n")); + return gpg_error (GPG_ERR_GENERAL); + } + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocate keyDB handle\n")); + return gpg_error (GPG_ERR_ENOMEM);; + } + + if (ephemeral) + keydb_set_ephemeral (kh, 1); + + err = keydb_lock (kh); + if (err) + { + log_error (_("error locking keybox: %s\n"), gpg_strerror (err)); + keydb_release (kh); + return err; + } + + err = keydb_search_fpr (kh, fpr); + if (err) + { + if (err == -1) + err = gpg_error (GPG_ERR_NOT_FOUND); + else + log_error (_("problem re-searching certificate: %s\n"), + gpg_strerror (err)); + keydb_release (kh); + return err; + } + + err = keydb_get_flags (kh, which, idx, &old_value); + if (err) + { + log_error (_("error getting stored flags: %s\n"), gpg_strerror (err)); + keydb_release (kh); + return err; + } + + value = ((old_value & ~mask) | (value & mask)); + + if (value != old_value) + { + err = keydb_set_flags (kh, which, idx, value); + if (err) + { + log_error (_("error storing flags: %s\n"), gpg_strerror (err)); + keydb_release (kh); + return err; + } + } + + keydb_release (kh); + return 0; +} + + +/* Reset all the certificate flags we have stored with the certificates + for performance reasons. */ +void +keydb_clear_some_cert_flags (ctrl_t ctrl, strlist_t names) +{ + gpg_error_t err; + KEYDB_HANDLE hd = NULL; + KEYDB_SEARCH_DESC *desc = NULL; + int ndesc; + strlist_t sl; + int rc=0; + unsigned int old_value, value; + + (void)ctrl; + + hd = keydb_new (0); + if (!hd) + { + log_error ("keydb_new failed\n"); + goto leave; + } + + if (!names) + ndesc = 1; + else + { + for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) + ; + } + + desc = xtrycalloc (ndesc, sizeof *desc); + if (!ndesc) + { + log_error ("allocating memory failed: %s\n", + gpg_strerror (out_of_core ())); + goto leave; + } + + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_FIRST; + else + { + for (ndesc=0, sl=names; sl; sl = sl->next) + { + rc = keydb_classify_name (sl->d, desc+ndesc); + if (rc) + { + log_error ("key `%s' not found: %s\n", + sl->d, gpg_strerror (rc)); + rc = 0; + } + else + ndesc++; + } + } + + err = keydb_lock (hd); + if (err) + { + log_error (_("error locking keybox: %s\n"), gpg_strerror (err)); + goto leave; + } + + while (!(rc = keydb_search (hd, desc, ndesc))) + { + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_NEXT; + + err = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &old_value); + if (err) + { + log_error (_("error getting stored flags: %s\n"), + gpg_strerror (err)); + goto leave; + } + + value = (old_value & ~VALIDITY_REVOKED); + if (value != old_value) + { + err = keydb_set_flags (hd, KEYBOX_FLAG_VALIDITY, 0, value); + if (err) + { + log_error (_("error storing flags: %s\n"), gpg_strerror (err)); + goto leave; + } + } + } + if (rc && rc != -1) + log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); + + leave: + xfree (desc); + keydb_release (hd); +} + + diff --git a/sm/keydb.h b/sm/keydb.h new file mode 100644 index 0000000..a440c50 --- /dev/null +++ b/sm/keydb.h @@ -0,0 +1,86 @@ +/* keydb.h - Key database + * Copyright (C) 1998, 1999, 2000, 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef GNUPG_KEYDB_H +#define GNUPG_KEYDB_H + +#include <ksba.h> + +#include "../kbx/keybox-search-desc.h" + +typedef struct keydb_handle *KEYDB_HANDLE; + +/* Flag value used with KEYBOX_FLAG_VALIDITY. */ +#define VALIDITY_REVOKED (1<<5) + + +/*-- keydb.c --*/ +int keydb_add_resource (const char *url, int force, int secret, + int *auto_created); +KEYDB_HANDLE keydb_new (int secret); +void keydb_release (KEYDB_HANDLE hd); +int keydb_set_ephemeral (KEYDB_HANDLE hd, int yes); +const char *keydb_get_resource_name (KEYDB_HANDLE hd); +gpg_error_t keydb_lock (KEYDB_HANDLE hd); + +#if 0 /* pgp stuff */ +int keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb); +int keydb_update_keyblock (KEYDB_HANDLE hd, KBNODE kb); +int keydb_insert_keyblock (KEYDB_HANDLE hd, KBNODE kb); +#endif + +gpg_error_t keydb_get_flags (KEYDB_HANDLE hd, int which, int idx, + unsigned int *value); +gpg_error_t keydb_set_flags (KEYDB_HANDLE hd, int which, int idx, + unsigned int value); +int keydb_get_cert (KEYDB_HANDLE hd, ksba_cert_t *r_cert); +int keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert); +int keydb_update_cert (KEYDB_HANDLE hd, ksba_cert_t cert); + +int keydb_delete (KEYDB_HANDLE hd, int unlock); + +int keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved); +void keydb_rebuild_caches (void); + +int keydb_search_reset (KEYDB_HANDLE hd); +int keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc); +int keydb_search_first (KEYDB_HANDLE hd); +int keydb_search_next (KEYDB_HANDLE hd); +int keydb_search_kid (KEYDB_HANDLE hd, u32 *kid); +int keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr); +int keydb_search_issuer (KEYDB_HANDLE hd, const char *issuer); +int keydb_search_issuer_sn (KEYDB_HANDLE hd, + const char *issuer, const unsigned char *serial); +int keydb_search_subject (KEYDB_HANDLE hd, const char *issuer); + +int keydb_classify_name (const char *name, KEYDB_SEARCH_DESC *desc); + +int keydb_store_cert (ksba_cert_t cert, int ephemeral, int *existed); +gpg_error_t keydb_set_cert_flags (ksba_cert_t cert, int ephemeral, + int which, int idx, + unsigned int mask, unsigned int value); + +void keydb_clear_some_cert_flags (ctrl_t ctrl, strlist_t names); + + +#endif /*GNUPG_KEYDB_H*/ + + + + diff --git a/sm/keylist.c b/sm/keylist.c new file mode 100644 index 0000000..9b8538c --- /dev/null +++ b/sm/keylist.c @@ -0,0 +1,1562 @@ +/* keylist.c - Print certificates in various formats. + * Copyright (C) 1998, 1999, 2000, 2001, 2003, + * 2004, 2005, 2008, 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" + +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */ +#include "i18n.h" +#include "tlv.h" + +struct list_external_parm_s +{ + ctrl_t ctrl; + estream_t fp; + int print_header; + int with_colons; + int with_chain; + int raw_mode; +}; + + +/* This table is to map Extended Key Usage OIDs to human readable + names. */ +struct +{ + const char *oid; + const char *name; +} key_purpose_map[] = { + { "1.3.6.1.5.5.7.3.1", "serverAuth" }, + { "1.3.6.1.5.5.7.3.2", "clientAuth" }, + { "1.3.6.1.5.5.7.3.3", "codeSigning" }, + { "1.3.6.1.5.5.7.3.4", "emailProtection" }, + { "1.3.6.1.5.5.7.3.5", "ipsecEndSystem" }, + { "1.3.6.1.5.5.7.3.6", "ipsecTunnel" }, + { "1.3.6.1.5.5.7.3.7", "ipsecUser" }, + { "1.3.6.1.5.5.7.3.8", "timeStamping" }, + { "1.3.6.1.5.5.7.3.9", "ocspSigning" }, + { "1.3.6.1.5.5.7.3.10", "dvcs" }, + { "1.3.6.1.5.5.7.3.11", "sbgpCertAAServerAuth" }, + { "1.3.6.1.5.5.7.3.13", "eapOverPPP" }, + { "1.3.6.1.5.5.7.3.14", "wlanSSID" }, + + { "2.16.840.1.113730.4.1", "serverGatedCrypto.ns" }, /* Netscape. */ + { "1.3.6.1.4.1.311.10.3.3", "serverGatedCrypto.ms"}, /* Microsoft. */ + + { "1.3.6.1.5.5.7.48.1.5", "ocspNoCheck" }, + + { NULL, NULL } +}; + + +/* Do not print this extension in the list of extensions. This is set + for oids which are already available via ksba fucntions. */ +#define OID_FLAG_SKIP 1 +/* The extension is a simple UTF8String and should be printed. */ +#define OID_FLAG_UTF8 2 + +/* A table mapping OIDs to a descriptive string. */ +static struct +{ + char *oid; + char *name; + unsigned int flag; /* A flag as described above. */ +} oidtranstbl[] = { + + /* Algorithms. */ + { "1.2.840.10040.4.1", "dsa" }, + { "1.2.840.10040.4.3", "dsaWithSha1" }, + + { "1.2.840.113549.1.1.1", "rsaEncryption" }, + { "1.2.840.113549.1.1.2", "md2WithRSAEncryption" }, + { "1.2.840.113549.1.1.3", "md4WithRSAEncryption" }, + { "1.2.840.113549.1.1.4", "md5WithRSAEncryption" }, + { "1.2.840.113549.1.1.5", "sha1WithRSAEncryption" }, + { "1.2.840.113549.1.1.7", "rsaOAEP" }, + { "1.2.840.113549.1.1.8", "rsaOAEP-MGF" }, + { "1.2.840.113549.1.1.9", "rsaOAEP-pSpecified" }, + { "1.2.840.113549.1.1.10", "rsaPSS" }, + { "1.2.840.113549.1.1.11", "sha256WithRSAEncryption" }, + { "1.2.840.113549.1.1.12", "sha384WithRSAEncryption" }, + { "1.2.840.113549.1.1.13", "sha512WithRSAEncryption" }, + + { "1.3.14.3.2.26", "sha1" }, + { "1.3.14.3.2.29", "sha-1WithRSAEncryption" }, + { "1.3.36.3.3.1.2", "rsaSignatureWithripemd160" }, + + + /* Telesec extensions. */ + { "0.2.262.1.10.12.0", "certExtensionLiabilityLimitationExt" }, + { "0.2.262.1.10.12.1", "telesecCertIdExt" }, + { "0.2.262.1.10.12.2", "telesecPolicyIdentifier" }, + { "0.2.262.1.10.12.3", "telesecPolicyQualifierID" }, + { "0.2.262.1.10.12.4", "telesecCRLFilteredExt" }, + { "0.2.262.1.10.12.5", "telesecCRLFilterExt"}, + { "0.2.262.1.10.12.6", "telesecNamingAuthorityExt" }, +#define OIDSTR_restriction \ + "1.3.36.8.3.8" + { OIDSTR_restriction, "restriction", OID_FLAG_UTF8 }, + + + /* PKIX private extensions. */ + { "1.3.6.1.5.5.7.1.1", "authorityInfoAccess" }, + { "1.3.6.1.5.5.7.1.2", "biometricInfo" }, + { "1.3.6.1.5.5.7.1.3", "qcStatements" }, + { "1.3.6.1.5.5.7.1.4", "acAuditIdentity" }, + { "1.3.6.1.5.5.7.1.5", "acTargeting" }, + { "1.3.6.1.5.5.7.1.6", "acAaControls" }, + { "1.3.6.1.5.5.7.1.7", "sbgp-ipAddrBlock" }, + { "1.3.6.1.5.5.7.1.8", "sbgp-autonomousSysNum" }, + { "1.3.6.1.5.5.7.1.9", "sbgp-routerIdentifier" }, + { "1.3.6.1.5.5.7.1.10", "acProxying" }, + { "1.3.6.1.5.5.7.1.11", "subjectInfoAccess" }, + + { "1.3.6.1.5.5.7.48.1", "ocsp" }, + { "1.3.6.1.5.5.7.48.2", "caIssuers" }, + { "1.3.6.1.5.5.7.48.3", "timeStamping" }, + { "1.3.6.1.5.5.7.48.5", "caRepository" }, + + /* X.509 id-ce */ + { "2.5.29.14", "subjectKeyIdentifier", OID_FLAG_SKIP}, + { "2.5.29.15", "keyUsage", OID_FLAG_SKIP}, + { "2.5.29.16", "privateKeyUsagePeriod" }, + { "2.5.29.17", "subjectAltName", OID_FLAG_SKIP}, + { "2.5.29.18", "issuerAltName", OID_FLAG_SKIP}, + { "2.5.29.19", "basicConstraints", OID_FLAG_SKIP}, + { "2.5.29.20", "cRLNumber" }, + { "2.5.29.21", "cRLReason" }, + { "2.5.29.22", "expirationDate" }, + { "2.5.29.23", "instructionCode" }, + { "2.5.29.24", "invalidityDate" }, + { "2.5.29.27", "deltaCRLIndicator" }, + { "2.5.29.28", "issuingDistributionPoint" }, + { "2.5.29.29", "certificateIssuer" }, + { "2.5.29.30", "nameConstraints" }, + { "2.5.29.31", "cRLDistributionPoints", OID_FLAG_SKIP}, + { "2.5.29.32", "certificatePolicies", OID_FLAG_SKIP}, + { "2.5.29.32.0", "anyPolicy" }, + { "2.5.29.33", "policyMappings" }, + { "2.5.29.35", "authorityKeyIdentifier", OID_FLAG_SKIP}, + { "2.5.29.36", "policyConstraints" }, + { "2.5.29.37", "extKeyUsage", OID_FLAG_SKIP}, + { "2.5.29.46", "freshestCRL" }, + { "2.5.29.54", "inhibitAnyPolicy" }, + + /* Netscape certificate extensions. */ + { "2.16.840.1.113730.1.1", "netscape-cert-type" }, + { "2.16.840.1.113730.1.2", "netscape-base-url" }, + { "2.16.840.1.113730.1.3", "netscape-revocation-url" }, + { "2.16.840.1.113730.1.4", "netscape-ca-revocation-url" }, + { "2.16.840.1.113730.1.7", "netscape-cert-renewal-url" }, + { "2.16.840.1.113730.1.8", "netscape-ca-policy-url" }, + { "2.16.840.1.113730.1.9", "netscape-homePage-url" }, + { "2.16.840.1.113730.1.10", "netscape-entitylogo" }, + { "2.16.840.1.113730.1.11", "netscape-userPicture" }, + { "2.16.840.1.113730.1.12", "netscape-ssl-server-name" }, + { "2.16.840.1.113730.1.13", "netscape-comment" }, + + /* GnuPG extensions */ + { "1.3.6.1.4.1.11591.2.1.1", "pkaAddress" }, + + /* Extensions used by the Bundesnetzagentur. */ + { "1.3.6.1.4.1.8301.3.5", "validityModel" }, + + { NULL } +}; + + +/* Return the description for OID; if no description is available + NULL is returned. */ +static const char * +get_oid_desc (const char *oid, unsigned int *flag) +{ + int i; + + if (oid) + for (i=0; oidtranstbl[i].oid; i++) + if (!strcmp (oidtranstbl[i].oid, oid)) + { + if (flag) + *flag = oidtranstbl[i].flag; + return oidtranstbl[i].name; + } + if (flag) + *flag = 0; + return NULL; +} + + +static void +print_key_data (ksba_cert_t cert, estream_t fp) +{ +#if 0 + int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0; + int i; + + for(i=0; i < n; i++ ) + { + es_fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) ); + mpi_print(stdout, pk->pkey[i], 1 ); + putchar(':'); + putchar('\n'); + } +#else + (void)cert; + (void)fp; +#endif +} + +static void +print_capabilities (ksba_cert_t cert, estream_t fp) +{ + gpg_error_t err; + unsigned int use; + size_t buflen; + char buffer[1]; + + err = ksba_cert_get_user_data (cert, "is_qualified", + &buffer, sizeof (buffer), &buflen); + if (!err && buflen) + { + if (*buffer) + es_putc ('q', fp); + } + else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + ; /* Don't know - will not get marked as 'q' */ + else + log_debug ("get_user_data(is_qualified) failed: %s\n", + gpg_strerror (err)); + + err = ksba_cert_get_key_usage (cert, &use); + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + { + es_putc ('e', fp); + es_putc ('s', fp); + es_putc ('c', fp); + es_putc ('E', fp); + es_putc ('S', fp); + es_putc ('C', fp); + return; + } + if (err) + { + log_error (_("error getting key usage information: %s\n"), + gpg_strerror (err)); + return; + } + + if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) + es_putc ('e', fp); + if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) + es_putc ('s', fp); + if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN)) + es_putc ('c', fp); + if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) + es_putc ('E', fp); + if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) + es_putc ('S', fp); + if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN)) + es_putc ('C', fp); + + es_putc (':', fp); +} + + +static void +print_time (gnupg_isotime_t t, estream_t fp) +{ + if (!t || !*t) + ; + else + es_fputs (t, fp); +} + + +/* Return an allocated string with the email address extracted from a + DN. Note hat we use this code also in ../kbx/keybox-blob.c. */ +static char * +email_kludge (const char *name) +{ + const char *p, *string; + unsigned char *buf; + int n; + + string = name; + for (;;) + { + p = strstr (string, "1.2.840.113549.1.9.1=#"); + if (!p) + return NULL; + if (p == name || (p > string+1 && p[-1] == ',' && p[-2] != '\\')) + { + name = p + 22; + break; + } + string = p + 22; + } + + + /* This looks pretty much like an email address in the subject's DN + we use this to add an additional user ID entry. This way, + OpenSSL generated keys get a nicer and usable listing. */ + for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++) + ; + if (!n) + return NULL; + buf = xtrymalloc (n+3); + if (!buf) + return NULL; /* oops, out of core */ + *buf = '<'; + for (n=1, p=name; hexdigitp (p); p +=2, n++) + buf[n] = xtoi_2 (p); + buf[n++] = '>'; + buf[n] = 0; + return (char*)buf; +} + + + + +/* List one certificate in colon mode */ +static void +list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity, + estream_t fp, int have_secret) +{ + int rc; + int idx; + char truststring[2]; + char *p; + ksba_sexp_t sexp; + char *fpr; + ksba_isotime_t t; + gpg_error_t valerr; + int algo; + unsigned int nbits; + const char *chain_id; + char *chain_id_buffer = NULL; + int is_root = 0; + char *kludge_uid; + + if (ctrl->with_validation) + valerr = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, NULL, 0, NULL); + else + valerr = 0; + + + /* We need to get the fingerprint and the chaining ID in advance. */ + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + { + ksba_cert_t next; + + rc = gpgsm_walk_cert_chain (ctrl, cert, &next); + if (!rc) /* We known the issuer's certificate. */ + { + p = gpgsm_get_fingerprint_hexstring (next, GCRY_MD_SHA1); + chain_id_buffer = p; + chain_id = chain_id_buffer; + ksba_cert_release (next); + } + else if (rc == -1) /* We have reached the root certificate. */ + { + chain_id = fpr; + is_root = 1; + } + else + chain_id = NULL; + } + + + es_fputs (have_secret? "crs:":"crt:", fp); + + /* Note: We can't use multiple flags, like "ei", because the + validation check does only return one error. */ + truststring[0] = 0; + truststring[1] = 0; + if ((validity & VALIDITY_REVOKED) + || gpg_err_code (valerr) == GPG_ERR_CERT_REVOKED) + *truststring = 'r'; + else if (gpg_err_code (valerr) == GPG_ERR_CERT_EXPIRED) + *truststring = 'e'; + else + { + /* Lets also check whether the certificate under question + expired. This is merely a hack until we found a proper way + to store the expiration flag in the keybox. */ + ksba_isotime_t current_time, not_after; + + gnupg_get_isotime (current_time); + if (!opt.ignore_expiration + && !ksba_cert_get_validity (cert, 1, not_after) + && *not_after && strcmp (current_time, not_after) > 0 ) + *truststring = 'e'; + else if (valerr) + *truststring = 'i'; + else if (ctrl->with_validation && !is_root) + *truststring = 'f'; + } + + /* If we have no truststring yet (i.e. the certificate might be + good) and this is a root certificate, we ask the agent whether + this is a trusted root certificate. */ + if (!*truststring && is_root) + { + struct rootca_flags_s dummy_flags; + + rc = gpgsm_agent_istrusted (ctrl, cert, NULL, &dummy_flags); + if (!rc) + *truststring = 'u'; /* Yes, we trust this one (ultimately). */ + else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED) + *truststring = 'n'; /* No, we do not trust this one. */ + /* (in case of an error we can't tell anything.) */ + } + + if (*truststring) + es_fputs (truststring, fp); + + algo = gpgsm_get_key_algo_info (cert, &nbits); + es_fprintf (fp, ":%u:%d:%s:", nbits, algo, fpr+24); + + /* We assume --fixed-list-mode for gpgsm */ + ksba_cert_get_validity (cert, 0, t); + print_time (t, fp); + es_putc (':', fp); + ksba_cert_get_validity (cert, 1, t); + print_time ( t, fp); + es_putc (':', fp); + /* Field 8, serial number: */ + if ((sexp = ksba_cert_get_serial (cert))) + { + int len; + const unsigned char *s = sexp; + + if (*s == '(') + { + s++; + for (len=0; *s && *s != ':' && digitp (s); s++) + len = len*10 + atoi_1 (s); + if (*s == ':') + for (s++; len; len--, s++) + es_fprintf (fp,"%02X", *s); + } + xfree (sexp); + } + es_putc (':', fp); + /* Field 9, ownertrust - not used here */ + es_putc (':', fp); + /* field 10, old user ID - we use it here for the issuer DN */ + if ((p = ksba_cert_get_issuer (cert,0))) + { + es_write_sanitized (fp, p, strlen (p), ":", NULL); + xfree (p); + } + es_putc (':', fp); + /* Field 11, signature class - not used */ + es_putc (':', fp); + /* Field 12, capabilities: */ + print_capabilities (cert, fp); + /* Field 13, not used: */ + es_putc (':', fp); + if (have_secret) + { + char *cardsn; + + p = gpgsm_get_keygrip_hexstring (cert); + if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn) && cardsn) + { + /* Field 14, not used: */ + es_putc (':', fp); + /* Field 15: Token serial number. */ + es_fputs (cardsn, fp); + es_putc (':', fp); + } + xfree (cardsn); + xfree (p); + } + es_putc ('\n', fp); + + /* FPR record */ + es_fprintf (fp, "fpr:::::::::%s:::", fpr); + /* Print chaining ID (field 13)*/ + if (chain_id) + es_fputs (chain_id, fp); + es_putc (':', fp); + es_putc ('\n', fp); + xfree (fpr); fpr = NULL; chain_id = NULL; + xfree (chain_id_buffer); chain_id_buffer = NULL; + + if (opt.with_key_data) + { + if ( (p = gpgsm_get_keygrip_hexstring (cert))) + { + es_fprintf (fp, "grp:::::::::%s:\n", p); + xfree (p); + } + print_key_data (cert, fp); + } + + kludge_uid = NULL; + for (idx=0; (p = ksba_cert_get_subject (cert,idx)); idx++) + { + /* In the case that the same email address is in the subject DN + as well as in an alternate subject name we avoid printing it + a second time. */ + if (kludge_uid && !strcmp (kludge_uid, p)) + continue; + + es_fprintf (fp, "uid:%s::::::::", truststring); + es_write_sanitized (fp, p, strlen (p), ":", NULL); + es_putc (':', fp); + es_putc (':', fp); + es_putc ('\n', fp); + if (!idx) + { + /* It would be better to get the faked email address from + the keydb. But as long as we don't have a way to pass + the meta data back, we just check it the same way as the + code used to create the keybox meta data does */ + kludge_uid = email_kludge (p); + if (kludge_uid) + { + es_fprintf (fp, "uid:%s::::::::", truststring); + es_write_sanitized (fp, kludge_uid, strlen (kludge_uid), + ":", NULL); + es_putc (':', fp); + es_putc (':', fp); + es_putc ('\n', fp); + } + } + xfree (p); + } + xfree (kludge_uid); +} + + +static void +print_name_raw (estream_t fp, const char *string) +{ + if (!string) + es_fputs ("[error]", fp); + else + es_write_sanitized (fp, string, strlen (string), NULL, NULL); +} + +static void +print_names_raw (estream_t fp, int indent, ksba_name_t name) +{ + int idx; + const char *s; + int indent_all; + + if ((indent_all = (indent < 0))) + indent = - indent; + + if (!name) + { + es_fputs ("none\n", fp); + return; + } + + for (idx=0; (s = ksba_name_enum (name, idx)); idx++) + { + char *p = ksba_name_get_uri (name, idx); + es_fprintf (fp, "%*s", idx||indent_all?indent:0, ""); + es_write_sanitized (fp, p?p:s, strlen (p?p:s), NULL, NULL); + es_putc ('\n', fp); + xfree (p); + } +} + + +static void +print_utf8_extn_raw (estream_t fp, int indent, + const unsigned char *der, size_t derlen) +{ + gpg_error_t err; + int class, tag, constructed, ndef; + size_t objlen, hdrlen; + + if (indent < 0) + indent = - indent; + + err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > derlen || tag != TAG_UTF8_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + { + es_fprintf (fp, "%*s[%s]\n", indent, "", gpg_strerror (err)); + return; + } + es_fprintf (fp, "%*s(%.*s)\n", indent, "", (int)objlen, der); +} + + +static void +print_utf8_extn (estream_t fp, int indent, + const unsigned char *der, size_t derlen) +{ + gpg_error_t err; + int class, tag, constructed, ndef; + size_t objlen, hdrlen; + int indent_all; + + if ((indent_all = (indent < 0))) + indent = - indent; + + err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > derlen || tag != TAG_UTF8_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + { + es_fprintf (fp, "%*s[%s%s]\n", + indent_all? indent:0, "", _("Error - "), gpg_strerror (err)); + return; + } + es_fprintf (fp, "%*s\"", indent_all? indent:0, ""); + /* Fixme: we should implement word wrapping */ + es_write_sanitized (fp, der, objlen, "\"", NULL); + es_fputs ("\"\n", fp); +} + + +/* List one certificate in raw mode useful to have a closer look at + the certificate. This one does no beautification and only minimal + output sanitation. It is mainly useful for debugging. */ +static void +list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd, + ksba_cert_t cert, estream_t fp, int have_secret, + int with_validation) +{ + gpg_error_t err; + size_t off, len; + ksba_sexp_t sexp, keyid; + char *dn; + ksba_isotime_t t; + int idx, i; + int is_ca, chainlen; + unsigned int kusage; + char *string, *p, *pend; + const char *oid, *s; + ksba_name_t name, name2; + unsigned int reason; + const unsigned char *cert_der = NULL; + + (void)have_secret; + + es_fprintf (fp, " ID: 0x%08lX\n", + gpgsm_get_short_fingerprint (cert, NULL)); + + sexp = ksba_cert_get_serial (cert); + es_fputs (" S/N: ", fp); + gpgsm_print_serial (fp, sexp); + ksba_free (sexp); + es_putc ('\n', fp); + + dn = ksba_cert_get_issuer (cert, 0); + es_fputs (" Issuer: ", fp); + print_name_raw (fp, dn); + ksba_free (dn); + es_putc ('\n', fp); + for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++) + { + es_fputs (" aka: ", fp); + print_name_raw (fp, dn); + ksba_free (dn); + es_putc ('\n', fp); + } + + dn = ksba_cert_get_subject (cert, 0); + es_fputs (" Subject: ", fp); + print_name_raw (fp, dn); + ksba_free (dn); + es_putc ('\n', fp); + for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++) + { + es_fputs (" aka: ", fp); + print_name_raw (fp, dn); + ksba_free (dn); + es_putc ('\n', fp); + } + + dn = gpgsm_get_fingerprint_string (cert, 0); + es_fprintf (fp, " sha1_fpr: %s\n", dn?dn:"error"); + xfree (dn); + + dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5); + es_fprintf (fp, " md5_fpr: %s\n", dn?dn:"error"); + xfree (dn); + + dn = gpgsm_get_certid (cert); + es_fprintf (fp, " certid: %s\n", dn?dn:"error"); + xfree (dn); + + dn = gpgsm_get_keygrip_hexstring (cert); + es_fprintf (fp, " keygrip: %s\n", dn?dn:"error"); + xfree (dn); + + ksba_cert_get_validity (cert, 0, t); + es_fputs (" notBefore: ", fp); + gpgsm_print_time (fp, t); + es_putc ('\n', fp); + es_fputs (" notAfter: ", fp); + ksba_cert_get_validity (cert, 1, t); + gpgsm_print_time (fp, t); + es_putc ('\n', fp); + + oid = ksba_cert_get_digest_algo (cert); + s = get_oid_desc (oid, NULL); + es_fprintf (fp, " hashAlgo: %s%s%s%s\n", oid, s?" (":"",s?s:"",s?")":""); + + { + const char *algoname; + unsigned int nbits; + + algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits)); + es_fprintf (fp, " keyType: %u bit %s\n", + nbits, algoname? algoname:"?"); + } + + /* subjectKeyIdentifier */ + es_fputs (" subjKeyId: ", fp); + err = ksba_cert_get_subj_key_id (cert, NULL, &keyid); + if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) + { + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + es_fputs ("[none]\n", fp); + else + { + gpgsm_print_serial (fp, keyid); + ksba_free (keyid); + es_putc ('\n', fp); + } + } + else + es_fputs ("[?]\n", fp); + + + /* authorityKeyIdentifier */ + es_fputs (" authKeyId: ", fp); + err = ksba_cert_get_auth_key_id (cert, &keyid, &name, &sexp); + if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) + { + if (gpg_err_code (err) == GPG_ERR_NO_DATA || !name) + es_fputs ("[none]\n", fp); + else + { + gpgsm_print_serial (fp, sexp); + ksba_free (sexp); + es_putc ('\n', fp); + print_names_raw (fp, -15, name); + ksba_name_release (name); + } + if (keyid) + { + es_fputs (" authKeyId.ki: ", fp); + gpgsm_print_serial (fp, keyid); + ksba_free (keyid); + es_putc ('\n', fp); + } + } + else + es_fputs ("[?]\n", fp); + + es_fputs (" keyUsage:", fp); + err = ksba_cert_get_key_usage (cert, &kusage); + if (gpg_err_code (err) != GPG_ERR_NO_DATA) + { + if (err) + es_fprintf (fp, " [error: %s]", gpg_strerror (err)); + else + { + if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE)) + es_fputs (" digitalSignature", fp); + if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION)) + es_fputs (" nonRepudiation", fp); + if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT)) + es_fputs (" keyEncipherment", fp); + if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT)) + es_fputs (" dataEncipherment", fp); + if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT)) + es_fputs (" keyAgreement", fp); + if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN)) + es_fputs (" certSign", fp); + if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN)) + es_fputs (" crlSign", fp); + if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY)) + es_fputs (" encipherOnly", fp); + if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY)) + es_fputs (" decipherOnly", fp); + } + es_putc ('\n', fp); + } + else + es_fputs (" [none]\n", fp); + + es_fputs (" extKeyUsage: ", fp); + err = ksba_cert_get_ext_key_usages (cert, &string); + if (gpg_err_code (err) != GPG_ERR_NO_DATA) + { + if (err) + es_fprintf (fp, "[error: %s]", gpg_strerror (err)); + else + { + p = string; + while (p && (pend=strchr (p, ':'))) + { + *pend++ = 0; + for (i=0; key_purpose_map[i].oid; i++) + if ( !strcmp (key_purpose_map[i].oid, p) ) + break; + es_fputs (key_purpose_map[i].oid?key_purpose_map[i].name:p, fp); + p = pend; + if (*p != 'C') + es_fputs (" (suggested)", fp); + if ((p = strchr (p, '\n'))) + { + p++; + es_fputs ("\n ", fp); + } + } + xfree (string); + } + es_putc ('\n', fp); + } + else + es_fputs ("[none]\n", fp); + + + es_fputs (" policies: ", fp); + err = ksba_cert_get_cert_policies (cert, &string); + if (gpg_err_code (err) != GPG_ERR_NO_DATA) + { + if (err) + es_fprintf (fp, "[error: %s]", gpg_strerror (err)); + else + { + p = string; + while (p && (pend=strchr (p, ':'))) + { + *pend++ = 0; + for (i=0; key_purpose_map[i].oid; i++) + if ( !strcmp (key_purpose_map[i].oid, p) ) + break; + es_fputs (p, fp); + p = pend; + if (*p == 'C') + es_fputs (" (critical)", fp); + if ((p = strchr (p, '\n'))) + { + p++; + es_fputs ("\n ", fp); + } + } + xfree (string); + } + es_putc ('\n', fp); + } + else + es_fputs ("[none]\n", fp); + + es_fputs (" chainLength: ", fp); + err = ksba_cert_is_ca (cert, &is_ca, &chainlen); + if (err || is_ca) + { + if (gpg_err_code (err) == GPG_ERR_NO_VALUE ) + es_fprintf (fp, "[none]"); + else if (err) + es_fprintf (fp, "[error: %s]", gpg_strerror (err)); + else if (chainlen == -1) + es_fputs ("unlimited", fp); + else + es_fprintf (fp, "%d", chainlen); + es_putc ('\n', fp); + } + else + es_fputs ("not a CA\n", fp); + + + /* CRL distribution point */ + for (idx=0; !(err=ksba_cert_get_crl_dist_point (cert, idx, &name, &name2, + &reason)) ;idx++) + { + es_fputs (" crlDP: ", fp); + print_names_raw (fp, 15, name); + if (reason) + { + es_fputs (" reason: ", fp); + if ( (reason & KSBA_CRLREASON_UNSPECIFIED)) + es_fputs (" unused", fp); + if ( (reason & KSBA_CRLREASON_KEY_COMPROMISE)) + es_fputs (" keyCompromise", fp); + if ( (reason & KSBA_CRLREASON_CA_COMPROMISE)) + es_fputs (" caCompromise", fp); + if ( (reason & KSBA_CRLREASON_AFFILIATION_CHANGED)) + es_fputs (" affiliationChanged", fp); + if ( (reason & KSBA_CRLREASON_SUPERSEDED)) + es_fputs (" superseded", fp); + if ( (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION)) + es_fputs (" cessationOfOperation", fp); + if ( (reason & KSBA_CRLREASON_CERTIFICATE_HOLD)) + es_fputs (" certificateHold", fp); + es_putc ('\n', fp); + } + es_fputs (" issuer: ", fp); + print_names_raw (fp, 23, name2); + ksba_name_release (name); + ksba_name_release (name2); + } + if (err && gpg_err_code (err) != GPG_ERR_EOF + && gpg_err_code (err) != GPG_ERR_NO_VALUE) + es_fputs (" crlDP: [error]\n", fp); + else if (!idx) + es_fputs (" crlDP: [none]\n", fp); + + + /* authorityInfoAccess. */ + for (idx=0; !(err=ksba_cert_get_authority_info_access (cert, idx, &string, + &name)); idx++) + { + es_fputs (" authInfo: ", fp); + s = get_oid_desc (string, NULL); + es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":""); + print_names_raw (fp, -15, name); + ksba_name_release (name); + ksba_free (string); + } + if (err && gpg_err_code (err) != GPG_ERR_EOF + && gpg_err_code (err) != GPG_ERR_NO_VALUE) + es_fputs (" authInfo: [error]\n", fp); + else if (!idx) + es_fputs (" authInfo: [none]\n", fp); + + /* subjectInfoAccess. */ + for (idx=0; !(err=ksba_cert_get_subject_info_access (cert, idx, &string, + &name)); idx++) + { + es_fputs (" subjectInfo: ", fp); + s = get_oid_desc (string, NULL); + es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":""); + print_names_raw (fp, -15, name); + ksba_name_release (name); + ksba_free (string); + } + if (err && gpg_err_code (err) != GPG_ERR_EOF + && gpg_err_code (err) != GPG_ERR_NO_VALUE) + es_fputs (" subjInfo: [error]\n", fp); + else if (!idx) + es_fputs (" subjInfo: [none]\n", fp); + + + for (idx=0; !(err=ksba_cert_get_extension (cert, idx, + &oid, &i, &off, &len));idx++) + { + unsigned int flag; + + s = get_oid_desc (oid, &flag); + if ((flag & OID_FLAG_SKIP)) + continue; + + es_fprintf (fp, " %s: %s%s%s%s [%d octets]\n", + i? "critExtn":" extn", + oid, s?" (":"", s?s:"", s?")":"", (int)len); + if ((flag & OID_FLAG_UTF8)) + { + if (!cert_der) + cert_der = ksba_cert_get_image (cert, NULL); + assert (cert_der); + print_utf8_extn_raw (fp, -15, cert_der+off, len); + } + } + + + if (with_validation) + { + err = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, fp, 0, NULL); + if (!err) + es_fprintf (fp, " [certificate is good]\n"); + else + es_fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err)); + } + + if (hd) + { + unsigned int blobflags; + + err = keydb_get_flags (hd, KEYBOX_FLAG_BLOB, 0, &blobflags); + if (err) + es_fprintf (fp, " [error getting keyflags: %s]\n",gpg_strerror (err)); + else if ((blobflags & KEYBOX_FLAG_BLOB_EPHEMERAL)) + es_fprintf (fp, " [stored as ephemeral]\n"); + } + +} + + + + +/* List one certificate in standard mode */ +static void +list_cert_std (ctrl_t ctrl, ksba_cert_t cert, estream_t fp, int have_secret, + int with_validation) +{ + gpg_error_t err; + ksba_sexp_t sexp; + char *dn; + ksba_isotime_t t; + int idx, i; + int is_ca, chainlen; + unsigned int kusage; + char *string, *p, *pend; + size_t off, len; + const char *oid; + const unsigned char *cert_der = NULL; + + + es_fprintf (fp, " ID: 0x%08lX\n", + gpgsm_get_short_fingerprint (cert, NULL)); + + sexp = ksba_cert_get_serial (cert); + es_fputs (" S/N: ", fp); + gpgsm_print_serial (fp, sexp); + ksba_free (sexp); + es_putc ('\n', fp); + + dn = ksba_cert_get_issuer (cert, 0); + es_fputs (" Issuer: ", fp); + gpgsm_es_print_name (fp, dn); + ksba_free (dn); + es_putc ('\n', fp); + for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++) + { + es_fputs (" aka: ", fp); + gpgsm_es_print_name (fp, dn); + ksba_free (dn); + es_putc ('\n', fp); + } + + dn = ksba_cert_get_subject (cert, 0); + es_fputs (" Subject: ", fp); + gpgsm_es_print_name (fp, dn); + ksba_free (dn); + es_putc ('\n', fp); + for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++) + { + es_fputs (" aka: ", fp); + gpgsm_es_print_name (fp, dn); + ksba_free (dn); + es_putc ('\n', fp); + } + + ksba_cert_get_validity (cert, 0, t); + es_fputs (" validity: ", fp); + gpgsm_print_time (fp, t); + es_fputs (" through ", fp); + ksba_cert_get_validity (cert, 1, t); + gpgsm_print_time (fp, t); + es_putc ('\n', fp); + + + { + const char *algoname; + unsigned int nbits; + + algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits)); + es_fprintf (fp, " key type: %u bit %s\n", + nbits, algoname? algoname:"?"); + } + + + err = ksba_cert_get_key_usage (cert, &kusage); + if (gpg_err_code (err) != GPG_ERR_NO_DATA) + { + es_fputs (" key usage:", fp); + if (err) + es_fprintf (fp, " [error: %s]", gpg_strerror (err)); + else + { + if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE)) + es_fputs (" digitalSignature", fp); + if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION)) + es_fputs (" nonRepudiation", fp); + if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT)) + es_fputs (" keyEncipherment", fp); + if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT)) + es_fputs (" dataEncipherment", fp); + if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT)) + es_fputs (" keyAgreement", fp); + if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN)) + es_fputs (" certSign", fp); + if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN)) + es_fputs (" crlSign", fp); + if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY)) + es_fputs (" encipherOnly", fp); + if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY)) + es_fputs (" decipherOnly", fp); + } + es_putc ('\n', fp); + } + + err = ksba_cert_get_ext_key_usages (cert, &string); + if (gpg_err_code (err) != GPG_ERR_NO_DATA) + { + es_fputs ("ext key usage: ", fp); + if (err) + es_fprintf (fp, "[error: %s]", gpg_strerror (err)); + else + { + p = string; + while (p && (pend=strchr (p, ':'))) + { + *pend++ = 0; + for (i=0; key_purpose_map[i].oid; i++) + if ( !strcmp (key_purpose_map[i].oid, p) ) + break; + es_fputs (key_purpose_map[i].oid?key_purpose_map[i].name:p, fp); + p = pend; + if (*p != 'C') + es_fputs (" (suggested)", fp); + if ((p = strchr (p, '\n'))) + { + p++; + es_fputs (", ", fp); + } + } + xfree (string); + } + es_putc ('\n', fp); + } + + /* Print restrictions. */ + for (idx=0; !(err=ksba_cert_get_extension (cert, idx, + &oid, NULL, &off, &len));idx++) + { + if (!strcmp (oid, OIDSTR_restriction) ) + { + if (!cert_der) + cert_der = ksba_cert_get_image (cert, NULL); + assert (cert_der); + es_fputs (" restriction: ", fp); + print_utf8_extn (fp, 15, cert_der+off, len); + } + } + + /* Print policies. */ + err = ksba_cert_get_cert_policies (cert, &string); + if (gpg_err_code (err) != GPG_ERR_NO_DATA) + { + es_fputs (" policies: ", fp); + if (err) + es_fprintf (fp, "[error: %s]", gpg_strerror (err)); + else + { + for (p=string; *p; p++) + { + if (*p == '\n') + *p = ','; + } + es_write_sanitized (fp, string, strlen (string), NULL, NULL); + xfree (string); + } + es_putc ('\n', fp); + } + + err = ksba_cert_is_ca (cert, &is_ca, &chainlen); + if (err || is_ca) + { + es_fputs (" chain length: ", fp); + if (gpg_err_code (err) == GPG_ERR_NO_VALUE ) + es_fprintf (fp, "none"); + else if (err) + es_fprintf (fp, "[error: %s]", gpg_strerror (err)); + else if (chainlen == -1) + es_fputs ("unlimited", fp); + else + es_fprintf (fp, "%d", chainlen); + es_putc ('\n', fp); + } + + if (opt.with_md5_fingerprint) + { + dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5); + es_fprintf (fp, " md5 fpr: %s\n", dn?dn:"error"); + xfree (dn); + } + + dn = gpgsm_get_fingerprint_string (cert, 0); + es_fprintf (fp, " fingerprint: %s\n", dn?dn:"error"); + xfree (dn); + + if (have_secret) + { + char *cardsn; + + p = gpgsm_get_keygrip_hexstring (cert); + if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn) && cardsn) + es_fprintf (fp, " card s/n: %s\n", cardsn); + xfree (cardsn); + xfree (p); + } + + if (with_validation) + { + gpg_error_t tmperr; + size_t buflen; + char buffer[1]; + + err = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, fp, 0, NULL); + tmperr = ksba_cert_get_user_data (cert, "is_qualified", + &buffer, sizeof (buffer), &buflen); + if (!tmperr && buflen) + { + if (*buffer) + es_fputs (" [qualified]\n", fp); + } + else if (gpg_err_code (tmperr) == GPG_ERR_NOT_FOUND) + ; /* Don't know - will not get marked as 'q' */ + else + log_debug ("get_user_data(is_qualified) failed: %s\n", + gpg_strerror (tmperr)); + + if (!err) + es_fprintf (fp, " [certificate is good]\n"); + else + es_fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err)); + } +} + + +/* Same as standard mode mode list all certifying certs too. */ +static void +list_cert_chain (ctrl_t ctrl, KEYDB_HANDLE hd, + ksba_cert_t cert, int raw_mode, + estream_t fp, int with_validation) +{ + ksba_cert_t next = NULL; + + if (raw_mode) + list_cert_raw (ctrl, hd, cert, fp, 0, with_validation); + else + list_cert_std (ctrl, cert, fp, 0, with_validation); + ksba_cert_ref (cert); + while (!gpgsm_walk_cert_chain (ctrl, cert, &next)) + { + ksba_cert_release (cert); + es_fputs ("Certified by\n", fp); + if (raw_mode) + list_cert_raw (ctrl, hd, next, fp, 0, with_validation); + else + list_cert_std (ctrl, next, fp, 0, with_validation); + cert = next; + } + ksba_cert_release (cert); + es_putc ('\n', fp); +} + + + +/* List all internal keys or just the keys given as NAMES. MODE is a + bit vector to specify what keys are to be included; see + gpgsm_list_keys (below) for details. If RAW_MODE is true, the raw + output mode will be used instead of the standard beautified one. + */ +static gpg_error_t +list_internal_keys (ctrl_t ctrl, strlist_t names, estream_t fp, + unsigned int mode, int raw_mode) +{ + KEYDB_HANDLE hd; + KEYDB_SEARCH_DESC *desc = NULL; + strlist_t sl; + int ndesc; + ksba_cert_t cert = NULL; + ksba_cert_t lastcert = NULL; + gpg_error_t rc = 0; + const char *lastresname, *resname; + int have_secret; + int want_ephemeral = ctrl->with_ephemeral_keys; + + hd = keydb_new (0); + if (!hd) + { + log_error ("keydb_new failed\n"); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + if (!names) + ndesc = 1; + else + { + for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) + ; + } + + desc = xtrycalloc (ndesc, sizeof *desc); + if (!ndesc) + { + rc = gpg_error_from_syserror (); + log_error ("out of core\n"); + goto leave; + } + + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_FIRST; + else + { + for (ndesc=0, sl=names; sl; sl = sl->next) + { + rc = keydb_classify_name (sl->d, desc+ndesc); + if (rc) + { + log_error ("key `%s' not found: %s\n", + sl->d, gpg_strerror (rc)); + rc = 0; + } + else + ndesc++; + } + + } + + /* If all specifications are done by fingerprint or keygrip, we + switch to ephemeral mode so that _all_ currently available and + matching certificates are listed. */ + if (!want_ephemeral && names && ndesc) + { + int i; + + for (i=0; (i < ndesc + && (desc[i].mode == KEYDB_SEARCH_MODE_FPR + || desc[i].mode == KEYDB_SEARCH_MODE_FPR20 + || desc[i].mode == KEYDB_SEARCH_MODE_FPR16 + || desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++) + ; + if (i == ndesc) + want_ephemeral = 1; + } + + if (want_ephemeral) + keydb_set_ephemeral (hd, 1); + + /* It would be nice to see which of the given users did actually + match one in the keyring. To implement this we need to have a + found flag for each entry in desc and to set this we must check + all those entries after a match to mark all matched one - + currently we stop at the first match. To do this we need an + extra flag to enable this feature so */ + + /* Suppress duplicates at least when they follow each other. */ + lastresname = NULL; + while (!(rc = keydb_search (hd, desc, ndesc))) + { + unsigned int validity; + + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_NEXT; + + rc = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &validity); + if (rc) + { + log_error ("keydb_get_flags failed: %s\n", gpg_strerror (rc)); + goto leave; + } + rc = keydb_get_cert (hd, &cert); + if (rc) + { + log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); + goto leave; + } + /* Skip duplicated certificates, at least if they follow each + others. This works best if a single key is searched for and + expected. FIXME: Non-sequential duplicates remain. */ + if (gpgsm_certs_identical_p (cert, lastcert)) + { + ksba_cert_release (cert); + cert = NULL; + continue; + } + + resname = keydb_get_resource_name (hd); + + if (lastresname != resname ) + { + int i; + + if (ctrl->no_server) + { + es_fprintf (fp, "%s\n", resname ); + for (i=strlen(resname); i; i-- ) + es_putc ('-', fp); + es_putc ('\n', fp); + lastresname = resname; + } + } + + have_secret = 0; + if (mode) + { + char *p = gpgsm_get_keygrip_hexstring (cert); + if (p) + { + rc = gpgsm_agent_havekey (ctrl, p); + if (!rc) + have_secret = 1; + else if ( gpg_err_code (rc) != GPG_ERR_NO_SECKEY) + goto leave; + rc = 0; + xfree (p); + } + } + + if (!mode + || ((mode & 1) && !have_secret) + || ((mode & 2) && have_secret) ) + { + if (ctrl->with_colons) + list_cert_colon (ctrl, cert, validity, fp, have_secret); + else if (ctrl->with_chain) + list_cert_chain (ctrl, hd, cert, + raw_mode, fp, ctrl->with_validation); + else + { + if (raw_mode) + list_cert_raw (ctrl, hd, cert, fp, have_secret, + ctrl->with_validation); + else + list_cert_std (ctrl, cert, fp, have_secret, + ctrl->with_validation); + es_putc ('\n', fp); + } + } + + ksba_cert_release (lastcert); + lastcert = cert; + cert = NULL; + } + if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1 ) + rc = 0; + if (rc) + log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); + + leave: + ksba_cert_release (cert); + ksba_cert_release (lastcert); + xfree (desc); + keydb_release (hd); + return rc; +} + + + +static void +list_external_cb (void *cb_value, ksba_cert_t cert) +{ + struct list_external_parm_s *parm = cb_value; + + if (keydb_store_cert (cert, 1, NULL)) + log_error ("error storing certificate as ephemeral\n"); + + if (parm->print_header) + { + const char *resname = "[external keys]"; + int i; + + es_fprintf (parm->fp, "%s\n", resname ); + for (i=strlen(resname); i; i-- ) + es_putc('-', parm->fp); + es_putc ('\n', parm->fp); + parm->print_header = 0; + } + + if (parm->with_colons) + list_cert_colon (parm->ctrl, cert, 0, parm->fp, 0); + else if (parm->with_chain) + list_cert_chain (parm->ctrl, NULL, cert, parm->raw_mode, parm->fp, 0); + else + { + if (parm->raw_mode) + list_cert_raw (parm->ctrl, NULL, cert, parm->fp, 0, 0); + else + list_cert_std (parm->ctrl, cert, parm->fp, 0, 0); + es_putc ('\n', parm->fp); + } +} + + +/* List external keys similar to internal one. Note: mode does not + make sense here because it would be unwise to list external secret + keys */ +static gpg_error_t +list_external_keys (ctrl_t ctrl, strlist_t names, estream_t fp, int raw_mode) +{ + int rc; + struct list_external_parm_s parm; + + parm.fp = fp; + parm.ctrl = ctrl, + parm.print_header = ctrl->no_server; + parm.with_colons = ctrl->with_colons; + parm.with_chain = ctrl->with_chain; + parm.raw_mode = raw_mode; + + rc = gpgsm_dirmngr_lookup (ctrl, names, 0, list_external_cb, &parm); + if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1 + || gpg_err_code (rc) == GPG_ERR_NOT_FOUND) + rc = 0; /* "Not found" is not an error here. */ + if (rc) + log_error ("listing external keys failed: %s\n", gpg_strerror (rc)); + return rc; +} + +/* List all keys or just the key given as NAMES. + MODE controls the operation mode: + Bit 0-2: + 0 = list all public keys but don't flag secret ones + 1 = list only public keys + 2 = list only secret keys + 3 = list secret and public keys + Bit 6: list internal keys + Bit 7: list external keys + Bit 8: Do a raw format dump. + */ +gpg_error_t +gpgsm_list_keys (ctrl_t ctrl, strlist_t names, estream_t fp, + unsigned int mode) +{ + gpg_error_t err = 0; + + if ((mode & (1<<6))) + err = list_internal_keys (ctrl, names, fp, (mode & 3), (mode&256)); + if (!err && (mode & (1<<7))) + err = list_external_keys (ctrl, names, fp, (mode&256)); + return err; +} diff --git a/sm/misc.c b/sm/misc.c new file mode 100644 index 0000000..628b321 --- /dev/null +++ b/sm/misc.c @@ -0,0 +1,89 @@ +/* misc.c - Miscellaneous fucntions + * Copyright (C) 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 <http://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> +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif + +#include "gpgsm.h" +#include "i18n.h" +#include "setenv.h" + +/* Setup the environment so that the pinentry is able to get all + required information. This is used prior to an exec of the + protect-tool. */ +void +setup_pinentry_env (void) +{ +#ifndef HAVE_W32_SYSTEM + char *lc; + const char *name, *value; + int iterator; + + /* Try to make sure that GPG_TTY has been set. This is needed if we + call for example the protect-tools with redirected stdin and thus + it won't be able to ge a default by itself. Try to do it here + but print a warning. */ + value = session_env_getenv (opt.session_env, "GPG_TTY"); + if (value) + setenv ("GPG_TTY", value, 1); + else if (!(lc=getenv ("GPG_TTY")) || !*lc) + { + log_error (_("GPG_TTY has not been set - " + "using maybe bogus default\n")); + lc = ttyname (0); + if (!lc) + lc = "/dev/tty"; + setenv ("GPG_TTY", lc, 1); + } + + if (opt.lc_ctype) + setenv ("LC_CTYPE", opt.lc_ctype, 1); +#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) + else if ( (lc = setlocale (LC_CTYPE, "")) ) + setenv ("LC_CTYPE", lc, 1); +#endif + + if (opt.lc_messages) + setenv ("LC_MESSAGES", opt.lc_messages, 1); +#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) + else if ( (lc = setlocale (LC_MESSAGES, "")) ) + setenv ("LC_MESSAGES", lc, 1); +#endif + + iterator = 0; + while ((name = session_env_list_stdenvnames (&iterator, NULL))) + { + if (!strcmp (name, "GPG_TTY")) + continue; /* Already set. */ + value = session_env_getenv (opt.session_env, name); + if (value) + setenv (name, value, 1); + } + +#endif /*!HAVE_W32_SYSTEM*/ +} + diff --git a/sm/qualified.c b/sm/qualified.c new file mode 100644 index 0000000..d0db481 --- /dev/null +++ b/sm/qualified.c @@ -0,0 +1,321 @@ +/* qualified.c - Routines related to qualified signatures + * Copyright (C) 2005, 2007 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <assert.h> +#include <errno.h> + +#include "gpgsm.h" +#include "i18n.h" +#include <ksba.h> + + +/* We open the file only once and keep the open file pointer as well + as the name of the file here. Note that, a listname not equal to + NULL indicates that this module has been intialized and if the + LISTFP is also NULL, no list of qualified signatures exists. */ +static char *listname; +static FILE *listfp; + + +/* Read the trustlist and return entry by entry. KEY must point to a + buffer of at least 41 characters. COUNTRY shall be a buffer of at + least 3 characters to receive the country code of that qualified + signature (i.e. "de" for German and "be" for Belgium). + + Reading a valid entry returns 0, EOF is indicated by GPG_ERR_EOF + and any other error condition is indicated by the appropriate error + code. */ +static gpg_error_t +read_list (char *key, char *country, int *lnr) +{ + gpg_error_t err; + int c, i, j; + char *p, line[256]; + + *key = 0; + *country = 0; + + if (!listname) + { + listname = make_filename (gnupg_datadir (), "qualified.txt", NULL); + listfp = fopen (listname, "r"); + if (!listfp && errno != ENOENT) + { + err = gpg_error_from_syserror (); + log_error (_("can't open `%s': %s\n"), listname, gpg_strerror (err)); + return err; + } + } + + if (!listfp) + return gpg_error (GPG_ERR_EOF); + + do + { + if (!fgets (line, DIM(line)-1, listfp) ) + { + if (feof (listfp)) + return gpg_error (GPG_ERR_EOF); + return gpg_error_from_syserror (); + } + + if (!*line || line[strlen(line)-1] != '\n') + { + /* Eat until end of line. */ + while ( (c=getc (listfp)) != EOF && c != '\n') + ; + return gpg_error (*line? GPG_ERR_LINE_TOO_LONG + : GPG_ERR_INCOMPLETE_LINE); + } + ++*lnr; + + /* Allow for empty lines and spaces */ + for (p=line; spacep (p); p++) + ; + } + while (!*p || *p == '\n' || *p == '#'); + + for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++) + if ( p[i] != ':' ) + key[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i]; + key[j] = 0; + if (j != 40 || !(spacep (p+i) || p[i] == '\n')) + { + log_error (_("invalid formatted fingerprint in `%s', line %d\n"), + listname, *lnr); + return gpg_error (GPG_ERR_BAD_DATA); + } + assert (p[i]); + i++; + while (spacep (p+i)) + i++; + if ( p[i] >= 'a' && p[i] <= 'z' + && p[i+1] >= 'a' && p[i+1] <= 'z' + && (spacep (p+i+2) || p[i+2] == '\n')) + { + country[0] = p[i]; + country[1] = p[i+1]; + country[2] = 0; + } + else + { + log_error (_("invalid country code in `%s', line %d\n"), listname, *lnr); + return gpg_error (GPG_ERR_BAD_DATA); + } + + return 0; +} + + + + +/* Check whether the certificate CERT is included in the list of + qualified certificates. This list is similar to the "trustlist.txt" + as maintained by gpg-agent and includes fingerprints of root + certificates to be used for qualified (legally binding like + handwritten) signatures. We keep this list system wide and not + per user because it is not a decision of the user. + + Returns: 0 if the certificate is included. GPG_ERR_NOT_FOUND if it + is not in the list or any other error (e.g. if no list of + qualified signatures is available. If COUNTRY has not been passed + as NULL a string witha maximum length of 2 will be copied into it; + thus the caller needs to provide a buffer of length 3. */ +gpg_error_t +gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert, char *country) +{ + gpg_error_t err; + char *fpr; + char key[41]; + char mycountry[3]; + int lnr = 0; + + (void)ctrl; + + if (country) + *country = 0; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + if (!fpr) + return gpg_error (GPG_ERR_GENERAL); + + if (listfp) + rewind (listfp); + while (!(err = read_list (key, mycountry, &lnr))) + { + if (!strcmp (key, fpr)) + break; + } + if (gpg_err_code (err) == GPG_ERR_EOF) + err = gpg_error (GPG_ERR_NOT_FOUND); + + if (!err && country) + strcpy (country, mycountry); + + xfree (fpr); + return err; +} + + +/* We know that CERT is a qualified certificate. Ask the user for + consent to actually create a signature using this certificate. + Returns: 0 for yes, GPG_ERR_CANCEL for no or any otehr error + code. */ +gpg_error_t +gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert) +{ + gpg_error_t err; + char *name, *subject, *buffer, *p; + const char *s; + char *orig_codeset = NULL; + + name = ksba_cert_get_subject (cert, 0); + if (!name) + return gpg_error (GPG_ERR_GENERAL); + subject = gpgsm_format_name2 (name, 0); + ksba_free (name); name = NULL; + + orig_codeset = i18n_switchto_utf8 (); + + if (asprintf (&name, + _("You are about to create a signature using your " + "certificate:\n" + "\"%s\"\n" + "This will create a qualified signature by law " + "equated to a handwritten signature.\n\n%s%s" + "Are you really sure that you want to do this?"), + subject? subject:"?", + opt.qualsig_approval? + "": + _("Note, that this software is not officially approved " + "to create or verify such signatures.\n"), + opt.qualsig_approval? "":"\n" + ) < 0 ) + err = gpg_error_from_syserror (); + else + err = 0; + + i18n_switchback (orig_codeset); + xfree (subject); + + if (err) + return err; + + buffer = p = xtrymalloc (strlen (name) * 3 + 1); + if (!buffer) + { + err = gpg_error_from_syserror (); + free (name); + return err; + } + for (s=name; *s; s++) + { + if (*s < ' ' || *s == '+') + { + sprintf (p, "%%%02X", *(unsigned char *)s); + p += 3; + } + else if (*s == ' ') + *p++ = '+'; + else + *p++ = *s; + } + *p = 0; + free (name); + + + err = gpgsm_agent_get_confirmation (ctrl, buffer); + + xfree (buffer); + return err; +} + + +/* Popup a prompt to inform the user that the signature created is not + a qualified one. This is of course only done if we know that we + have been approved. */ +gpg_error_t +gpgsm_not_qualified_warning (ctrl_t ctrl, ksba_cert_t cert) +{ + gpg_error_t err; + char *name, *subject, *buffer, *p; + const char *s; + char *orig_codeset; + + if (!opt.qualsig_approval) + return 0; + + name = ksba_cert_get_subject (cert, 0); + if (!name) + return gpg_error (GPG_ERR_GENERAL); + subject = gpgsm_format_name2 (name, 0); + ksba_free (name); name = NULL; + + orig_codeset = i18n_switchto_utf8 (); + + if (asprintf (&name, + _("You are about to create a signature using your " + "certificate:\n" + "\"%s\"\n" + "Note, that this certificate will NOT create a " + "qualified signature!"), + subject? subject:"?") < 0 ) + err = gpg_error_from_syserror (); + else + err = 0; + + i18n_switchback (orig_codeset); + xfree (subject); + + if (err) + return err; + + buffer = p = xtrymalloc (strlen (name) * 3 + 1); + if (!buffer) + { + err = gpg_error_from_syserror (); + free (name); + return err; + } + for (s=name; *s; s++) + { + if (*s < ' ' || *s == '+') + { + sprintf (p, "%%%02X", *(unsigned char *)s); + p += 3; + } + else if (*s == ' ') + *p++ = '+'; + else + *p++ = *s; + } + *p = 0; + free (name); + + + err = gpgsm_agent_get_confirmation (ctrl, buffer); + + xfree (buffer); + return err; +} diff --git a/sm/server.c b/sm/server.c new file mode 100644 index 0000000..fcf47a7 --- /dev/null +++ b/sm/server.c @@ -0,0 +1,1464 @@ +/* server.c - Server mode and main entry point + * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, + * 2009, 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <ctype.h> +#include <unistd.h> + +#include "gpgsm.h" +#include <assuan.h> +#include "sysutils.h" + +#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) + + +/* The filepointer for status message used in non-server mode */ +static FILE *statusfp; + +/* Data used to assuciate an Assuan context with local server data */ +struct server_local_s { + assuan_context_t assuan_ctx; + int message_fd; + int list_internal; + int list_external; + int list_to_output; /* Write keylistings to the output fd. */ + int enable_audit_log; /* Use an audit log. */ + certlist_t recplist; + certlist_t signerlist; + certlist_t default_recplist; /* As set by main() - don't release. */ + int allow_pinentry_notify; /* Set if pinentry notifications should + be passed back to the client. */ + int no_encrypt_to; /* Local version of option. */ +}; + + +/* Cookie definition for assuan data line output. */ +static ssize_t data_line_cookie_write (void *cookie, + const void *buffer, size_t size); +static int data_line_cookie_close (void *cookie); +static es_cookie_io_functions_t data_line_cookie_functions = + { + NULL, + data_line_cookie_write, + NULL, + data_line_cookie_close + }; + + + +static int command_has_option (const char *cmd, const char *cmdopt); + + + + +/* Note that it is sufficient to allocate the target string D as + long as the source string S, i.e.: strlen(s)+1; */ +static void +strcpy_escaped_plus (char *d, const char *s) +{ + while (*s) + { + if (*s == '%' && s[1] && s[2]) + { + s++; + *d++ = xtoi_2 (s); + s += 2; + } + else if (*s == '+') + *d++ = ' ', s++; + else + *d++ = *s++; + } + *d = 0; +} + + +/* Skip over options. + Blanks after the options are also removed. */ +static char * +skip_options (const char *line) +{ + while (spacep (line)) + line++; + while ( *line == '-' && line[1] == '-' ) + { + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + } + return (char*)line; +} + + +/* Check whether the option NAME appears in LINE */ +static int +has_option (const char *line, const char *name) +{ + const char *s; + int n = strlen (name); + + s = strstr (line, name); + if (s && s >= skip_options (line)) + return 0; + return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); +} + + +/* A write handler used by es_fopencookie to write assuan data + lines. */ +static ssize_t +data_line_cookie_write (void *cookie, const void *buffer, size_t size) +{ + assuan_context_t ctx = cookie; + + if (assuan_send_data (ctx, buffer, size)) + { + errno = EIO; + return -1; + } + + return size; +} + +static int +data_line_cookie_close (void *cookie) +{ + assuan_context_t ctx = cookie; + + if (assuan_send_data (ctx, NULL, 0)) + { + errno = EIO; + return -1; + } + + return 0; +} + + +static void +close_message_fd (ctrl_t ctrl) +{ + if (ctrl->server_local->message_fd != -1) + { + close (ctrl->server_local->message_fd); + ctrl->server_local->message_fd = -1; + } +} + + +/* Start a new audit session if this has been enabled. */ +static gpg_error_t +start_audit_session (ctrl_t ctrl) +{ + audit_release (ctrl->audit); + ctrl->audit = NULL; + if (ctrl->server_local->enable_audit_log && !(ctrl->audit = audit_new ()) ) + return gpg_error_from_syserror (); + + return 0; +} + + +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, "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 (opt.session_env, value); + } + else if (!strcmp (key, "display")) + { + err = session_env_setenv (opt.session_env, "DISPLAY", value); + } + else if (!strcmp (key, "ttyname")) + { + err = session_env_setenv (opt.session_env, "GPG_TTY", value); + } + else if (!strcmp (key, "ttytype")) + { + err = session_env_setenv (opt.session_env, "TERM", value); + } + else if (!strcmp (key, "lc-ctype")) + { + xfree (opt.lc_ctype); + opt.lc_ctype = xtrystrdup (value); + if (!opt.lc_ctype) + err = gpg_error_from_syserror (); + } + else if (!strcmp (key, "lc-messages")) + { + xfree (opt.lc_messages); + opt.lc_messages = xtrystrdup (value); + if (!opt.lc_messages) + err = gpg_error_from_syserror (); + } + else if (!strcmp (key, "xauthority")) + { + err = session_env_setenv (opt.session_env, "XAUTHORITY", value); + } + else if (!strcmp (key, "pinentry-user-data")) + { + err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value); + } + else if (!strcmp (key, "include-certs")) + { + int i = *value? atoi (value) : -1; + if (ctrl->include_certs < -2) + err = gpg_error (GPG_ERR_ASS_PARAMETER); + else + ctrl->include_certs = i; + } + else if (!strcmp (key, "list-mode")) + { + int i = *value? atoi (value) : 0; + if (!i || i == 1) /* default and mode 1 */ + { + ctrl->server_local->list_internal = 1; + ctrl->server_local->list_external = 0; + } + else if (i == 2) + { + ctrl->server_local->list_internal = 0; + ctrl->server_local->list_external = 1; + } + else if (i == 3) + { + ctrl->server_local->list_internal = 1; + ctrl->server_local->list_external = 1; + } + else + err = gpg_error (GPG_ERR_ASS_PARAMETER); + } + else if (!strcmp (key, "list-to-output")) + { + int i = *value? atoi (value) : 0; + ctrl->server_local->list_to_output = i; + } + else if (!strcmp (key, "with-validation")) + { + int i = *value? atoi (value) : 0; + ctrl->with_validation = i; + } + else if (!strcmp (key, "validation-model")) + { + int i = gpgsm_parse_validation_model (value); + if ( i >= 0 && i <= 1 ) + ctrl->validation_model = i; + else + err = gpg_error (GPG_ERR_ASS_PARAMETER); + } + else if (!strcmp (key, "with-key-data")) + { + opt.with_key_data = 1; + } + else if (!strcmp (key, "enable-audit-log")) + { + int i = *value? atoi (value) : 0; + ctrl->server_local->enable_audit_log = i; + } + else if (!strcmp (key, "allow-pinentry-notify")) + { + ctrl->server_local->allow_pinentry_notify = 1; + } + else if (!strcmp (key, "with-ephemeral-keys")) + { + int i = *value? atoi (value) : 0; + ctrl->with_ephemeral_keys = i; + } + else if (!strcmp (key, "no-encrypt-to")) + { + ctrl->server_local->no_encrypt_to = 1; + } + else + err = gpg_error (GPG_ERR_UNKNOWN_OPTION); + + return err; +} + + +static gpg_error_t +reset_notify (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + (void) line; + + gpgsm_release_certlist (ctrl->server_local->recplist); + gpgsm_release_certlist (ctrl->server_local->signerlist); + ctrl->server_local->recplist = NULL; + ctrl->server_local->signerlist = NULL; + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + return 0; +} + + +static gpg_error_t +input_notify (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + ctrl->autodetect_encoding = 0; + ctrl->is_pem = 0; + ctrl->is_base64 = 0; + if (strstr (line, "--armor")) + ctrl->is_pem = 1; + else if (strstr (line, "--base64")) + ctrl->is_base64 = 1; + else if (strstr (line, "--binary")) + ; + else + ctrl->autodetect_encoding = 1; + return 0; +} + +static gpg_error_t +output_notify (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + ctrl->create_pem = 0; + ctrl->create_base64 = 0; + if (strstr (line, "--armor")) + ctrl->create_pem = 1; + else if (strstr (line, "--base64")) + ctrl->create_base64 = 1; /* just the raw output */ + return 0; +} + + +static const char hlp_recipient[] = + "RECIPIENT <userID>\n" + "\n" + "Set the recipient for the encryption. USERID shall be the\n" + "internal representation of the key; the server may accept any other\n" + "way of specification [we will support this]. If this is a valid and\n" + "trusted recipient the server does respond with OK, otherwise the\n" + "return is an ERR with the reason why the recipient can't be used,\n" + "the encryption will then not be done for this recipient. If the\n" + "policy is not to encrypt at all if not all recipients are valid, the\n" + "client has to take care of this. All RECIPIENT commands are\n" + "cumulative until a RESET or an successful ENCRYPT command."; +static gpg_error_t +cmd_recipient (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + + if (!ctrl->audit) + rc = start_audit_session (ctrl); + else + rc = 0; + + if (!rc) + rc = gpgsm_add_to_certlist (ctrl, line, 0, + &ctrl->server_local->recplist, 0); + if (rc) + { + gpgsm_status2 (ctrl, STATUS_INV_RECP, + get_inv_recpsgnr_code (rc), line, NULL); + } + + return rc; +} + + +static const char hlp_signer[] = + "SIGNER <userID>\n" + "\n" + "Set the signer's keys for the signature creation. USERID should\n" + "be the internal representation of the key; the server may accept any\n" + "other way of specification [we will support this]. If this is a\n" + "valid and usable signing key the server does respond with OK,\n" + "otherwise it returns an ERR with the reason why the key can't be\n" + "used, the signing will then not be done for this key. If the policy\n" + "is not to sign at all if not all signer keys are valid, the client\n" + "has to take care of this. All SIGNER commands are cumulative until\n" + "a RESET but they are *not* reset by an SIGN command becuase it can\n" + "be expected that set of signers are used for more than one sign\n" + "operation."; +static gpg_error_t +cmd_signer (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + + rc = gpgsm_add_to_certlist (ctrl, line, 1, + &ctrl->server_local->signerlist, 0); + if (rc) + { + gpgsm_status2 (ctrl, STATUS_INV_SGNR, + get_inv_recpsgnr_code (rc), line, NULL); + /* For compatibiliy reasons we also issue the old code after the + new one. */ + gpgsm_status2 (ctrl, STATUS_INV_RECP, + get_inv_recpsgnr_code (rc), line, NULL); + } + return rc; +} + + +static const char hlp_encrypt[] = + "ENCRYPT \n" + "\n" + "Do the actual encryption process. Takes the plaintext from the INPUT\n" + "command, writes to the ciphertext to the file descriptor set with\n" + "the OUTPUT command, take the recipients form all the recipients set\n" + "so far. If this command fails the clients should try to delete all\n" + "output currently done or otherwise mark it as invalid. GPGSM does\n" + "ensure that there won't be any security problem with leftover data\n" + "on the output in this case.\n" + "\n" + "This command should in general not fail, as all necessary checks\n" + "have been done while setting the recipients. The input and output\n" + "pipes are closed."; +static gpg_error_t +cmd_encrypt (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + certlist_t cl; + int inp_fd, out_fd; + FILE *out_fp; + int rc; + + (void)line; + + inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); + if (inp_fd == -1) + return set_error (GPG_ERR_ASS_NO_INPUT, NULL); + out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); + if (out_fd == -1) + return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); + + out_fp = fdopen (dup (out_fd), "w"); + if (!out_fp) + return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed"); + + /* Now add all encrypt-to marked recipients from the default + list. */ + rc = 0; + if (!opt.no_encrypt_to && !ctrl->server_local->no_encrypt_to) + { + for (cl=ctrl->server_local->default_recplist; !rc && cl; cl = cl->next) + if (cl->is_encrypt_to) + rc = gpgsm_add_cert_to_certlist (ctrl, cl->cert, + &ctrl->server_local->recplist, 1); + } + if (!rc) + rc = ctrl->audit? 0 : start_audit_session (ctrl); + if (!rc) + rc = gpgsm_encrypt (assuan_get_pointer (ctx), + ctrl->server_local->recplist, + inp_fd, out_fp); + fclose (out_fp); + + gpgsm_release_certlist (ctrl->server_local->recplist); + ctrl->server_local->recplist = NULL; + /* Close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + return rc; +} + + +static const char hlp_decrypt[] = + "DECRYPT\n" + "\n" + "This performs the decrypt operation after doing some check on the\n" + "internal state. (e.g. that only needed data has been set). Because\n" + "it utilizes the GPG-Agent for the session key decryption, there is\n" + "no need to ask the client for a protecting passphrase - GPG-Agent\n" + "does take care of this by requesting this from the user."; +static gpg_error_t +cmd_decrypt (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int inp_fd, out_fd; + FILE *out_fp; + int rc; + + (void)line; + + inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); + if (inp_fd == -1) + return set_error (GPG_ERR_ASS_NO_INPUT, NULL); + out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); + if (out_fd == -1) + return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); + + out_fp = fdopen (dup(out_fd), "w"); + if (!out_fp) + return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed"); + + rc = start_audit_session (ctrl); + if (!rc) + rc = gpgsm_decrypt (ctrl, inp_fd, out_fp); + fclose (out_fp); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return rc; +} + + +static const char hlp_verify[] = + "VERIFY\n" + "\n" + "This does a verify operation on the message send to the input FD.\n" + "The result is written out using status lines. If an output FD was\n" + "given, the signed text will be written to that.\n" + "\n" + "If the signature is a detached one, the server will inquire about\n" + "the signed material and the client must provide it."; +static gpg_error_t +cmd_verify (assuan_context_t ctx, char *line) +{ + int rc; + ctrl_t ctrl = assuan_get_pointer (ctx); + int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); + int out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); + FILE *out_fp = NULL; + + (void)line; + + if (fd == -1) + return set_error (GPG_ERR_ASS_NO_INPUT, NULL); + + if (out_fd != -1) + { + out_fp = fdopen ( dup(out_fd), "w"); + if (!out_fp) + return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed"); + } + + rc = start_audit_session (ctrl); + if (!rc) + rc = gpgsm_verify (assuan_get_pointer (ctx), fd, + ctrl->server_local->message_fd, out_fp); + if (out_fp) + fclose (out_fp); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return rc; +} + + +static const char hlp_sign[] = + "SIGN [--detached]\n" + "\n" + "Sign the data set with the INPUT command and write it to the sink\n" + "set by OUTPUT. With \"--detached\", a detached signature is\n" + "created (surprise)."; +static gpg_error_t +cmd_sign (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int inp_fd, out_fd; + FILE *out_fp; + int detached; + int rc; + + inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); + if (inp_fd == -1) + return set_error (GPG_ERR_ASS_NO_INPUT, NULL); + out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); + if (out_fd == -1) + return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); + + detached = has_option (line, "--detached"); + + out_fp = fdopen ( dup(out_fd), "w"); + if (!out_fp) + return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed"); + + rc = start_audit_session (ctrl); + if (!rc) + rc = gpgsm_sign (assuan_get_pointer (ctx), ctrl->server_local->signerlist, + inp_fd, detached, out_fp); + fclose (out_fp); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return rc; +} + + +static const char hlp_import[] = + "IMPORT [--re-import]\n" + "\n" + "Import the certificates read form the input-fd, return status\n" + "message for each imported one. The import checks the validity of\n" + "the certificate but not of the entire chain. It is possible to\n" + "import expired certificates.\n" + "\n" + "With the option --re-import the input data is expected to a be a LF\n" + "separated list of fingerprints. The command will re-import these\n" + "certificates, meaning that they are made permanent by removing\n" + "their ephemeral flag."; +static gpg_error_t +cmd_import (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); + int reimport = has_option (line, "--re-import"); + + (void)line; + + if (fd == -1) + return set_error (GPG_ERR_ASS_NO_INPUT, NULL); + + rc = gpgsm_import (assuan_get_pointer (ctx), fd, reimport); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return rc; +} + + +static const char hlp_export[] = + "EXPORT [--data [--armor|--base64]] [--] <pattern>\n" + "\n" + "Export the certificates selected by PATTERN. With --data the output\n" + "is returned using Assuan D lines; the default is to use the sink given\n" + "by the last \"OUTPUT\" command. The options --armor or --base64 encode \n" + "the output using the PEM respective a plain base-64 format; the default\n" + "is a binary format which is only suitable for a single certificate."; +static gpg_error_t +cmd_export (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + char *p; + strlist_t list, sl; + int use_data; + + use_data = has_option (line, "--data"); + + if (use_data) + { + /* We need to override any possible setting done by an OUTPUT command. */ + ctrl->create_pem = has_option (line, "--armor"); + ctrl->create_base64 = has_option (line, "--base64"); + } + + line = skip_options (line); + + /* Break the line down into an strlist_t. */ + list = NULL; + for (p=line; *p; line = p) + { + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + if (*line) + { + sl = xtrymalloc (sizeof *sl + strlen (line)); + if (!sl) + { + free_strlist (list); + return out_of_core (); + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + if (use_data) + { + estream_t stream; + + stream = es_fopencookie (ctx, "w", data_line_cookie_functions); + if (!stream) + { + free_strlist (list); + return set_error (GPG_ERR_ASS_GENERAL, + "error setting up a data stream"); + } + gpgsm_export (ctrl, list, NULL, stream); + es_fclose (stream); + } + else + { + int fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); + FILE *out_fp; + + if (fd == -1) + { + free_strlist (list); + return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); + } + out_fp = fdopen ( dup(fd), "w"); + if (!out_fp) + { + free_strlist (list); + return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed"); + } + + gpgsm_export (ctrl, list, out_fp, NULL); + fclose (out_fp); + } + + free_strlist (list); + /* Close and reset the fds. */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + return 0; +} + + + +static const char hlp_delkeys[] = + "DELKEYS <patterns>\n" + "\n" + "Delete the certificates specified by PATTERNS. Each pattern shall be\n" + "a percent-plus escaped certificate specification. Usually a\n" + "fingerprint will be used for this."; +static gpg_error_t +cmd_delkeys (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + char *p; + strlist_t list, sl; + int rc; + + /* break the line down into an strlist_t */ + list = NULL; + for (p=line; *p; line = p) + { + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + if (*line) + { + sl = xtrymalloc (sizeof *sl + strlen (line)); + if (!sl) + { + free_strlist (list); + return out_of_core (); + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + rc = gpgsm_delete (ctrl, list); + free_strlist (list); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return rc; +} + + + +static const char hlp_output[] = + "OUTPUT FD[=<n>]\n" + "\n" + "Set the file descriptor to write the output data to N. If N is not\n" + "given and the operating system supports file descriptor passing, the\n" + "file descriptor currently in flight will be used. See also the\n" + "\"INPUT\" and \"MESSAGE\" commands."; +static const char hlp_input[] = + "INPUT FD[=<n>]\n" + "\n" + "Set the file descriptor to read the input data to N. If N is not\n" + "given and the operating system supports file descriptor passing, the\n" + "file descriptor currently in flight will be used. See also the\n" + "\"MESSAGE\" and \"OUTPUT\" commands."; +static const char hlp_message[] = + "MESSAGE FD[=<n>]\n" + "\n" + "Set the file descriptor to read the message for a detached\n" + "signatures to N. If N is not given and the operating system\n" + "supports file descriptor passing, the file descriptor currently in\n" + "flight will be used. See also the \"INPUT\" and \"OUTPUT\" commands."; +static gpg_error_t +cmd_message (assuan_context_t ctx, char *line) +{ + int rc; + gnupg_fd_t sysfd; + int fd; + ctrl_t ctrl = assuan_get_pointer (ctx); + + rc = assuan_command_parse_fd (ctx, line, &sysfd); + if (rc) + return rc; + fd = translate_sys2libc_fd (sysfd, 0); + if (fd == -1) + return set_error (GPG_ERR_ASS_NO_INPUT, NULL); + ctrl->server_local->message_fd = fd; + return 0; +} + + + +static const char hlp_listkeys[] = + "LISTKEYS [<patterns>]\n" + "LISTSECRETKEYS [<patterns>]\n" + "DUMPKEYS [<patterns>]\n" + "DUMPSECRETKEYS [<patterns>]\n" + "\n" + "List all certificates or only those specified by PATTERNS. Each\n" + "pattern shall be a percent-plus escaped certificate specification.\n" + "The \"SECRET\" versions of the command filter the output to include\n" + "only certificates where the secret key is available or a corresponding\n" + "smartcard has been registered. The \"DUMP\" versions of the command\n" + "are only useful for debugging. The output format is a percent escaped\n" + "colon delimited listing as described in the manual.\n" + "\n" + "These \"OPTION\" command keys effect the output::\n" + "\n" + " \"list-mode\" set to 0: List only local certificates (default).\n" + " 1: Ditto.\n" + " 2: List only external certificates.\n" + " 3: List local and external certificates.\n" + "\n" + " \"with-validation\" set to true: Validate each certificate.\n" + "\n" + " \"with-ephemeral-key\" set to true: Always include ephemeral\n" + " certificates.\n" + "\n" + " \"list-to-output\" set to true: Write output to the file descriptor\n" + " given by the last \"OUTPUT\" command."; +static int +do_listkeys (assuan_context_t ctx, char *line, int mode) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + estream_t fp; + char *p; + strlist_t list, sl; + unsigned int listmode; + gpg_error_t err; + + /* Break the line down into an strlist. */ + list = NULL; + for (p=line; *p; line = p) + { + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + if (*line) + { + sl = xtrymalloc (sizeof *sl + strlen (line)); + if (!sl) + { + free_strlist (list); + return out_of_core (); + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + if (ctrl->server_local->list_to_output) + { + int outfd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); + + if ( outfd == -1 ) + return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); + fp = es_fdopen ( dup (outfd), "w"); + if (!fp) + return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen() failed"); + } + else + { + fp = es_fopencookie (ctx, "w", data_line_cookie_functions); + if (!fp) + return set_error (GPG_ERR_ASS_GENERAL, + "error setting up a data stream"); + } + + ctrl->with_colons = 1; + listmode = mode; + if (ctrl->server_local->list_internal) + listmode |= (1<<6); + if (ctrl->server_local->list_external) + listmode |= (1<<7); + err = gpgsm_list_keys (assuan_get_pointer (ctx), list, fp, listmode); + free_strlist (list); + es_fclose (fp); + if (ctrl->server_local->list_to_output) + assuan_close_output_fd (ctx); + return err; +} + +static gpg_error_t +cmd_listkeys (assuan_context_t ctx, char *line) +{ + return do_listkeys (ctx, line, 3); +} + +static gpg_error_t +cmd_dumpkeys (assuan_context_t ctx, char *line) +{ + return do_listkeys (ctx, line, 259); +} + +static gpg_error_t +cmd_listsecretkeys (assuan_context_t ctx, char *line) +{ + return do_listkeys (ctx, line, 2); +} + +static gpg_error_t +cmd_dumpsecretkeys (assuan_context_t ctx, char *line) +{ + return do_listkeys (ctx, line, 258); +} + + + +static const char hlp_genkey[] = + "GENKEY\n" + "\n" + "Read the parameters in native format from the input fd and write a\n" + "certificate request to the output."; +static gpg_error_t +cmd_genkey (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int inp_fd, out_fd; + FILE *out_fp; + int rc; + estream_t in_stream; + + (void)line; + + inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); + if (inp_fd == -1) + return set_error (GPG_ERR_ASS_NO_INPUT, NULL); + out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); + if (out_fd == -1) + return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); + + in_stream = es_fdopen_nc (inp_fd, "r"); + if (!in_stream) + return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen failed"); + + out_fp = fdopen ( dup(out_fd), "w"); + if (!out_fp) + { + es_fclose (in_stream); + return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed"); + } + rc = gpgsm_genkey (ctrl, in_stream, out_fp); + fclose (out_fp); + + /* close and reset the fds */ + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return rc; +} + + + +static const char hlp_getauditlog[] = + "GETAUDITLOG [--data] [--html]\n" + "\n" + "If --data is used, the output is send using D-lines and not to the\n" + "file descriptor given by an OUTPUT command.\n" + "\n" + "If --html is used the output is formated as an XHTML block. This is\n" + "designed to be incorporated into a HTML document."; +static gpg_error_t +cmd_getauditlog (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int out_fd; + estream_t out_stream; + int opt_data, opt_html; + int rc; + + opt_data = has_option (line, "--data"); + opt_html = has_option (line, "--html"); + line = skip_options (line); + + if (!ctrl->audit) + return gpg_error (GPG_ERR_NO_DATA); + + if (opt_data) + { + out_stream = es_fopencookie (ctx, "w", data_line_cookie_functions); + if (!out_stream) + return set_error (GPG_ERR_ASS_GENERAL, + "error setting up a data stream"); + } + else + { + out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); + if (out_fd == -1) + return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); + + out_stream = es_fdopen_nc ( dup (out_fd), "w"); + if (!out_stream) + { + return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen() failed"); + } + } + + audit_print_result (ctrl->audit, out_stream, opt_html); + rc = 0; + + es_fclose (out_stream); + + /* Close and reset the fd. */ + if (!opt_data) + assuan_close_output_fd (ctx); + return rc; +} + + +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" + " agent-check - Return success if the agent is running.\n" + " cmd_has_option CMD OPT\n" + " - Returns OK if the command CMD implements the option OPT."; +static gpg_error_t +cmd_getinfo (assuan_context_t ctx, char *line) +{ + int rc = 0; + + if (!strcmp (line, "version")) + { + const char *s = VERSION; + rc = assuan_send_data (ctx, s, strlen (s)); + } + 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, "agent-check")) + { + ctrl_t ctrl = assuan_get_pointer (ctx); + rc = gpgsm_agent_send_nop (ctrl); + } + 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_GENERAL); + } + } + } + } + else + rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); + + return rc; +} + + + +static const char hlp_passwd[] = + "PASSWD <userID>\n" + "\n" + "Change the passphrase of the secret key for USERID."; +static gpg_error_t +cmd_passwd (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + ksba_cert_t cert = NULL; + char *grip = NULL; + + line = skip_options (line); + + err = gpgsm_find_cert (line, NULL, &cert); + if (err) + ; + else if (!(grip = gpgsm_get_keygrip_hexstring (cert))) + err = gpg_error (GPG_ERR_INTERNAL); + else + { + char *desc = gpgsm_format_keydesc (cert); + err = gpgsm_agent_passwd (ctrl, grip, desc); + xfree (desc); + } + + xfree (grip); + ksba_cert_release (cert); + + return err; +} + + + + + +/* Return true if the command CMD implements the option OPT. */ +static int +command_has_option (const char *cmd, const char *cmdopt) +{ + if (!strcmp (cmd, "IMPORT")) + { + if (!strcmp (cmdopt, "re-import")) + return 1; + } + + return 0; +} + + +/* Tell the assuan library about our commands */ +static int +register_commands (assuan_context_t ctx) +{ + static struct { + const char *name; + assuan_handler_t handler; + const char * const help; + } table[] = { + { "RECIPIENT", cmd_recipient, hlp_recipient }, + { "SIGNER", cmd_signer, hlp_signer }, + { "ENCRYPT", cmd_encrypt, hlp_encrypt }, + { "DECRYPT", cmd_decrypt, hlp_decrypt }, + { "VERIFY", cmd_verify, hlp_verify }, + { "SIGN", cmd_sign, hlp_sign }, + { "IMPORT", cmd_import, hlp_import }, + { "EXPORT", cmd_export, hlp_export }, + { "INPUT", NULL, hlp_input }, + { "OUTPUT", NULL, hlp_output }, + { "MESSAGE", cmd_message, hlp_message }, + { "LISTKEYS", cmd_listkeys, hlp_listkeys }, + { "DUMPKEYS", cmd_dumpkeys, hlp_listkeys }, + { "LISTSECRETKEYS",cmd_listsecretkeys, hlp_listkeys }, + { "DUMPSECRETKEYS",cmd_dumpsecretkeys, hlp_listkeys }, + { "GENKEY", cmd_genkey, hlp_genkey }, + { "DELKEYS", cmd_delkeys, hlp_delkeys }, + { "GETAUDITLOG", cmd_getauditlog, hlp_getauditlog }, + { "GETINFO", cmd_getinfo, hlp_getinfo }, + { "PASSWD", cmd_passwd, hlp_passwd }, + { 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; + } + return 0; +} + +/* Startup the server. DEFAULT_RECPLIST is the list of recipients as + set from the command line or config file. We only require those + marked as encrypt-to. */ +void +gpgsm_server (certlist_t default_recplist) +{ + int rc; + assuan_fd_t filedes[2]; + assuan_context_t ctx; + struct server_control_s ctrl; + static const char hello[] = ("GNU Privacy Guard's S/M server " + VERSION " ready"); + + memset (&ctrl, 0, sizeof ctrl); + gpgsm_init_default_ctrl (&ctrl); + + /* We use a pipe based server so that we can work from scripts. + assuan_init_pipe_server will automagically detect when we are + called with a socketpair and ignore FIELDES in this case. */ + filedes[0] = assuan_fdopen (0); + filedes[1] = assuan_fdopen (1); + rc = assuan_new (&ctx); + if (rc) + { + log_error ("failed to allocate assuan context: %s\n", + gpg_strerror (rc)); + gpgsm_exit (2); + } + + rc = assuan_init_pipe_server (ctx, filedes); + if (rc) + { + log_error ("failed to initialize the server: %s\n", + gpg_strerror (rc)); + gpgsm_exit (2); + } + rc = register_commands (ctx); + if (rc) + { + log_error ("failed to the register commands with Assuan: %s\n", + gpg_strerror(rc)); + gpgsm_exit (2); + } + if (opt.verbose || opt.debug) + { + char *tmp = NULL; + const char *s1 = getenv ("GPG_AGENT_INFO"); + const char *s2 = getenv ("DIRMNGR_INFO"); + + if (asprintf (&tmp, + "Home: %s\n" + "Config: %s\n" + "AgentInfo: %s\n" + "DirmngrInfo: %s\n" + "%s", + opt.homedir, + opt.config_filename, + s1?s1:"[not set]", + s2?s2:"[not set]", + hello) > 0) + { + assuan_set_hello_line (ctx, tmp); + free (tmp); + } + } + else + assuan_set_hello_line (ctx, hello); + + assuan_register_reset_notify (ctx, reset_notify); + assuan_register_input_notify (ctx, input_notify); + assuan_register_output_notify (ctx, output_notify); + assuan_register_option_handler (ctx, option_handler); + + assuan_set_pointer (ctx, &ctrl); + ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); + ctrl.server_local->assuan_ctx = ctx; + ctrl.server_local->message_fd = -1; + ctrl.server_local->list_internal = 1; + ctrl.server_local->list_external = 0; + ctrl.server_local->default_recplist = default_recplist; + + if (DBG_ASSUAN) + assuan_set_log_stream (ctx, log_get_stream ()); + + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + { + break; + } + else if (rc) + { + log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); + break; + } + + rc = assuan_process (ctx); + if (rc) + { + log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); + continue; + } + } + + gpgsm_release_certlist (ctrl.server_local->recplist); + ctrl.server_local->recplist = NULL; + gpgsm_release_certlist (ctrl.server_local->signerlist); + ctrl.server_local->signerlist = NULL; + xfree (ctrl.server_local); + + audit_release (ctrl.audit); + ctrl.audit = NULL; + + assuan_release (ctx); +} + + + +gpg_error_t +gpgsm_status2 (ctrl_t ctrl, int no, ...) +{ + gpg_error_t err = 0; + va_list arg_ptr; + const char *text; + + va_start (arg_ptr, no); + + if (ctrl->no_server && ctrl->status_fd == -1) + ; /* No status wanted. */ + else if (ctrl->no_server) + { + if (!statusfp) + { + if (ctrl->status_fd == 1) + statusfp = stdout; + else if (ctrl->status_fd == 2) + statusfp = stderr; + else + statusfp = fdopen (ctrl->status_fd, "w"); + + if (!statusfp) + { + log_fatal ("can't open fd %d for status output: %s\n", + ctrl->status_fd, strerror(errno)); + } + } + + fputs ("[GNUPG:] ", statusfp); + fputs (get_status_string (no), statusfp); + + while ( (text = va_arg (arg_ptr, const char*) )) + { + putc ( ' ', statusfp ); + for (; *text; text++) + { + if (*text == '\n') + fputs ( "\\n", statusfp ); + else if (*text == '\r') + fputs ( "\\r", statusfp ); + else + putc ( *(const byte *)text, statusfp ); + } + } + putc ('\n', statusfp); + fflush (statusfp); + } + else + { + assuan_context_t ctx = ctrl->server_local->assuan_ctx; + char buf[950], *p; + size_t n; + + p = buf; + n = 0; + while ( (text = va_arg (arg_ptr, const char *)) ) + { + if (n) + { + *p++ = ' '; + n++; + } + for ( ; *text && n < DIM (buf)-2; n++) + *p++ = *text++; + } + *p = 0; + err = assuan_write_status (ctx, get_status_string (no), buf); + } + + va_end (arg_ptr); + return err; +} + +gpg_error_t +gpgsm_status (ctrl_t ctrl, int no, const char *text) +{ + return gpgsm_status2 (ctrl, no, text, NULL); +} + +gpg_error_t +gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, + gpg_err_code_t ec) +{ + char buf[30]; + + sprintf (buf, "%u", (unsigned int)ec); + if (text) + return gpgsm_status2 (ctrl, no, text, buf, NULL); + else + return gpgsm_status2 (ctrl, no, buf, NULL); +} + + +/* Helper to notify the client about Pinentry events. Because that + might disturb some older clients, this is only done when enabled + via an option. Returns an gpg error code. */ +gpg_error_t +gpgsm_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line) +{ + if (!ctrl || !ctrl->server_local + || !ctrl->server_local->allow_pinentry_notify) + return 0; + return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0); +} + + + diff --git a/sm/sign.c b/sm/sign.c new file mode 100644 index 0000000..fd7c4ff --- /dev/null +++ b/sm/sign.c @@ -0,0 +1,780 @@ +/* sign.c - Sign a message + * Copyright (C) 2001, 2002, 2003, 2008 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + + +/* Hash the data and return if something was hashed. Return -1 on error. */ +static int +hash_data (int fd, gcry_md_hd_t md) +{ + FILE *fp; + char buffer[4096]; + int nread; + int rc = 0; + + fp = fdopen ( dup (fd), "rb"); + if (!fp) + { + log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); + return -1; + } + + do + { + nread = fread (buffer, 1, DIM(buffer), fp); + gcry_md_write (md, buffer, nread); + } + while (nread); + if (ferror (fp)) + { + log_error ("read error on fd %d: %s\n", fd, strerror (errno)); + rc = -1; + } + fclose (fp); + return rc; +} + +static int +hash_and_copy_data (int fd, gcry_md_hd_t md, ksba_writer_t writer) +{ + gpg_error_t err; + FILE *fp; + char buffer[4096]; + int nread; + int rc = 0; + int any = 0; + + fp = fdopen ( dup (fd), "rb"); + if (!fp) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); + return tmperr; + } + + do + { + nread = fread (buffer, 1, DIM(buffer), fp); + if (nread) + { + any = 1; + gcry_md_write (md, buffer, nread); + err = ksba_writer_write_octet_string (writer, buffer, nread, 0); + if (err) + { + log_error ("write failed: %s\n", gpg_strerror (err)); + rc = err; + } + } + } + while (nread && !rc); + if (ferror (fp)) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("read error on fd %d: %s\n", fd, strerror (errno)); + } + fclose (fp); + if (!any) + { + /* We can't allow to sign an empty message because it does not + make much sense and more seriously, ksba-cms_build has + already written the tag for data and now expects an octet + string but an octet string of zeize 0 is illegal. */ + log_error ("cannot sign an empty message\n"); + rc = gpg_error (GPG_ERR_NO_DATA); + } + if (!rc) + { + err = ksba_writer_write_octet_string (writer, NULL, 0, 1); + if (err) + { + log_error ("write failed: %s\n", gpg_strerror (err)); + rc = err; + } + } + + return rc; +} + + +/* Get the default certificate which is defined as the first + certificate capable of signing returned by the keyDB and has a + secret key available. */ +int +gpgsm_get_default_cert (ctrl_t ctrl, ksba_cert_t *r_cert) +{ + KEYDB_HANDLE hd; + ksba_cert_t cert = NULL; + int rc; + char *p; + + hd = keydb_new (0); + if (!hd) + return gpg_error (GPG_ERR_GENERAL); + rc = keydb_search_first (hd); + if (rc) + { + keydb_release (hd); + return rc; + } + + do + { + rc = keydb_get_cert (hd, &cert); + if (rc) + { + log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); + keydb_release (hd); + return rc; + } + + if (!gpgsm_cert_use_sign_p (cert)) + { + p = gpgsm_get_keygrip_hexstring (cert); + if (p) + { + if (!gpgsm_agent_havekey (ctrl, p)) + { + xfree (p); + keydb_release (hd); + *r_cert = cert; + return 0; /* got it */ + } + xfree (p); + } + } + + ksba_cert_release (cert); + cert = NULL; + } + while (!(rc = keydb_search_next (hd))); + if (rc && rc != -1) + log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc)); + + ksba_cert_release (cert); + keydb_release (hd); + return rc; +} + + +static ksba_cert_t +get_default_signer (ctrl_t ctrl) +{ + KEYDB_SEARCH_DESC desc; + ksba_cert_t cert = NULL; + KEYDB_HANDLE kh = NULL; + int rc; + + if (!opt.local_user) + { + rc = gpgsm_get_default_cert (ctrl, &cert); + if (rc) + { + if (rc != -1) + log_debug ("failed to find default certificate: %s\n", + gpg_strerror (rc)); + return NULL; + } + return cert; + } + + rc = keydb_classify_name (opt.local_user, &desc); + if (rc) + { + log_error ("failed to find default signer: %s\n", gpg_strerror (rc)); + return NULL; + } + + kh = keydb_new (0); + if (!kh) + return NULL; + + rc = keydb_search (kh, &desc, 1); + if (rc) + { + log_debug ("failed to find default certificate: rc=%d\n", rc); + } + else + { + rc = keydb_get_cert (kh, &cert); + if (rc) + { + log_debug ("failed to get cert: rc=%d\n", rc); + } + } + + keydb_release (kh); + return cert; +} + +/* Depending on the options in CTRL add the certificate CERT as well as + other certificate up in the chain to the Root-CA to the CMS + object. */ +static int +add_certificate_list (ctrl_t ctrl, ksba_cms_t cms, ksba_cert_t cert) +{ + gpg_error_t err; + int rc = 0; + ksba_cert_t next = NULL; + int n; + int not_root = 0; + + ksba_cert_ref (cert); + + n = ctrl->include_certs; + log_debug ("adding certificates at level %d\n", n); + if (n == -2) + { + not_root = 1; + n = -1; + } + if (n < 0 || n > 50) + n = 50; /* We better apply an upper bound */ + + /* First add my own certificate unless we don't want any certificate + included at all. */ + if (n) + { + if (not_root && gpgsm_is_root_cert (cert)) + err = 0; + else + err = ksba_cms_add_cert (cms, cert); + if (err) + goto ksba_failure; + if (n>0) + n--; + } + /* Walk the chain to include all other certificates. Note that a -1 + used for N makes sure that there is no limit and all certs get + included. */ + while ( n-- && !(rc = gpgsm_walk_cert_chain (ctrl, cert, &next)) ) + { + if (not_root && gpgsm_is_root_cert (next)) + err = 0; + else + err = ksba_cms_add_cert (cms, next); + ksba_cert_release (cert); + cert = next; next = NULL; + if (err) + goto ksba_failure; + } + ksba_cert_release (cert); + + return rc == -1? 0: rc; + + ksba_failure: + ksba_cert_release (cert); + log_error ("ksba_cms_add_cert failed: %s\n", gpg_strerror (err)); + return err; +} + + + + +/* Perform a sign operation. + + Sign the data received on DATA-FD in embedded mode or in detached + mode when DETACHED is true. Write the signature to OUT_FP. The + keys used to sign are taken from SIGNERLIST or the default one will + be used if the value of this argument is NULL. */ +int +gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, + int data_fd, int detached, FILE *out_fp) +{ + int i, rc; + gpg_error_t err; + Base64Context b64writer = NULL; + ksba_writer_t writer; + ksba_cms_t cms = NULL; + ksba_stop_reason_t stopreason; + KEYDB_HANDLE kh = NULL; + gcry_md_hd_t data_md = NULL; + int signer; + const char *algoid; + int algo; + ksba_isotime_t signed_at; + certlist_t cl; + int release_signerlist = 0; + + audit_set_type (ctrl->audit, AUDIT_TYPE_SIGN); + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + ctrl->pem_name = "SIGNED MESSAGE"; + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, NULL, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + + err = ksba_cms_new (&cms); + if (err) + { + rc = err; + goto leave; + } + + err = ksba_cms_set_reader_writer (cms, NULL, writer); + if (err) + { + log_debug ("ksba_cms_set_reader_writer failed: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + + /* We are going to create signed data with data as encap. content */ + err = ksba_cms_set_content_type (cms, 0, KSBA_CT_SIGNED_DATA); + if (!err) + err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA); + if (err) + { + log_debug ("ksba_cms_set_content_type failed: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + + /* If no list of signers is given, use the default certificate. */ + if (!signerlist) + { + ksba_cert_t cert = get_default_signer (ctrl); + if (!cert) + { + log_error ("no default signer found\n"); + gpgsm_status2 (ctrl, STATUS_INV_SGNR, + get_inv_recpsgnr_code (GPG_ERR_NO_SECKEY), NULL); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + /* Although we don't check for ambigious specification we will + check that the signer's certificate is usable and valid. */ + rc = gpgsm_cert_use_sign_p (cert); + if (!rc) + rc = gpgsm_validate_chain (ctrl, cert, "", NULL, 0, NULL, 0, NULL); + if (rc) + { + char *tmpfpr; + + tmpfpr = gpgsm_get_fingerprint_hexstring (cert, 0); + gpgsm_status2 (ctrl, STATUS_INV_SGNR, + get_inv_recpsgnr_code (rc), tmpfpr, NULL); + xfree (tmpfpr); + goto leave; + } + + /* That one is fine - create signerlist. */ + signerlist = xtrycalloc (1, sizeof *signerlist); + if (!signerlist) + { + rc = out_of_core (); + ksba_cert_release (cert); + goto leave; + } + signerlist->cert = cert; + release_signerlist = 1; + } + + /* Figure out the hash algorithm to use. We do not want to use the + one for the certificate but if possible an OID for the plain + algorithm. */ + if (opt.forced_digest_algo && opt.verbose) + log_info ("user requested hash algorithm %d\n", opt.forced_digest_algo); + for (i=0, cl=signerlist; cl; cl = cl->next, i++) + { + const char *oid; + + if (opt.forced_digest_algo) + { + oid = NULL; + cl->hash_algo = opt.forced_digest_algo; + } + else + { + oid = ksba_cert_get_digest_algo (cl->cert); + cl->hash_algo = oid ? gcry_md_map_name (oid) : 0; + } + switch (cl->hash_algo) + { + case GCRY_MD_SHA1: oid = "1.3.14.3.2.26"; break; + case GCRY_MD_RMD160: oid = "1.3.36.3.2.1"; break; + case GCRY_MD_SHA224: oid = "2.16.840.1.101.3.4.2.4"; break; + case GCRY_MD_SHA256: oid = "2.16.840.1.101.3.4.2.1"; break; + case GCRY_MD_SHA384: oid = "2.16.840.1.101.3.4.2.2"; break; + case GCRY_MD_SHA512: oid = "2.16.840.1.101.3.4.2.3"; break; +/* case GCRY_MD_WHIRLPOOL: oid = "No OID yet"; break; */ + + case GCRY_MD_MD5: /* We don't want to use MD5. */ + case 0: /* No algorithm found in cert. */ + default: /* Other algorithms. */ + log_info (_("hash algorithm %d (%s) for signer %d not supported;" + " using %s\n"), + cl->hash_algo, oid? oid: "?", i, + gcry_md_algo_name (GCRY_MD_SHA1)); + cl->hash_algo = GCRY_MD_SHA1; + oid = "1.3.14.3.2.26"; + break; + } + cl->hash_algo_oid = oid; + } + + if (opt.verbose) + { + for (i=0, cl=signerlist; cl; cl = cl->next, i++) + log_info (_("hash algorithm used for signer %d: %s (%s)\n"), + i, gcry_md_algo_name (cl->hash_algo), cl->hash_algo_oid); + } + + + /* Gather certificates of signers and store them in the CMS object. */ + for (cl=signerlist; cl; cl = cl->next) + { + rc = gpgsm_cert_use_sign_p (cl->cert); + if (rc) + goto leave; + + err = ksba_cms_add_signer (cms, cl->cert); + if (err) + { + log_error ("ksba_cms_add_signer failed: %s\n", gpg_strerror (err)); + rc = err; + goto leave; + } + rc = add_certificate_list (ctrl, cms, cl->cert); + if (rc) + { + log_error ("failed to store list of certificates: %s\n", + gpg_strerror(rc)); + goto leave; + } + /* Set the hash algorithm we are going to use */ + err = ksba_cms_add_digest_algo (cms, cl->hash_algo_oid); + if (err) + { + log_debug ("ksba_cms_add_digest_algo failed: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + + + /* Check whether one of the certificates is qualified. Note that we + already validated the certificate and thus the user data stored + flag must be available. */ + for (cl=signerlist; cl; cl = cl->next) + { + size_t buflen; + char buffer[1]; + + err = ksba_cert_get_user_data (cl->cert, "is_qualified", + &buffer, sizeof (buffer), &buflen); + if (err || !buflen) + { + log_error (_("checking for qualified certificate failed: %s\n"), + gpg_strerror (err)); + rc = err; + goto leave; + } + if (*buffer) + err = gpgsm_qualified_consent (ctrl, cl->cert); + else + err = gpgsm_not_qualified_warning (ctrl, cl->cert); + if (err) + { + rc = err; + goto leave; + } + } + + /* Prepare hashing (actually we are figuring out what we have set + above). */ + rc = gcry_md_open (&data_md, 0, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_start_debug (data_md, "sign.data"); + + for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++) + { + algo = gcry_md_map_name (algoid); + if (!algo) + { + log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?"); + rc = gpg_error (GPG_ERR_BUG); + goto leave; + } + gcry_md_enable (data_md, algo); + audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo); + } + + audit_log (ctrl->audit, AUDIT_SETUP_READY); + + if (detached) + { /* We hash the data right now so that we can store the message + digest. ksba_cms_build() takes this as an flag that detached + data is expected. */ + unsigned char *digest; + size_t digest_len; + + if (!hash_data (data_fd, data_md)) + audit_log (ctrl->audit, AUDIT_GOT_DATA); + for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) + { + digest = gcry_md_read (data_md, cl->hash_algo); + digest_len = gcry_md_get_algo_dlen (cl->hash_algo); + if ( !digest || !digest_len ) + { + log_error ("problem getting the hash of the data\n"); + rc = gpg_error (GPG_ERR_BUG); + goto leave; + } + err = ksba_cms_set_message_digest (cms, signer, digest, digest_len); + if (err) + { + log_error ("ksba_cms_set_message_digest failed: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + } + + gnupg_get_isotime (signed_at); + for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) + { + err = ksba_cms_set_signing_time (cms, signer, signed_at); + if (err) + { + log_error ("ksba_cms_set_signing_time failed: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + + /* We need to write at least a minimal list of our capabilities to + try to convince some MUAs to use 3DES and not the crippled + RC2. Our list is: + + aes128-CBC + des-EDE3-CBC + */ + err = ksba_cms_add_smime_capability (cms, "2.16.840.1.101.3.4.1.2", NULL, 0); + if (!err) + err = ksba_cms_add_smime_capability (cms, "1.2.840.113549.3.7", NULL, 0); + if (err) + { + log_error ("ksba_cms_add_smime_capability failed: %s\n", + gpg_strerror (err)); + goto leave; + } + + + /* Main building loop. */ + do + { + err = ksba_cms_build (cms, &stopreason); + if (err) + { + log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err)); + rc = err; + goto leave; + } + + if (stopreason == KSBA_SR_BEGIN_DATA) + { + /* Hash the data and store the message digest. */ + unsigned char *digest; + size_t digest_len; + + assert (!detached); + + rc = hash_and_copy_data (data_fd, data_md, writer); + if (rc) + goto leave; + audit_log (ctrl->audit, AUDIT_GOT_DATA); + for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) + { + digest = gcry_md_read (data_md, cl->hash_algo); + digest_len = gcry_md_get_algo_dlen (cl->hash_algo); + if ( !digest || !digest_len ) + { + log_error ("problem getting the hash of the data\n"); + rc = gpg_error (GPG_ERR_BUG); + goto leave; + } + err = ksba_cms_set_message_digest (cms, signer, + digest, digest_len); + if (err) + { + log_error ("ksba_cms_set_message_digest failed: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + } + else if (stopreason == KSBA_SR_NEED_SIG) + { + /* Compute the signature for all signers. */ + gcry_md_hd_t md; + + rc = gcry_md_open (&md, 0, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_start_debug (md, "sign.attr"); + ksba_cms_set_hash_function (cms, HASH_FNC, md); + for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) + { + unsigned char *sigval = NULL; + char *buf, *fpr; + + audit_log_i (ctrl->audit, AUDIT_NEW_SIG, signer); + if (signer) + gcry_md_reset (md); + { + certlist_t cl_tmp; + + for (cl_tmp=signerlist; cl_tmp; cl_tmp = cl_tmp->next) + { + gcry_md_enable (md, cl_tmp->hash_algo); + audit_log_i (ctrl->audit, AUDIT_ATTR_HASH_ALGO, + cl_tmp->hash_algo); + } + } + + rc = ksba_cms_hash_signed_attrs (cms, signer); + if (rc) + { + log_debug ("hashing signed attrs failed: %s\n", + gpg_strerror (rc)); + gcry_md_close (md); + goto leave; + } + + rc = gpgsm_create_cms_signature (ctrl, cl->cert, + md, cl->hash_algo, &sigval); + if (rc) + { + audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, rc); + gcry_md_close (md); + goto leave; + } + + err = ksba_cms_set_sig_val (cms, signer, sigval); + xfree (sigval); + if (err) + { + audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, err); + log_error ("failed to store the signature: %s\n", + gpg_strerror (err)); + rc = err; + gcry_md_close (md); + goto leave; + } + + /* write a status message */ + fpr = gpgsm_get_fingerprint_hexstring (cl->cert, GCRY_MD_SHA1); + if (!fpr) + { + rc = gpg_error (GPG_ERR_ENOMEM); + gcry_md_close (md); + goto leave; + } + rc = 0; + { + int pkalgo = gpgsm_get_key_algo_info (cl->cert, NULL); + buf = xtryasprintf ("%c %d %d 00 %s %s", + detached? 'D':'S', + pkalgo, + cl->hash_algo, + signed_at, + fpr); + if (!buf) + rc = gpg_error_from_syserror (); + } + xfree (fpr); + if (rc) + { + gcry_md_close (md); + goto leave; + } + gpgsm_status (ctrl, STATUS_SIG_CREATED, buf); + xfree (buf); + audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, 0); + } + gcry_md_close (md); + } + } + while (stopreason != KSBA_SR_READY); + + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + audit_log (ctrl->audit, AUDIT_SIGNING_DONE); + log_info ("signature created\n"); + + + leave: + if (rc) + log_error ("error creating signature: %s <%s>\n", + gpg_strerror (rc), gpg_strsource (rc) ); + if (release_signerlist) + gpgsm_release_certlist (signerlist); + ksba_cms_release (cms); + gpgsm_destroy_writer (b64writer); + keydb_release (kh); + gcry_md_close (data_md); + return rc; +} diff --git a/sm/verify.c b/sm/verify.c new file mode 100644 index 0000000..c8663e3 --- /dev/null +++ b/sm/verify.c @@ -0,0 +1,660 @@ +/* verify.c - Verify a messages signature + * Copyright (C) 2001, 2002, 2003, 2007 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + +static char * +strtimestamp_r (ksba_isotime_t atime) +{ + char *buffer = xmalloc (15); + + if (!atime || !*atime) + strcpy (buffer, "none"); + else + sprintf (buffer, "%.4s-%.2s-%.2s", atime, atime+4, atime+6); + return buffer; +} + + + +/* Hash the data for a detached signature. Returns 0 on success. */ +static gpg_error_t +hash_data (int fd, gcry_md_hd_t md) +{ + gpg_error_t err = 0; + FILE *fp; + char buffer[4096]; + int nread; + + fp = fdopen ( dup (fd), "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("fdopen(%d) failed: %s\n", fd, gpg_strerror (err)); + return err; + } + + do + { + nread = fread (buffer, 1, DIM(buffer), fp); + gcry_md_write (md, buffer, nread); + } + while (nread); + if (ferror (fp)) + { + err = gpg_error_from_syserror (); + log_error ("read error on fd %d: %s\n", fd, gpg_strerror (err)); + } + fclose (fp); + return err; +} + + + + +/* Perform a verify operation. To verify detached signatures, data_fd + must be different than -1. With OUT_FP given and a non-detached + signature, the signed material is written to that stream. */ +int +gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, FILE *out_fp) +{ + int i, rc; + Base64Context b64reader = NULL; + Base64Context b64writer = NULL; + ksba_reader_t reader; + ksba_writer_t writer = NULL; + ksba_cms_t cms = NULL; + ksba_stop_reason_t stopreason; + ksba_cert_t cert; + KEYDB_HANDLE kh; + gcry_md_hd_t data_md = NULL; + int signer; + const char *algoid; + int algo; + int is_detached; + FILE *fp = NULL; + char *p; + + audit_set_type (ctrl->audit, AUDIT_TYPE_VERIFY); + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + + fp = fdopen ( dup (in_fd), "rb"); + if (!fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + rc = gpgsm_create_reader (&b64reader, ctrl, fp, 0, &reader); + if (rc) + { + log_error ("can't create reader: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (out_fp) + { + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, NULL, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + } + + rc = ksba_cms_new (&cms); + if (rc) + goto leave; + + rc = ksba_cms_set_reader_writer (cms, reader, writer); + if (rc) + { + log_error ("ksba_cms_set_reader_writer failed: %s\n", + gpg_strerror (rc)); + goto leave; + } + + rc = gcry_md_open (&data_md, 0, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_start_debug (data_md, "vrfy.data"); + + audit_log (ctrl->audit, AUDIT_SETUP_READY); + + is_detached = 0; + do + { + rc = ksba_cms_parse (cms, &stopreason); + if (rc) + { + log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (stopreason == KSBA_SR_NEED_HASH) + { + is_detached = 1; + audit_log (ctrl->audit, AUDIT_DETACHED_SIGNATURE); + if (opt.verbose) + log_info ("detached signature\n"); + } + + if (stopreason == KSBA_SR_NEED_HASH + || stopreason == KSBA_SR_BEGIN_DATA) + { + audit_log (ctrl->audit, AUDIT_GOT_DATA); + + /* We are now able to enable the hash algorithms */ + for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++) + { + algo = gcry_md_map_name (algoid); + if (!algo) + { + log_error ("unknown hash algorithm `%s'\n", + algoid? algoid:"?"); + if (algoid + && ( !strcmp (algoid, "1.2.840.113549.1.1.2") + ||!strcmp (algoid, "1.2.840.113549.2.2"))) + log_info (_("(this is the MD2 algorithm)\n")); + audit_log_s (ctrl->audit, AUDIT_BAD_DATA_HASH_ALGO, algoid); + } + else + { + if (DBG_X509) + log_debug ("enabling hash algorithm %d (%s)\n", + algo, algoid? algoid:""); + gcry_md_enable (data_md, algo); + audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo); + } + } + if (opt.extra_digest_algo) + { + if (DBG_X509) + log_debug ("enabling extra hash algorithm %d\n", + opt.extra_digest_algo); + gcry_md_enable (data_md, opt.extra_digest_algo); + audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, + opt.extra_digest_algo); + } + if (is_detached) + { + if (data_fd == -1) + { + log_info ("detached signature w/o data " + "- assuming certs-only\n"); + audit_log (ctrl->audit, AUDIT_CERT_ONLY_SIG); + } + else + audit_log_ok (ctrl->audit, AUDIT_DATA_HASHING, + hash_data (data_fd, data_md)); + } + else + { + ksba_cms_set_hash_function (cms, HASH_FNC, data_md); + } + } + else if (stopreason == KSBA_SR_END_DATA) + { /* The data bas been hashed */ + audit_log_ok (ctrl->audit, AUDIT_DATA_HASHING, 0); + } + } + while (stopreason != KSBA_SR_READY); + + if (b64writer) + { + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + audit_log_ok (ctrl->audit, AUDIT_WRITE_ERROR, rc); + goto leave; + } + } + + if (data_fd != -1 && !is_detached) + { + log_error ("data given for a non-detached signature\n"); + rc = gpg_error (GPG_ERR_CONFLICT); + audit_log (ctrl->audit, AUDIT_USAGE_ERROR); + goto leave; + } + + for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++) + { + /* Fixme: it might be better to check the validity of the + certificate first before entering it into the DB. This way + we would avoid cluttering the DB with invalid + certificates. */ + audit_log_cert (ctrl->audit, AUDIT_SAVE_CERT, cert, + keydb_store_cert (cert, 0, NULL)); + ksba_cert_release (cert); + } + + cert = NULL; + for (signer=0; ; signer++) + { + char *issuer = NULL; + ksba_sexp_t sigval = NULL; + ksba_isotime_t sigtime, keyexptime; + ksba_sexp_t serial; + char *msgdigest = NULL; + size_t msgdigestlen; + char *ctattr; + int sigval_hash_algo; + int info_pkalgo; + unsigned int verifyflags; + + rc = ksba_cms_get_issuer_serial (cms, signer, &issuer, &serial); + if (!signer && gpg_err_code (rc) == GPG_ERR_NO_DATA + && data_fd == -1 && is_detached) + { + log_info ("certs-only message accepted\n"); + rc = 0; + break; + } + if (rc) + { + if (signer && rc == -1) + rc = 0; + break; + } + + gpgsm_status (ctrl, STATUS_NEWSIG, NULL); + audit_log_i (ctrl->audit, AUDIT_NEW_SIG, signer); + + if (DBG_X509) + { + log_debug ("signer %d - issuer: `%s'\n", + signer, issuer? issuer:"[NONE]"); + log_debug ("signer %d - serial: ", signer); + gpgsm_dump_serial (serial); + log_printf ("\n"); + } + if (ctrl->audit) + { + char *tmpstr = gpgsm_format_sn_issuer (serial, issuer); + audit_log_s (ctrl->audit, AUDIT_SIG_NAME, tmpstr); + xfree (tmpstr); + } + + rc = ksba_cms_get_signing_time (cms, signer, sigtime); + if (gpg_err_code (rc) == GPG_ERR_NO_DATA) + *sigtime = 0; + else if (rc) + { + log_error ("error getting signing time: %s\n", gpg_strerror (rc)); + *sigtime = 0; /* (we can't encode an error in the time string.) */ + } + + rc = ksba_cms_get_message_digest (cms, signer, + &msgdigest, &msgdigestlen); + if (!rc) + { + size_t is_enabled; + + algoid = ksba_cms_get_digest_algo (cms, signer); + algo = gcry_md_map_name (algoid); + if (DBG_X509) + log_debug ("signer %d - digest algo: %d\n", signer, algo); + is_enabled = sizeof algo; + if ( gcry_md_info (data_md, GCRYCTL_IS_ALGO_ENABLED, + &algo, &is_enabled) + || !is_enabled) + { + log_error ("digest algo %d (%s) has not been enabled\n", + algo, algoid?algoid:""); + audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "unsupported"); + goto next_signer; + } + } + else if (gpg_err_code (rc) == GPG_ERR_NO_DATA) + { + assert (!msgdigest); + rc = 0; + algoid = NULL; + algo = 0; + } + else /* real error */ + { + audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error"); + break; + } + + rc = ksba_cms_get_sigattr_oids (cms, signer, + "1.2.840.113549.1.9.3", &ctattr); + if (!rc) + { + const char *s; + + if (DBG_X509) + log_debug ("signer %d - content-type attribute: %s", + signer, ctattr); + + s = ksba_cms_get_content_oid (cms, 1); + if (!s || strcmp (ctattr, s)) + { + log_error ("content-type attribute does not match " + "actual content-type\n"); + ksba_free (ctattr); + ctattr = NULL; + audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad"); + goto next_signer; + } + ksba_free (ctattr); + ctattr = NULL; + } + else if (rc != -1) + { + log_error ("error getting content-type attribute: %s\n", + gpg_strerror (rc)); + audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad"); + goto next_signer; + } + rc = 0; + + + sigval = ksba_cms_get_sig_val (cms, signer); + if (!sigval) + { + log_error ("no signature value available\n"); + audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad"); + goto next_signer; + } + sigval_hash_algo = hash_algo_from_sigval (sigval); + if (DBG_X509) + { + log_debug ("signer %d - signature available (sigval hash=%d)", + signer, sigval_hash_algo); +/* log_printhex ("sigval ", sigval, */ +/* gcry_sexp_canon_len (sigval, 0, NULL, NULL)); */ + } + if (!sigval_hash_algo) + sigval_hash_algo = algo; /* Fallback used e.g. with old libksba. */ + + /* Find the certificate of the signer */ + keydb_search_reset (kh); + rc = keydb_search_issuer_sn (kh, issuer, serial); + if (rc) + { + if (rc == -1) + { + log_error ("certificate not found\n"); + rc = gpg_error (GPG_ERR_NO_PUBKEY); + } + else + log_error ("failed to find the certificate: %s\n", + gpg_strerror(rc)); + { + char numbuf[50]; + sprintf (numbuf, "%d", rc); + + gpgsm_status2 (ctrl, STATUS_ERROR, "verify.findkey", + numbuf, NULL); + } + audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "no-cert"); + goto next_signer; + } + + rc = keydb_get_cert (kh, &cert); + if (rc) + { + log_error ("failed to get cert: %s\n", gpg_strerror (rc)); + audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error"); + goto next_signer; + } + + log_info (_("Signature made ")); + if (*sigtime) + dump_isotime (sigtime); + else + log_printf (_("[date not given]")); + log_printf (_(" using certificate ID 0x%08lX\n"), + gpgsm_get_short_fingerprint (cert, NULL)); + + audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo); + + if (msgdigest) + { /* Signed attributes are available. */ + gcry_md_hd_t md; + unsigned char *s; + + /* Check that the message digest in the signed attributes + matches the one we calculated on the data. */ + s = gcry_md_read (data_md, algo); + if ( !s || !msgdigestlen + || gcry_md_get_algo_dlen (algo) != msgdigestlen + || !s || memcmp (s, msgdigest, msgdigestlen) ) + { + char *fpr; + + log_error (_("invalid signature: message digest attribute " + "does not match computed one\n")); + if (DBG_X509) + { + if (msgdigest) + log_printhex ("message: ", msgdigest, msgdigestlen); + if (s) + log_printhex ("computed: ", + s, gcry_md_get_algo_dlen (algo)); + } + fpr = gpgsm_fpr_and_name_for_status (cert); + gpgsm_status (ctrl, STATUS_BADSIG, fpr); + xfree (fpr); + audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad"); + goto next_signer; + } + + audit_log_i (ctrl->audit, AUDIT_ATTR_HASH_ALGO, sigval_hash_algo); + rc = gcry_md_open (&md, sigval_hash_algo, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error"); + goto next_signer; + } + if (DBG_HASHING) + gcry_md_start_debug (md, "vrfy.attr"); + + ksba_cms_set_hash_function (cms, HASH_FNC, md); + rc = ksba_cms_hash_signed_attrs (cms, signer); + if (rc) + { + log_error ("hashing signed attrs failed: %s\n", + gpg_strerror (rc)); + gcry_md_close (md); + audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "error"); + goto next_signer; + } + rc = gpgsm_check_cms_signature (cert, sigval, md, + sigval_hash_algo, &info_pkalgo); + gcry_md_close (md); + } + else + { + rc = gpgsm_check_cms_signature (cert, sigval, data_md, + algo, &info_pkalgo); + } + + if (rc) + { + char *fpr; + + log_error ("invalid signature: %s\n", gpg_strerror (rc)); + fpr = gpgsm_fpr_and_name_for_status (cert); + gpgsm_status (ctrl, STATUS_BADSIG, fpr); + xfree (fpr); + audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad"); + goto next_signer; + } + rc = gpgsm_cert_use_verify_p (cert); /*(this displays an info message)*/ + if (rc) + { + gpgsm_status_with_err_code (ctrl, STATUS_ERROR, "verify.keyusage", + gpg_err_code (rc)); + rc = 0; + } + + if (DBG_X509) + log_debug ("signature okay - checking certs\n"); + audit_log (ctrl->audit, AUDIT_VALIDATE_CHAIN); + rc = gpgsm_validate_chain (ctrl, cert, + *sigtime? sigtime : "19700101T000000", + keyexptime, 0, + NULL, 0, &verifyflags); + { + char *fpr, *buf, *tstr; + + fpr = gpgsm_fpr_and_name_for_status (cert); + if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED) + { + gpgsm_status (ctrl, STATUS_EXPKEYSIG, fpr); + rc = 0; + } + else + gpgsm_status (ctrl, STATUS_GOODSIG, fpr); + + xfree (fpr); + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + tstr = strtimestamp_r (sigtime); + buf = xasprintf ("%s %s %s %s 0 0 %d %d 00", fpr, tstr, + *sigtime? sigtime : "0", + *keyexptime? keyexptime : "0", + info_pkalgo, algo); + xfree (tstr); + xfree (fpr); + gpgsm_status (ctrl, STATUS_VALIDSIG, buf); + xfree (buf); + } + + audit_log_ok (ctrl->audit, AUDIT_CHAIN_STATUS, rc); + if (rc) /* of validate_chain */ + { + log_error ("invalid certification chain: %s\n", gpg_strerror (rc)); + if (gpg_err_code (rc) == GPG_ERR_BAD_CERT_CHAIN + || gpg_err_code (rc) == GPG_ERR_BAD_CERT + || gpg_err_code (rc) == GPG_ERR_BAD_CA_CERT + || gpg_err_code (rc) == GPG_ERR_CERT_REVOKED) + gpgsm_status_with_err_code (ctrl, STATUS_TRUST_NEVER, NULL, + gpg_err_code (rc)); + else + gpgsm_status_with_err_code (ctrl, STATUS_TRUST_UNDEFINED, NULL, + gpg_err_code (rc)); + audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "bad"); + goto next_signer; + } + + audit_log_s (ctrl->audit, AUDIT_SIG_STATUS, "good"); + + for (i=0; (p = ksba_cert_get_subject (cert, i)); i++) + { + log_info (!i? _("Good signature from") + : _(" aka")); + log_printf (" \""); + gpgsm_print_name (log_get_stream (), p); + log_printf ("\"\n"); + ksba_free (p); + } + + /* Print a note if this is a qualified signature. */ + { + size_t qualbuflen; + char qualbuffer[1]; + + rc = ksba_cert_get_user_data (cert, "is_qualified", &qualbuffer, + sizeof (qualbuffer), &qualbuflen); + if (!rc && qualbuflen) + { + if (*qualbuffer) + { + log_info (_("This is a qualified signature\n")); + if (!opt.qualsig_approval) + log_info + (_("Note, that this software is not officially approved " + "to create or verify such signatures.\n")); + } + } + else if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND) + log_error ("get_user_data(is_qualified) failed: %s\n", + gpg_strerror (rc)); + } + + gpgsm_status (ctrl, STATUS_TRUST_FULLY, + (verifyflags & VALIDATE_FLAG_CHAIN_MODEL)? + "0 chain": "0 shell"); + + + next_signer: + rc = 0; + xfree (issuer); + xfree (serial); + xfree (sigval); + xfree (msgdigest); + ksba_cert_release (cert); + cert = NULL; + } + rc = 0; + + leave: + ksba_cms_release (cms); + gpgsm_destroy_reader (b64reader); + gpgsm_destroy_writer (b64writer); + keydb_release (kh); + gcry_md_close (data_md); + if (fp) + fclose (fp); + + if (rc) + { + char numbuf[50]; + sprintf (numbuf, "%d", rc ); + gpgsm_status2 (ctrl, STATUS_ERROR, "verify.leave", + numbuf, NULL); + } + + return rc; +} + |