diff options
Diffstat (limited to 'sm')
-rw-r--r-- | sm/ChangeLog-2011 | 2968 | ||||
-rw-r--r-- | sm/Makefile.am | 73 | ||||
-rw-r--r-- | sm/Makefile.in | 907 | ||||
-rw-r--r-- | sm/call-agent.c | 1455 | ||||
-rw-r--r-- | sm/call-dirmngr.c | 1099 | ||||
-rw-r--r-- | sm/certchain.c | 2380 | ||||
-rw-r--r-- | sm/certcheck.c | 616 | ||||
-rw-r--r-- | sm/certdump.c | 940 | ||||
-rw-r--r-- | sm/certlist.c | 618 | ||||
-rw-r--r-- | sm/certreqgen-ui.c | 473 | ||||
-rw-r--r-- | sm/certreqgen.c | 1393 | ||||
-rw-r--r-- | sm/decrypt.c | 1166 | ||||
-rw-r--r-- | sm/delete.c | 182 | ||||
-rw-r--r-- | sm/encrypt.c | 589 | ||||
-rw-r--r-- | sm/export.c | 756 | ||||
-rw-r--r-- | sm/fingerprint.c | 380 | ||||
-rw-r--r-- | sm/gpgsm-w32info.rc | 52 | ||||
-rw-r--r-- | sm/gpgsm.c | 2262 | ||||
-rw-r--r-- | sm/gpgsm.h | 467 | ||||
-rw-r--r-- | sm/gpgsm.w32-manifest.in | 18 | ||||
-rw-r--r-- | sm/import.c | 941 | ||||
-rw-r--r-- | sm/keydb.c | 1322 | ||||
-rw-r--r-- | sm/keydb.h | 79 | ||||
-rw-r--r-- | sm/keylist.c | 1686 | ||||
-rw-r--r-- | sm/minip12.c | 3048 | ||||
-rw-r--r-- | sm/minip12.h | 42 | ||||
-rw-r--r-- | sm/misc.c | 308 | ||||
-rw-r--r-- | sm/passphrase.c | 90 | ||||
-rw-r--r-- | sm/passphrase.h | 27 | ||||
-rw-r--r-- | sm/qualified.c | 325 | ||||
-rw-r--r-- | sm/server.c | 1508 | ||||
-rw-r--r-- | sm/sign.c | 828 | ||||
-rw-r--r-- | sm/verify.c | 731 |
33 files changed, 29729 insertions, 0 deletions
diff --git a/sm/ChangeLog-2011 b/sm/ChangeLog-2011 new file mode 100644 index 0000000..4a4df86 --- /dev/null +++ b/sm/ChangeLog-2011 @@ -0,0 +1,2968 @@ +2011-12-01 Werner Koch <wk@g10code.com> + + NB: ChangeLog files are no longer manually maintained. Starting + on December 1st, 2011 we put change information only in the GIT + commit log, and generate a top-level ChangeLog file from logs at + "make dist". See doc/HACKING for details. + +2011-09-20 Werner Koch <wk@g10code.com> + + * verify.c (gpgsm_verify): s/gcry_md_start_debug/gcry_md_debug/ + in preparation for Libgcrypt 1.6. + * sign.c (gpgsm_sign): Ditto. + * certreqgen.c (create_request): Ditto. + * certcheck.c (gpgsm_check_cert_sig): Ditto. + +2011-08-10 Werner Koch <wk@g10code.com> + + * keydb.c (keydb_add_resource): Remove unsued var CREATED_FNAME. + + * gpgsm.c (main): Remove unused 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. + +2011-06-01 Marcus Brinkmann <mb@g10code.com> + + * certreqgen.c (proc_parameters): Initialize RC. + +2011-04-25 Werner Koch <wk@g10code.com> + + * certlist.c (gpgsm_add_to_certlist): Mark classify_user_id for + use with non-OpenPGP. + (gpgsm_find_cert): Ditto. + * sign.c (get_default_signer): Ditto. + * keylist.c (list_internal_keys): Ditto. + * import.c (reimport_one): Ditto. + * export.c (gpgsm_export): Ditto. + * delete.c (delete_one): Ditto. + +2011-03-10 Werner Koch <wk@g10code.com> + + * minip12.c (oid_pkcs5PBKDF2, oid_pkcs5PBES2, oid_aes128_CBC): New. + (set_key_iv_pbes2): New. + (crypt_block): Add args IV and IVLEN. Call set_key_iv_pbes2. + (decrypt_block): Add args IV and IVLEN. + (parse_bag_encrypted_data): Hack to support PBES2 data. + (parse_bag_data): Ditto. + +2011-03-03 Werner Koch <wk@g10code.com> + + * base64.c (base64_finish_write): Do not copy to radbuf to get rid + of a faulty gcc 4.4 "used uninitialized" warning. + +2011-03-01 Werner Koch <wk@g10code.com> + + * certreqgen.c (pSERIAL, pISSUERDN, pNOTBEFORE, pNOTAFTER) + (pSIGNINGKEY, pHASHALGO): New. + (reqgen_ctrl_s): Remove field WRITER. + (read_parameters): Support new keywords. Change arg WRITER to + OUT_FP; pass that to proc_parameters. + (proc_parameters): Add arg WRITER. Check values of new keywords. + Create writer object here. Support generation of certificates. + (create_request): Take new arg SIGKEY. Allow for hash algorithms + other than SHA-1. Set serialno and other values for certificate + creation. + (gpgsm_genkey): Do not create writer object but pass output stream + to read_parameters. + * certreqgen-ui.c (gpgsm_gencertreq_tty): Ask for self-signed. + * misc.c (transform_sigval): New. + +2011-02-25 Werner Koch <wk@g10code.com> + + * certreqgen.c (create_request): Add arg SIGKEY. + +2010-11-25 Werner Koch <wk@g10code.com> + + * base64.c (gpgsm_create_writer): Remove arg FP which is not used + by any caller. Change all callers. + (struct writer_cb_parm_s): Remove field FP. + (do_putc, do_fputs): Remove and replace callers by direct calls to + es_ functions. + +2010-11-23 Werner Koch <wk@g10code.com> + + * Makefile.am (gpgsm_LDADD): Add extra_bin_ldflags. + +2010-10-08 Werner Koch <wk@g10code.com> + + * gpgsm.c: Add option --with-keygrip. + * gpgsm.h (struct opt): Add WITH_KEYGRIP. + * keylist.c (list_cert_std): Implement option. + +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-08-16 Werner Koch <wk@g10code.com> + + * gpgsm.c (main) <aGPGConfList>: Use es_printf. + + * call-dirmngr.c (start_dirmngr_ext): Use new start_new_dirmngr + function. + + * gpgsm.c: Mark option --prefer-system-dirmngr obsolete. + (main): Enable dirmngr by default. + + * gpgsm.h (struct opt): Remove field PREFER_SYSTEM_DIRMNGR. + + * server.c (gpgsm_server): Use dirmngr_socket_name instead of the + envvar for the hello line info. + +2010-06-21 Werner Koch <wk@g10code.com> + + * minip12.c (p12_build): Change arg CERT to const void ptr. + (build_cert_sequence): Change arg CERT to const ptr. + + * gpgsm.c (main) <aExportSecretKeyP12>: Use to estream. + (open_fwrite): Removed. + + * export.c: Include minip12.h. + (popen_protect_tool): Remove. + (export_p12): Use gpg-agent directly. Change calling convention. + (gpgsm_p12_export): Adjust for that change. Change arg FP to an + estream_t. + (do_putc): Remove. Change callers to es_putc. + (do_fputs): Likewise. + (print_short_info): Remove arg FP. + * call-agent.c (gpgsm_agent_export_key): new. + +2010-06-17 Werner Koch <wk@g10code.com> + + * import.c (parse_p12): Remove arg retfp. Use the agent's new + import command. + (import_one): Adjust call to pkcs12. + (store_cert_cb, rsa_key_check): New. + (popen_protect_tool): Remove. + * minip12.c (parse_bag_encrypted_data, p12_parse): Add arg + R_BADPASS. + * call-agent.c (gpgsm_agent_ask_passphrase): New. + (gpgsm_agent_keywrap_key): New. + (struct import_key_parm_s): New. + (gpgsm_agent_import_key): New. + * minip12.c, minip12.h: Move from ../agent/. + * Makefile.am (gpgsm_SOURCES): Add them. + +2010-06-11 Marcus Brinkmann <marcus@g10code.de> + + * server.c (cmd_message) [HAVE_W32CE_SYSTEM]: Finish pipe. + +2010-06-10 Marcus Brinkmann <marcus@g10code.de> + + * server.c (SERVER_STDIN, SERVER_STDOUT): New macros. + (gpgsm_server): Use them with assuan_fdopen. + +2010-04-23 Marcus Brinkmann <marcus@g10code.de> + + * certreqgen.c (read_parameters): Use ascii_isspace instead of + spacep to stop at newline, too. + +2010-04-14 Werner Koch <wk@g10code.com> + + * gpgsm.c (main) [W32CE]: Disable dirmngr for now. + +2010-04-13 Werner Koch <wk@g10code.com> + + * sign.c (gpgsm_sign): Do not check qualified status in + no-chain-validation mode. + +2010-04-08 Werner Koch <wk@g10code.com> + + * gpgsm.c (open_es_fread): Add arg mode. + (main) <aKeygen>: Call with mode "r" instead of "rb". + +2010-04-07 Werner Koch <wk@g10code.com> + + * misc.c: Remove setenv.h. Include sysutils.h. + (setup_pinentry_env): s/setenv/gnupg_setenv/ + +2010-03-24 Werner Koch <wk@g10code.com> + + * Makefile.am (gpgsm_LDADD): Add extra_sys_libs. + +2010-03-23 Werner Koch <wk@g10code.com> + + * qualified.c (gpgsm_is_in_qualified_list): Replace rewind by + fseek+clearerr. + +2010-03-22 Werner Koch <wk@g10code.com> + + * import.c (parse_p12): Use estream functions for the tmp streams. + * export.c (export_p12): Ditto. + +2010-03-11 Werner Koch <wk@g10code.com> + + * verify.c (gpgsm_verify): Use gpgsm_es_print_name. + + * gpgsm.c: Include "asshelp.h". + (main): Remove assuan_set_assuan_log_prefix. Add + assuan_set_log_cb. + * server.c (gpgsm_server): Remove assuan_set_log_stream. + +2010-03-10 Werner Koch <wk@g10code.com> + + * Makefile.am (common_libs): Remove libjnlib.a. Change order. + + * gpgsm.h: Remove "estream.h". + +2010-03-08 Werner Koch <wk@g10code.com> + + * certreqgen.c (gpgsm_genkey): Change OUT_FP to an estream_t + OUT_STREAM. + * certreqgen-ui.c (gpgsm_gencertreq_tty): ditto. + + * server.c (cmd_genkey): Close IN_STREAM. + + * server.c (cmd_encrypt, cmd_decrypt, cmd_verify, cmd_sign): Avoid + dup call by using es_fdopen_nc. + (do_listkeys): Use es_fdopen_nc instead of dup and es_fdopen. + (cmd_export): Ditto. + (cmd_genkey): Ditto. + * export.c (popen_protect_tool): Change OUTFILE to an estream_t. + (export_p12): Change OUTFP and arg RETFP to an estream_t. + (gpgsm_p12_export): Change DATAFP to an estream_t. + (gpgsm_export): Remove arg FP. + * import.c (import_one): Change CERTFP and arg FP to an estream_t. + (popen_protect_tool): Ditto for OUTFILE. + (parse_p12): Change CERTFP to an estream_t. + * sign.c (hash_data, hash_and_copy_data): Use estream. + (gpgsm_sign): Change arg OUT_FP to an estream_t. + * verify.c (gpgsm_verify): Rename FP to IN_FP. Change FP and arg + OUT_FP to an estream_t. + (hash_data): Use estream. + * base64.c (struct reader_cb_parm_s): Change FP to an estream_t. + (gpgsm_create_reader): Ditto. + (simple_reader_cb, base64_reader_cb): Adjust accordingly. + * decrypt.c (gpgsm_decrypt): Change OUT_FP and IN_FP to an estream_t. + * encrypt.c (gpgsm_encrypt): Change OUT_FP to an estream_t. Ditto + for DATA_FD. + (encrypt_cb): Use estream. + * gpgsm.c (main) <aEncr, aVerify, aSign, aDecrypt>: Use estream + functions. + (main) <aExport, aKeygen>: Use open_es_fwrite. + +2009-12-14 Werner Koch <wk@g10code.com> + + * server.c (cmd_passwd): New. + (register_commands): Register new command. + +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-08 Werner Koch <wk@g10code.com> + + * keydb.c (keydb_search_kid): Fix code even that it is not used. + (classify_user_id): Adjust for change of u.kid type. + (keydb_classify_name): Replace GPG_ERR_INV_NAME by + GPG_ERR_INV_USER_ID. + (keydb_classify_name): Remove. Replace all callers by + classify_user_id. + +2009-12-08 Marcus Brinkmann <marcus@g10code.de> + + * call-dirmngr.c (start_dirmngr_ext): Convert posix fd to assuan fd. + +2009-12-03 Werner Koch <wk@g10code.com> + + * gpgsm.c (set_debug): Allow for numerical debug leveles. Print + active debug flags. + +2009-12-02 Werner Koch <wk@g10code.com> + + * verify.c (gpgsm_verify): Add audit info on hash algorithms. + + * sign.c (gpgsm_sign): Add audit log calls. + (hash_data): Return an error indicator. + +2009-12-01 Werner Koch <wk@g10code.com> + + * decrypt.c (gpgsm_decrypt): Add audit log calls. + + * gpgsm.c: New option --html-audit-log. + +2009-11-25 Marcus Brinkmann <marcus@g10code.de> + + * server.c (gpgsm_server): Use assuan_fd_t and assuan_fdopen on + fds. + +2009-11-23 Werner Koch <wk@g10code.com> + + * gpgsm.c (main) <aGpgConfList>: Add key "default_pubkey_algo". + +2009-11-10 Marcus Brinkmann <marcus@g10code.de> + + * server.c (cmd_getauditlog): Don't dup FD for es_fdopen_nc as + this leaks the FD here. + +2009-11-05 Marcus Brinkmann <marcus@g10code.de> + + * call-dirmngr.c (start_dirmngr_ext): Update use of + assuan_pipe_connect and assuan_socket_connect. + +2009-11-04 Werner Koch <wk@g10code.com> + + * certreqgen.c (proc_parameters): Change fallback key length to + 2048. + + * server.c (register_commands): Add help arg to + assuan_register_command. Provide help strings for all commands. + +2009-11-02 Marcus Brinkmann <marcus@g10code.de> + + * server.c (reset_notify, input_notify, output_notify): Update to + new assuan interface. + (register_commands): Use assuan_handler_t. + * 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. + +2009-10-16 Werner Koch <wk@g10code.com> + + * gpgsm.c (default_include_certs): Change to -2. + (DEFAULT_INCLUDE_CERTS): New. + (DEFAULT_CIPHER_ALGO): New. Use instead of hardcoded "3DES". + +2009-09-30 Werner Koch <wk@g10code.com> + + * gpgsm.c (main): Remove obsolete GCRYCTL_DISABLE_INTERNAL_LOCKING. + +2009-09-23 Marcus Brinkmann <marcus@g10code.de> + + * 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. + (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): Same for member HANDLER in table. + (gpgsm_server): Allocate assuan context before starting server. + * sm/call-dirmngr.c: + * call-dirmngr.c (prepare_dirmngr): Check for CTX and error before + setting LDAPSERVER. + (start_dirmngr_ext): Allocate assuan context before starting + server. + (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-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, + 2010, 2011 Free Software Foundation, Inc. + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +Local Variables: +buffer-read-only: t +End: diff --git a/sm/Makefile.am b/sm/Makefile.am new file mode 100644 index 0000000..0bc7640 --- /dev/null +++ b/sm/Makefile.am @@ -0,0 +1,73 @@ +# 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 <https://www.gnu.org/licenses/>. + +## Process this file with automake to produce Makefile.in + +EXTRA_DIST = ChangeLog-2011 gpgsm-w32info.rc gpgsm.w32-manifest.in + +bin_PROGRAMS = gpgsm + +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) + +AM_CPPFLAGS = -DKEYBOX_WITH_X509=1 +include $(top_srcdir)/am/cmacros.am + +if HAVE_W32_SYSTEM +gpgsm_robjs = $(resource_objs) gpgsm-w32info.o +gpgsm-w32info.o : gpgsm.w32-manifest +else +gpgsm_robjs = +endif + +gpgsm_SOURCES = \ + gpgsm.c gpgsm.h \ + misc.c \ + keydb.c keydb.h \ + server.c \ + call-agent.c \ + call-dirmngr.c \ + fingerprint.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 \ + minip12.c minip12.h \ + qualified.c \ + passphrase.c passphrase.h + + +common_libs = ../kbx/libkeybox509.a $(libcommon) + +gpgsm_LDADD = $(common_libs) ../common/libgpgrl.a \ + $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) \ + $(GPG_ERROR_LIBS) $(LIBREADLINE) $(LIBINTL) \ + $(LIBICONV) $(gpgsm_robjs) $(extra_sys_libs) $(NETLIBS) +gpgsm_LDFLAGS = $(extra_bin_ldflags) + +# 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..286b652 --- /dev/null +++ b/sm/Makefile.in @@ -0,0 +1,907 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# Copyright (C) 2001, 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 <https://www.gnu.org/licenses/>. + +# cmacros.am - C macro definitions +# Copyright (C) 2004 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses/>. + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +bin_PROGRAMS = gpgsm$(EXEEXT) +@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@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LOCALSTATEDIR="\"$(localstatedir)\"" + + +# If a specific protect tool program has been defined, pass its name +# to cc. Note that these macros should not be used directly but via +# the gnupg_module_name function. +@GNUPG_AGENT_PGM_TRUE@am__append_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@\"" +@GNUPG_DIRMNGR_LDAP_PGM_TRUE@am__append_7 = -DGNUPG_DEFAULT_DIRMNGR_LDAP="\"@GNUPG_DIRMNGR_LDAP_PGM@\"" +subdir = sm +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/autobuild.m4 \ + $(top_srcdir)/m4/codeset.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/isc-posix.m4 $(top_srcdir)/m4/ksba.m4 \ + $(top_srcdir)/m4/lcmessage.m4 $(top_srcdir)/m4/ldap.m4 \ + $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \ + $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libassuan.m4 \ + $(top_srcdir)/m4/libgcrypt.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/npth.m4 $(top_srcdir)/m4/ntbtls.m4 \ + $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/m4/po.m4 \ + $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/readline.m4 \ + $(top_srcdir)/m4/socklen.m4 $(top_srcdir)/m4/sys_socket_h.m4 \ + $(top_srcdir)/m4/tar-ustar.m4 $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = gpgsm.w32-manifest +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) 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) \ + minip12.$(OBJEXT) qualified.$(OBJEXT) passphrase.$(OBJEXT) +gpgsm_OBJECTS = $(am_gpgsm_OBJECTS) +am__DEPENDENCIES_1 = +@HAVE_W32_SYSTEM_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) \ +@HAVE_W32_SYSTEM_TRUE@ gpgsm-w32info.o +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_2) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +gpgsm_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(gpgsm_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/call-agent.Po \ + ./$(DEPDIR)/call-dirmngr.Po ./$(DEPDIR)/certchain.Po \ + ./$(DEPDIR)/certcheck.Po ./$(DEPDIR)/certdump.Po \ + ./$(DEPDIR)/certlist.Po ./$(DEPDIR)/certreqgen-ui.Po \ + ./$(DEPDIR)/certreqgen.Po ./$(DEPDIR)/decrypt.Po \ + ./$(DEPDIR)/delete.Po ./$(DEPDIR)/encrypt.Po \ + ./$(DEPDIR)/export.Po ./$(DEPDIR)/fingerprint.Po \ + ./$(DEPDIR)/gpgsm.Po ./$(DEPDIR)/import.Po \ + ./$(DEPDIR)/keydb.Po ./$(DEPDIR)/keylist.Po \ + ./$(DEPDIR)/minip12.Po ./$(DEPDIR)/misc.Po \ + ./$(DEPDIR)/passphrase.Po ./$(DEPDIR)/qualified.Po \ + ./$(DEPDIR)/server.Po ./$(DEPDIR)/sign.Po \ + ./$(DEPDIR)/verify.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(gpgsm_SOURCES) +DIST_SOURCES = $(gpgsm_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(srcdir)/gpgsm.w32-manifest.in $(top_srcdir)/am/cmacros.am \ + $(top_srcdir)/build-aux/depcomp \ + $(top_srcdir)/build-aux/mkinstalldirs +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +AWK_HEX_NUMBER_OPTION = @AWK_HEX_NUMBER_OPTION@ +BUILD_FILEVERSION = @BUILD_FILEVERSION@ +BUILD_HOSTNAME = @BUILD_HOSTNAME@ +BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@ +BUILD_REVISION = @BUILD_REVISION@ +BUILD_TIMESTAMP = @BUILD_TIMESTAMP@ +BUILD_VERSION = @BUILD_VERSION@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DL_LIBS = @DL_LIBS@ +DNSLIBS = @DNSLIBS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENCFS = @ENCFS@ +EXEEXT = @EXEEXT@ +FUSERMOUNT = @FUSERMOUNT@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUPG_AGENT_PGM = @GNUPG_AGENT_PGM@ +GNUPG_DIRMNGR_LDAP_PGM = @GNUPG_DIRMNGR_LDAP_PGM@ +GNUPG_DIRMNGR_PGM = @GNUPG_DIRMNGR_PGM@ +GNUPG_PINENTRY_PGM = @GNUPG_PINENTRY_PGM@ +GNUPG_PROTECT_TOOL_PGM = @GNUPG_PROTECT_TOOL_PGM@ +GNUPG_SCDAEMON_PGM = @GNUPG_SCDAEMON_PGM@ +GPGKEYS_LDAP = @GPGKEYS_LDAP@ +GPGRT_CONFIG = @GPGRT_CONFIG@ +GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@ +GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@ +GPG_ERROR_LIBS = @GPG_ERROR_LIBS@ +GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@ +GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +KSBA_CFLAGS = @KSBA_CFLAGS@ +KSBA_CONFIG = @KSBA_CONFIG@ +KSBA_LIBS = @KSBA_LIBS@ +LBER_LIBS = @LBER_LIBS@ +LDAPLIBS = @LDAPLIBS@ +LDAP_CPPFLAGS = @LDAP_CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@ +LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@ +LIBASSUAN_LIBS = @LIBASSUAN_LIBS@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBGNUTLS_CFLAGS = @LIBGNUTLS_CFLAGS@ +LIBGNUTLS_LIBS = @LIBGNUTLS_LIBS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBOBJS = @LIBOBJS@ +LIBREADLINE = @LIBREADLINE@ +LIBS = @LIBS@ +LIBUSB_CPPFLAGS = @LIBUSB_CPPFLAGS@ +LIBUSB_LIBS = @LIBUSB_LIBS@ +LIBUTIL_LIBS = @LIBUTIL_LIBS@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NETLIBS = @NETLIBS@ +NPTH_CFLAGS = @NPTH_CFLAGS@ +NPTH_CONFIG = @NPTH_CONFIG@ +NPTH_LIBS = @NPTH_LIBS@ +NTBTLS_CFLAGS = @NTBTLS_CFLAGS@ +NTBTLS_CONFIG = @NTBTLS_CONFIG@ +NTBTLS_LIBS = @NTBTLS_LIBS@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_GT = @PACKAGE_GT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POSUB = @POSUB@ +RANLIB = @RANLIB@ +SENDMAIL = @SENDMAIL@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SHRED = @SHRED@ +SQLITE3_CFLAGS = @SQLITE3_CFLAGS@ +SQLITE3_LIBS = @SQLITE3_LIBS@ +STRIP = @STRIP@ +SYSROOT = @SYSROOT@ +SYS_SOCKET_H = @SYS_SOCKET_H@ +TAR = @TAR@ +USE_C99_CFLAGS = @USE_C99_CFLAGS@ +USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +W32SOCKLIBS = @W32SOCKLIBS@ +WINDRES = @WINDRES@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +YAT2M = @YAT2M@ +ZLIBS = @ZLIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = $(datadir)/locale +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +EXTRA_DIST = ChangeLog-2011 gpgsm-w32info.rc gpgsm.w32-manifest.in +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) + +# NB: AM_CFLAGS may also be used by tools running on the build +# platform to create source files. +AM_CPPFLAGS = -DKEYBOX_WITH_X509=1 -DLOCALEDIR=\"$(localedir)\" \ + $(am__append_1) $(am__append_2) $(am__append_3) \ + $(am__append_4) $(am__append_5) $(am__append_6) \ + $(am__append_7) +@HAVE_W32CE_SYSTEM_FALSE@extra_sys_libs = + +# Under Windows we use LockFileEx. WindowsCE provides this only on +# the WindowsMobile 6 platform and thus we need to use the coredll6 +# import library. We also want to use a stacksize of 256k instead of +# the 2MB which is the default with cegcc. 256k is the largest stack +# we use with pth. +@HAVE_W32CE_SYSTEM_TRUE@extra_sys_libs = -lcoredll6 +@HAVE_W32CE_SYSTEM_FALSE@extra_bin_ldflags = +@HAVE_W32CE_SYSTEM_TRUE@extra_bin_ldflags = -Wl,--stack=0x40000 +resource_objs = + +# Convenience macros +libcommon = ../common/libcommon.a +libcommonpth = ../common/libcommonpth.a +libcommontls = ../common/libcommontls.a +libcommontlsnpth = ../common/libcommontlsnpth.a +@HAVE_W32_SYSTEM_FALSE@gpgsm_robjs = +@HAVE_W32_SYSTEM_TRUE@gpgsm_robjs = $(resource_objs) gpgsm-w32info.o +gpgsm_SOURCES = \ + gpgsm.c gpgsm.h \ + misc.c \ + keydb.c keydb.h \ + server.c \ + call-agent.c \ + call-dirmngr.c \ + fingerprint.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 \ + minip12.c minip12.h \ + qualified.c \ + passphrase.c passphrase.h + +common_libs = ../kbx/libkeybox509.a $(libcommon) +gpgsm_LDADD = $(common_libs) ../common/libgpgrl.a \ + $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) \ + $(GPG_ERROR_LIBS) $(LIBREADLINE) $(LIBINTL) \ + $(LIBICONV) $(gpgsm_robjs) $(extra_sys_libs) $(NETLIBS) + +gpgsm_LDFLAGS = $(extra_bin_ldflags) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj .rc +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/am/cmacros.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu sm/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu sm/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; +$(top_srcdir)/am/cmacros.am $(am__empty): + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +gpgsm.w32-manifest: $(top_builddir)/config.status $(srcdir)/gpgsm.w32-manifest.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) + +gpgsm$(EXEEXT): $(gpgsm_OBJECTS) $(gpgsm_DEPENDENCIES) $(EXTRA_gpgsm_DEPENDENCIES) + @rm -f gpgsm$(EXEEXT) + $(AM_V_CCLD)$(gpgsm_LINK) $(gpgsm_OBJECTS) $(gpgsm_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/call-agent.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/call-dirmngr.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certchain.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certcheck.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certdump.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certlist.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certreqgen-ui.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certreqgen.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/decrypt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/delete.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/encrypt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/export.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fingerprint.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgsm.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/import.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keydb.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keylist.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/minip12.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passphrase.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/qualified.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sign.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/verify.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +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: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/call-agent.Po + -rm -f ./$(DEPDIR)/call-dirmngr.Po + -rm -f ./$(DEPDIR)/certchain.Po + -rm -f ./$(DEPDIR)/certcheck.Po + -rm -f ./$(DEPDIR)/certdump.Po + -rm -f ./$(DEPDIR)/certlist.Po + -rm -f ./$(DEPDIR)/certreqgen-ui.Po + -rm -f ./$(DEPDIR)/certreqgen.Po + -rm -f ./$(DEPDIR)/decrypt.Po + -rm -f ./$(DEPDIR)/delete.Po + -rm -f ./$(DEPDIR)/encrypt.Po + -rm -f ./$(DEPDIR)/export.Po + -rm -f ./$(DEPDIR)/fingerprint.Po + -rm -f ./$(DEPDIR)/gpgsm.Po + -rm -f ./$(DEPDIR)/import.Po + -rm -f ./$(DEPDIR)/keydb.Po + -rm -f ./$(DEPDIR)/keylist.Po + -rm -f ./$(DEPDIR)/minip12.Po + -rm -f ./$(DEPDIR)/misc.Po + -rm -f ./$(DEPDIR)/passphrase.Po + -rm -f ./$(DEPDIR)/qualified.Po + -rm -f ./$(DEPDIR)/server.Po + -rm -f ./$(DEPDIR)/sign.Po + -rm -f ./$(DEPDIR)/verify.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/call-agent.Po + -rm -f ./$(DEPDIR)/call-dirmngr.Po + -rm -f ./$(DEPDIR)/certchain.Po + -rm -f ./$(DEPDIR)/certcheck.Po + -rm -f ./$(DEPDIR)/certdump.Po + -rm -f ./$(DEPDIR)/certlist.Po + -rm -f ./$(DEPDIR)/certreqgen-ui.Po + -rm -f ./$(DEPDIR)/certreqgen.Po + -rm -f ./$(DEPDIR)/decrypt.Po + -rm -f ./$(DEPDIR)/delete.Po + -rm -f ./$(DEPDIR)/encrypt.Po + -rm -f ./$(DEPDIR)/export.Po + -rm -f ./$(DEPDIR)/fingerprint.Po + -rm -f ./$(DEPDIR)/gpgsm.Po + -rm -f ./$(DEPDIR)/import.Po + -rm -f ./$(DEPDIR)/keydb.Po + -rm -f ./$(DEPDIR)/keylist.Po + -rm -f ./$(DEPDIR)/minip12.Po + -rm -f ./$(DEPDIR)/misc.Po + -rm -f ./$(DEPDIR)/passphrase.Po + -rm -f ./$(DEPDIR)/qualified.Po + -rm -f ./$(DEPDIR)/server.Po + -rm -f ./$(DEPDIR)/sign.Po + -rm -f ./$(DEPDIR)/verify.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-binPROGRAMS clean-generic cscopelist-am ctags ctags-am \ + distclean distclean-compile distclean-generic distclean-tags \ + distdir dvi dvi-am html html-am info info-am install \ + install-am install-binPROGRAMS install-data install-data-am \ + install-dvi install-dvi-am install-exec install-exec-am \ + install-html install-html-am install-info install-info-am \ + install-man install-pdf install-pdf-am install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic pdf pdf-am \ + ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-binPROGRAMS + +.PRECIOUS: Makefile + + +@HAVE_W32_SYSTEM_TRUE@.rc.o: +@HAVE_W32_SYSTEM_TRUE@ $(WINDRES) $(DEFAULT_INCLUDES) $(INCLUDES) "$<" "$@" +@HAVE_W32_SYSTEM_TRUE@gpgsm-w32info.o : gpgsm.w32-manifest + +# 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/call-agent.c b/sm/call-agent.c new file mode 100644 index 0000000..0c271d9 --- /dev/null +++ b/sm/call-agent.c @@ -0,0 +1,1455 @@ +/* call-agent.c - Divert GPGSM operations to the agent + * Copyright (C) 2001, 2002, 2003, 2005, 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 <https://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 "../common/i18n.h" +#include "../common/asshelp.h" +#include "keydb.h" /* fixme: Move this to import.c */ +#include "../common/membuf.h" +#include "../common/shareddefs.h" +#include "passphrase.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; +}; + +struct import_key_parm_s +{ + ctrl_t ctrl; + assuan_context_t ctx; + const void *key; + size_t keylen; +}; + +struct default_inq_parm_s +{ + ctrl_t ctrl; + assuan_context_t ctx; +}; + + +/* Print a warning if the server's version number is less than our + version number. Returns an error code on a connection problem. */ +static gpg_error_t +warn_version_mismatch (ctrl_t ctrl, assuan_context_t ctx, + const char *servername, int mode) +{ + gpg_error_t err; + char *serverversion; + const char *myversion = strusage (13); + + err = get_assuan_server_version (ctx, mode, &serverversion); + if (err) + log_error (_("error getting version from '%s': %s\n"), + servername, gpg_strerror (err)); + else if (compare_version_strings (serverversion, myversion) < 0) + { + char *warn; + + warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), + servername, serverversion, myversion); + if (!warn) + err = gpg_error_from_syserror (); + else + { + log_info (_("WARNING: %s\n"), warn); + if (!opt.quiet) + { + log_info (_("Note: Outdated servers may lack important" + " security fixes.\n")); + log_info (_("Note: Use the command \"%s\" to restart them.\n"), + "gpgconf --kill all"); + } + gpgsm_status2 (ctrl, STATUS_WARNING, "server_version_mismatch 0", + warn, NULL); + xfree (warn); + } + } + xfree (serverversion); + return err; +} + + +/* 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.agent_program, + opt.lc_ctype, opt.lc_messages, + opt.session_env, + opt.autostart, opt.verbose, DBG_IPC, + gpgsm_status2, ctrl); + + if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT) + { + static int shown; + + if (!shown) + { + shown = 1; + log_info (_("no gpg-agent running in this session\n")); + } + } + else if (!rc && !(rc = warn_version_mismatch (ctrl, agent_ctx, + GPG_AGENT_NAME, 0))) + { + /* Tell the agent that we support Pinentry notifications. No + error checking so that it will work also with older + agents. */ + assuan_transact (agent_ctx, "OPTION allow-pinentry-notify", + NULL, NULL, NULL, NULL, NULL, NULL); + + /* Pass on the pinentry mode. */ + if (opt.pinentry_mode) + { + char *tmp = xasprintf ("OPTION pinentry-mode=%s", + str_pinentry_mode (opt.pinentry_mode)); + rc = assuan_transact (agent_ctx, tmp, + NULL, NULL, NULL, NULL, NULL, NULL); + xfree (tmp); + if (rc) + log_error ("setting pinentry mode '%s' failed: %s\n", + str_pinentry_mode (opt.pinentry_mode), + gpg_strerror (rc)); + } + + /* Pass on the request origin. */ + if (opt.request_origin) + { + char *tmp = xasprintf ("OPTION pretend-request-origin=%s", + str_request_origin (opt.request_origin)); + rc = assuan_transact (agent_ctx, tmp, + NULL, NULL, NULL, NULL, NULL, NULL); + xfree (tmp); + if (rc) + log_error ("setting request origin '%s' failed: %s\n", + str_request_origin (opt.request_origin), + gpg_strerror (rc)); + } + + /* In DE_VS mode under Windows we require that the JENT RNG + * is active. */ +#ifdef HAVE_W32_SYSTEM + if (!rc && opt.compliance == CO_DE_VS) + { + if (assuan_transact (agent_ctx, "GETINFO jent_active", + NULL, NULL, NULL, NULL, NULL, NULL)) + { + rc = gpg_error (GPG_ERR_FORBIDDEN); + log_error (_("%s is not compliant with %s mode\n"), + GPG_AGENT_NAME, + gnupg_compliance_option_string (opt.compliance)); + gpgsm_status_with_error (ctrl, STATUS_ERROR, + "random-compliance", rc); + } + } +#endif /*HAVE_W32_SYSTEM*/ + + } + } + + if (!ctrl->agent_seen) + { + ctrl->agent_seen = 1; + audit_log_ok (ctrl->audit, AUDIT_AGENT_READY, rc); + } + + return rc; +} + +/* This is the default inquiry callback. It mainly handles the + Pinentry notifications. */ +static gpg_error_t +default_inq_cb (void *opaque, const char *line) +{ + gpg_error_t err = 0; + struct default_inq_parm_s *parm = opaque; + ctrl_t ctrl = parm->ctrl; + + if (has_leading_keyword (line, "PINENTRY_LAUNCHED")) + { + 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 if ((has_leading_keyword (line, "PASSPHRASE") + || has_leading_keyword (line, "NEW_PASSPHRASE")) + && opt.pinentry_mode == PINENTRY_MODE_LOOPBACK + && have_static_passphrase ()) + { + const char *s = get_static_passphrase (); + err = assuan_send_data (parm->ctx, s, strlen (s)); + } + else + log_error ("ignoring gpg-agent inquiry '%s'\n", line); + + return err; +} + + + + +/* 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; + struct default_inq_parm_s inq_parm; + + *r_buf = NULL; + rc = start_agent (ctrl); + if (rc) + return rc; + inq_parm.ctrl = ctrl; + inq_parm.ctx = agent_ctx; + + 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), "SIGKEY %s", keygrip); + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + 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", + put_membuf_cb, &data, default_inq_cb, &inq_parm, + 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; + struct default_inq_parm_s inq_parm; + + (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; + inq_parm.ctrl = ctrl; + inq_parm.ctx = agent_ctx; + + 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), "SCD PKSIGN %s %s", hashopt, keyid); + rc = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, default_inq_cb, &inq_parm, + 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 takes care of flushing and writing the end */ +static gpg_error_t +inq_ciphertext_cb (void *opaque, const char *line) +{ + struct cipher_parm_s *parm = opaque; + int rc; + + if (has_leading_keyword (line, "CIPHERTEXT")) + { + assuan_begin_confidential (parm->ctx); + rc = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen); + assuan_end_confidential (parm->ctx); + } + else + { + struct default_inq_parm_s inq_parm = { parm->ctrl, parm->ctx }; + rc = default_inq_cb (&inq_parm, 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), "SETKEY %s", keygrip); + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + 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", + put_membuf_cb, &data, + inq_ciphertext_cb, &cipher_parm, NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return rc; + } + + /* Make sure it is 0 terminated so we can invoke strtoul safely. */ + put_membuf (&data, "", 1); + 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); + /* Trim any spurious trailing Nuls: */ + while (buf[len-1] == 0) + len--; + if (buf[len-1] != ')') + return gpg_error (GPG_ERR_INV_SEXP); + len--; /* Drop the final close-paren: */ + p = buf + 8; /* Skip leading parenthesis and the value tag. */ + len -= 8; /* Count only the data of the second part. */ + } + 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 (has_leading_keyword (line, "KEYPARAM")) + { + rc = assuan_send_data (parm->ctx, parm->sexp, parm->sexplen); + } + else + { + struct default_inq_parm_s inq_parm = { parm->ctrl, parm->ctx }; + rc = default_inq_cb (&inq_parm, line); + } + + return rc; +} + + + +/* Call the agent to generate a new key */ +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; + gnupg_isotime_t timebuf; + 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; + + 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); + gnupg_get_isotime (timebuf); + snprintf (line, sizeof line, "GENKEY --timestamp=%s", timebuf); + rc = assuan_transact (agent_ctx, line, + put_membuf_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]; + struct default_inq_parm_s inq_parm; + + *r_pubkey = NULL; + rc = start_agent (ctrl); + if (rc) + return rc; + inq_parm.ctrl = ctrl; + inq_parm.ctx = agent_ctx; + + rc = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + + snprintf (line, DIM(line), "%sREADKEY %s", + fromcard? "SCD ":"", hexkeygrip); + + init_membuf (&data, 1024); + rc = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &inq_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; +} + + + +/* 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 function. */ +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; + struct default_inq_parm_s inq_parm; + + *r_serialno = NULL; + rc = start_agent (ctrl); + if (rc) + return rc; + inq_parm.ctrl = ctrl; + inq_parm.ctx = agent_ctx; + + rc = assuan_transact (agent_ctx, "SCD SERIALNO", + NULL, NULL, + default_inq_cb, &inq_parm, + 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 function. */ +static gpg_error_t +scd_keypairinfo_status_cb (void *opaque, const char *line) +{ + strlist_t *listaddr = opaque; + const char *keyword = line; + int keywordlen; + strlist_t sl; + char *p; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) + { + sl = append_to_strlist (listaddr, line); + p = sl->d; + /* Make sure that we only have two tokens so that future + * extensions of the format won't change the format expected by + * the caller. */ + while (*p && !spacep (p)) + p++; + if (*p) + { + while (spacep (p)) + p++; + while (*p && !spacep (p)) + p++; + if (*p) + { + *p++ = 0; + while (spacep (p)) + p++; + while (*p && !spacep (p)) + { + switch (*p++) + { + case 'c': sl->flags |= GCRY_PK_USAGE_CERT; break; + case 's': sl->flags |= GCRY_PK_USAGE_SIGN; break; + case 'e': sl->flags |= GCRY_PK_USAGE_ENCR; break; + case 'a': sl->flags |= GCRY_PK_USAGE_AUTH; break; + } + } + } + } + } + + return 0; +} + + +/* 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. The flags of the string carry the usage bits. */ +int +gpgsm_agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list) +{ + int rc; + strlist_t list = NULL; + struct default_inq_parm_s inq_parm; + + *r_list = NULL; + rc = start_agent (ctrl); + if (rc) + return rc; + inq_parm.ctrl = ctrl; + inq_parm.ctx = agent_ctx; + + rc = assuan_transact (agent_ctx, "SCD LEARN --keypairinfo", + NULL, NULL, + default_inq_cb, &inq_parm, + 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; + const char *s; + + if ((s = has_leading_keyword (line, "TRUSTLISTFLAG"))) + { + line = s; + if (has_leading_keyword (line, "relax")) + flags->relax = 1; + else if (has_leading_keyword (line, "cm")) + 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), "ISTRUSTED %s", hexfpr); + } + 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), "ISTRUSTED %s", fpr); + 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]; + struct default_inq_parm_s inq_parm; + + rc = start_agent (ctrl); + if (rc) + return rc; + inq_parm.ctrl = ctrl; + inq_parm.ctx = agent_ctx; + + 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), "MARKTRUSTED %s S %s", fpr, dnfmt); + ksba_free (dnfmt); + xfree (fpr); + + rc = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, &inq_parm, 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), "HAVEKEY %s", hexkeygrip); + + 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; + const char *s; + + /* Pass progress data to the caller. */ + if ((s = has_leading_keyword (line, "PROGRESS"))) + { + line = s; + if (parm->ctrl) + { + 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; + char *string, *p, *pend; + strlist_t sl; + + 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; + } + + /* Ignore certificates matching certain extended usage flags. */ + rc = ksba_cert_get_ext_key_usages (cert, &string); + if (!rc) + { + p = string; + while (p && (pend=strchr (p, ':'))) + { + *pend++ = 0; + for (sl=opt.ignore_cert_with_oid; + sl && strcmp (sl->d, p); sl = sl->next) + ; + if (sl) + { + if (opt.verbose) + log_info ("certificate ignored due to OID %s\n", sl->d); + goto leave; + } + p = pend; + if ((p = strchr (p, '\n'))) + p++; + } + } + else if (gpg_err_code (rc) != GPG_ERR_NO_DATA) + log_error (_("error getting key usage information: %s\n"), + gpg_strerror (rc)); + xfree (string); + string = NULL; + + + /* 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 (parm->ctrl, 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"); + } + } + + leave: + xfree (string); + string = NULL; + 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; + + rc = warn_version_mismatch (ctrl, agent_ctx, SCDAEMON_NAME, 2); + 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]; + struct default_inq_parm_s inq_parm; + + rc = start_agent (ctrl); + if (rc) + return rc; + inq_parm.ctrl = ctrl; + inq_parm.ctx = agent_ctx; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + rc = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + } + + snprintf (line, DIM(line), "PASSWD %s", hexkeygrip); + + rc = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, &inq_parm, 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]; + struct default_inq_parm_s inq_parm; + + rc = start_agent (ctrl); + if (rc) + return rc; + inq_parm.ctrl = ctrl; + inq_parm.ctx = agent_ctx; + + snprintf (line, DIM(line), "GET_CONFIRMATION %s", desc); + + rc = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, &inq_parm, 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 ((s = has_leading_keyword (line, "KEYINFO")) && !*serialno) + { + s = strchr (s, ' '); + 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), "KEYINFO %s", hexkeygrip); + + 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; +} + + + +/* Ask for the passphrase (this is used for pkcs#12 import/export. On + success the caller needs to free the string stored at R_PASSPHRASE. + On error NULL will be stored at R_PASSPHRASE and an appropriate + error code returned. If REPEAT is true the agent tries to get a + new passphrase (i.e. asks the user to confirm it). */ +gpg_error_t +gpgsm_agent_ask_passphrase (ctrl_t ctrl, const char *desc_msg, int repeat, + char **r_passphrase) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + char *arg4 = NULL; + membuf_t data; + struct default_inq_parm_s inq_parm; + + *r_passphrase = NULL; + + err = start_agent (ctrl); + if (err) + return err; + inq_parm.ctrl = ctrl; + inq_parm.ctx = agent_ctx; + + if (desc_msg && *desc_msg && !(arg4 = percent_plus_escape (desc_msg))) + return gpg_error_from_syserror (); + + snprintf (line, DIM(line), "GET_PASSPHRASE --data%s -- X X X %s", + repeat? " --repeat=1 --check":"", + arg4); + xfree (arg4); + + init_membuf_secure (&data, 64); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &inq_parm, NULL, NULL); + + if (err) + xfree (get_membuf (&data, NULL)); + else + { + put_membuf (&data, "", 1); + *r_passphrase = get_membuf (&data, NULL); + if (!*r_passphrase) + err = gpg_error_from_syserror (); + } + return err; +} + + + +/* Retrieve a key encryption key from the agent. With FOREXPORT true + the key shall be use for export, with false for import. On success + the new key is stored at R_KEY and its length at R_KEKLEN. */ +gpg_error_t +gpgsm_agent_keywrap_key (ctrl_t ctrl, int forexport, + void **r_kek, size_t *r_keklen) +{ + gpg_error_t err; + membuf_t data; + size_t len; + unsigned char *buf; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s inq_parm; + + *r_kek = NULL; + err = start_agent (ctrl); + if (err) + return err; + inq_parm.ctrl = ctrl; + inq_parm.ctx = agent_ctx; + + snprintf (line, DIM(line), "KEYWRAP_KEY %s", + forexport? "--export":"--import"); + + init_membuf_secure (&data, 64); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &inq_parm, NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error_from_syserror (); + *r_kek = buf; + *r_keklen = len; + return 0; +} + + + + +/* Handle the inquiry for an IMPORT_KEY command. */ +static gpg_error_t +inq_import_key_parms (void *opaque, const char *line) +{ + struct import_key_parm_s *parm = opaque; + gpg_error_t err; + + if (has_leading_keyword (line, "KEYDATA")) + { + assuan_begin_confidential (parm->ctx); + err = assuan_send_data (parm->ctx, parm->key, parm->keylen); + assuan_end_confidential (parm->ctx); + } + else + { + struct default_inq_parm_s inq_parm = { parm->ctrl, parm->ctx }; + err = default_inq_cb (&inq_parm, line); + } + + return err; +} + + +/* Call the agent to import a key into the agent. */ +gpg_error_t +gpgsm_agent_import_key (ctrl_t ctrl, const void *key, size_t keylen) +{ + gpg_error_t err; + struct import_key_parm_s parm; + gnupg_isotime_t timebuf; + char line[ASSUAN_LINELENGTH]; + + err = start_agent (ctrl); + if (err) + return err; + + parm.ctrl = ctrl; + parm.ctx = agent_ctx; + parm.key = key; + parm.keylen = keylen; + + gnupg_get_isotime (timebuf); + snprintf (line, sizeof line, "IMPORT_KEY --timestamp=%s", timebuf); + err = assuan_transact (agent_ctx, line, + NULL, NULL, inq_import_key_parms, &parm, NULL, NULL); + return err; +} + + + +/* Receive a secret key from the agent. KEYGRIP is the hexified + keygrip, DESC a prompt to be displayed with the agent's passphrase + question (needs to be plus+percent escaped). On success the key is + stored as a canonical S-expression at R_RESULT and R_RESULTLEN. */ +gpg_error_t +gpgsm_agent_export_key (ctrl_t ctrl, const char *keygrip, const char *desc, + unsigned char **r_result, size_t *r_resultlen) +{ + gpg_error_t err; + membuf_t data; + size_t len; + unsigned char *buf; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s inq_parm; + + *r_result = NULL; + + err = start_agent (ctrl); + if (err) + return err; + inq_parm.ctrl = ctrl; + inq_parm.ctx = agent_ctx; + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + snprintf (line, DIM(line), "EXPORT_KEY %s", keygrip); + + init_membuf_secure (&data, 1024); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &inq_parm, NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error_from_syserror (); + *r_result = buf; + *r_resultlen = len; + return 0; +} diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c new file mode 100644 index 0000000..1a411f2 --- /dev/null +++ b/sm/call-dirmngr.c @@ -0,0 +1,1099 @@ +/* call-dirmngr.c - Communication with the dirmngr + * Copyright (C) 2002, 2003, 2005, 2007, 2008, + * 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <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 "../common/i18n.h" +#include "keydb.h" +#include "../common/asshelp.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; + +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 { + ctrl_t ctrl; + 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; +} + + +/* Print a warning if the server's version number is less than our + version number. Returns an error code on a connection problem. */ +static gpg_error_t +warn_version_mismatch (ctrl_t ctrl, assuan_context_t ctx, + const char *servername, int mode) +{ + gpg_error_t err; + char *serverversion; + const char *myversion = strusage (13); + + err = get_assuan_server_version (ctx, mode, &serverversion); + if (err) + log_error (_("error getting version from '%s': %s\n"), + servername, gpg_strerror (err)); + else if (compare_version_strings (serverversion, myversion) < 0) + { + char *warn; + + warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), + servername, serverversion, myversion); + if (!warn) + err = gpg_error_from_syserror (); + else + { + log_info (_("WARNING: %s\n"), warn); + if (!opt.quiet) + { + log_info (_("Note: Outdated servers may lack important" + " security fixes.\n")); + log_info (_("Note: Use the command \"%s\" to restart them.\n"), + "gpgconf --kill all"); + } + gpgsm_status2 (ctrl, STATUS_WARNING, "server_version_mismatch 0", + warn, NULL); + xfree (warn); + } + } + xfree (serverversion); + return err; +} + + +/* 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) +{ + strlist_t server; + + if (!err) + err = warn_version_mismatch (ctrl, ctx, DIRMNGR_NAME, 0); + + 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]; + + /* If the host is "ldap" we prefix the entire line with "ldap:" + * to avoid an ambiguity on the server due to the introduction + * of this optional prefix. */ + snprintf (line, DIM (line), "LDAPSERVER %s%s", + !strncmp (server->d, "ldap:", 5)? "ldap:":"", + server->d); + + assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + /* The code below is not required because we don't return an error. */ + /* err = [above call] */ + /* if (gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD) */ + /* err = 0; /\* Allow the use of old dirmngr versions. *\/ */ + + server = server->next; + } +} + + + +/* Return a new assuan context for a Dirmngr connection. */ +static gpg_error_t +start_dirmngr_ext (ctrl_t ctrl, assuan_context_t *ctx_r) +{ + gpg_error_t err; + assuan_context_t ctx; + + if (opt.disable_dirmngr || ctrl->offline) + 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. */ + + err = start_new_dirmngr (&ctx, GPG_ERR_SOURCE_DEFAULT, + opt.dirmngr_program, + opt.autostart, opt.verbose, DBG_IPC, + gpgsm_status2, ctrl); + if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_DIRMNGR) + { + static int shown; + + if (!shown) + { + shown = 1; + log_info (_("no dirmngr running in this session\n")); + } + } + prepare_dirmngr (ctrl, ctx, err); + if (err) + return err; + + *ctx_r = ctx; + 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 existence 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; + const char *s; + int rc; + size_t n; + const unsigned char *der; + size_t derlen; + int issuer_mode = 0; + ksba_sexp_t ski = NULL; + + if ((s = has_leading_keyword (line, "SENDCERT"))) + { + line = s; + } + else if ((s = has_leading_keyword (line, "SENDCERT_SKI"))) + { + /* Send a certificate where a sourceKeyIdentifier is included. */ + line = s; + ski = make_simple_sexp_from_hexstr (line, &n); + line += n; + while (*line == ' ') + line++; + } + else if ((s = has_leading_keyword (line, "SENDISSUERCERT"))) + { + line = s; + issuer_mode = 1; + } + else if ((s = has_leading_keyword (line, "ISTRUSTED"))) + { + /* The server is asking us whether the certificate is a trusted + root certificate. */ + char fpr[41]; + struct rootca_flags_s rootca_flags; + + line = s; + + 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 certificate 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 (parm->ctrl, line, ski, &cert, 1); + 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 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). */ + 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; + const char *s; + + if ((s = has_leading_keyword (line, "PROGRESS"))) + { + if (parm->ctrl) + { + line = s; + if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line)) + return gpg_error (GPG_ERR_ASS_CANCELED); + } + } + else if ((s = has_leading_keyword (line, "ONLY_VALID_IF_CERT_VALID"))) + { + parm->seen++; + if (!*s || !unhexify_fpr (s, 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 but fallback to CRL unless CRLS are disabled. + 2 = Do only 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, *certfpr; + char line[ASSUAN_LINELENGTH]; + struct inq_certificate_parm_s parm; + struct isvalid_status_parm_s stparm; + + keydb_close_all_files (); + + rc = start_dirmngr (ctrl); + if (rc) + return rc; + + certfpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + 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); + + /* 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), "ISVALID%s%s %s%s%s", + use_ocsp == 2 || opt.no_crl_check ? " --only-ocsp":"", + use_ocsp == 2? " --force-default-responder":"", + certid, + use_ocsp? " ":"", + use_ocsp? certfpr:""); + xfree (certid); + xfree (certfpr); + + 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"); + + 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 (); + if (!kh) + rc = gpg_error (GPG_ERR_ENOMEM); + if (!rc) + rc = keydb_search_fpr (ctrl, 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; + const char *s; + + if ((s = has_leading_keyword (line, "PROGRESS"))) + { + if (parm->ctrl) + { + line = s; + if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line)) + return gpg_error (GPG_ERR_ASS_CANCELED); + } + } + else if ((s = has_leading_keyword (line, "TRUNCATED"))) + { + if (parm->ctrl) + { + line = s; + 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 or from URI. 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, const char *uri, + int cache_only, + void (*cb)(void*, ksba_cert_t), void *cb_value) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct lookup_parm_s parm; + size_t len; + assuan_context_t ctx; + const char *s; + + if ((names && uri) || (!names && !uri)) + return gpg_error (GPG_ERR_INV_ARG); + + keydb_close_all_files (); + + /* 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"); + } + + if (names) + { + char *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), "LOOKUP%s %s", + cache_only? " --cache-only":"", pattern); + xfree (pattern); + } + else + { + for (s=uri; *s; s++) + if (*s <= ' ') + { + if (ctx == dirmngr_ctx) + release_dirmngr (ctrl); + else + release_dirmngr2 (ctrl); + return gpg_error (GPG_ERR_INV_URI); + } + snprintf (line, DIM(line), "LOOKUP --url %s", uri); + } + + 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 = 0; + ksba_cert_t cert; + + *r_cert = NULL; + + bin2hex (fpr, 20, hexfpr); + snprintf (line, DIM(line), "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; + const char *s; + int rc = 0; + + if ((s = has_leading_keyword (line, "SENDCERT"))) + { /* send the given certificate */ + int err; + ksba_cert_t cert; + const unsigned char *der; + size_t derlen; + + line = s; + if (!*line) + return gpg_error (GPG_ERR_ASS_PARAMETER); + + err = gpgsm_find_cert (parm->ctrl, line, NULL, &cert, 1); + 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 ((s = has_leading_keyword (line, "PRINTINFO"))) + { /* Simply show the message given in the argument. */ + line = s; + log_info ("dirmngr: %s\n", line); + } + else if ((s = has_leading_keyword (line, "ISTRUSTED"))) + { + /* The server is asking us whether the certificate is a trusted + root certificate. */ + char fpr[41]; + struct rootca_flags_s rootca_flags; + int n; + + line = s; + + 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 command 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; + const char *s; + + if (opt.verbose) + { + log_info ("dirmngr status: %s\n", line); + } + if ((s = has_leading_keyword (line, "PROGRESS"))) + { + if (ctrl) + { + line = s; + 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; + + keydb_close_all_files (); + + rc = start_dirmngr (ctrl); + if (rc) + return rc; + + parm.ctrl = ctrl; + 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..d2a1800 --- /dev/null +++ b/sm/certchain.c @@ -0,0 +1,2380 @@ +/* certchain.c - certificate chain validation + * Copyright (C) 2001, 2002, 2003, 2004, 2005, + * 2006, 2007, 2008, 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <stdarg.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */ +#include "../common/i18n.h" +#include "../common/tlv.h" + + +/* The OID for the authorityInfoAccess's caIssuers. */ +static const char oidstr_caIssuers[] = "1.3.6.1.5.5.7.48.2"; + + +/* 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? GPGRT_LOG_ERROR: GPGRT_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 supported. Check the list + of to be ignored extensions to see 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 allowed 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; + estream_t 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 = es_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 (!es_fgets (line, DIM(line)-1, fp) ) + { + gpg_error_t tmperr = gpg_error_from_syserror (); + + xfree (policies); + if (es_feof (fp)) + { + es_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); + } + es_fclose (fp); + return tmperr; + } + + if (!*line || line[strlen(line)-1] != '\n') + { + /* eat until end of line */ + while ((c = es_getc (fp)) != EOF && c != '\n') + ; + es_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. Note that the line has always a LF and spacep + does not consider a LF a space. Thus strpbrk will always + succeed. */ + for (allowed=line; spacep (allowed); allowed++) + ; + p = strpbrk (allowed, " :\n"); + if (!*p || p == allowed) + { + es_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. */ + es_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 (ctrl_t ctrl, KEYDB_HANDLE kh, + const char *issuer, ksba_sexp_t keyid) +{ + int rc; + ksba_cert_t cert = NULL; + ksba_sexp_t subj = NULL; + ksba_isotime_t not_before, not_after, last_not_before, ne_last_not_before; + ksba_cert_t found_cert = NULL; + ksba_cert_t ne_found_cert = NULL; + + keydb_search_reset (kh); + while (!(rc = keydb_search_subject (ctrl, 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; + goto leave; + } + xfree (subj); + if (!ksba_cert_get_subj_key_id (cert, NULL, &subj)) + { + if (!cmp_simple_canon_sexp (keyid, subj)) + { + /* Found matching cert. */ + rc = ksba_cert_get_validity (cert, 0, not_before); + if (!rc) + rc = ksba_cert_get_validity (cert, 1, not_after); + if (rc) + { + log_error ("keydb_get_validity() failed: rc=%d\n", rc); + rc = -1; + goto leave; + } + + if (!found_cert + || strcmp (last_not_before, not_before) < 0) + { + /* This certificate is the first one found or newer + * than the previous one. This copes with + * re-issuing CA certificates while keeping the same + * key information. */ + gnupg_copy_time (last_not_before, not_before); + ksba_cert_release (found_cert); + ksba_cert_ref ((found_cert = cert)); + keydb_push_found_state (kh); + } + + if (*not_after && strcmp (ctrl->current_time, not_after) > 0 ) + ; /* CERT has expired - don't consider it. */ + else if (!ne_found_cert + || strcmp (ne_last_not_before, not_before) < 0) + { + /* This certificate is the first non-expired one + * found or newer than the previous non-expired one. */ + gnupg_copy_time (ne_last_not_before, not_before); + ksba_cert_release (ne_found_cert); + ksba_cert_ref ((ne_found_cert = cert)); + } + } + } + } + + if (!found_cert) + goto leave; + + /* Take the last saved one. Note that push/pop_found_state are + * misnomers because there is no stack of states. Renaming them to + * save/restore_found_state would be better. */ + keydb_pop_found_state (kh); + rc = 0; /* Ignore EOF or other error after the first cert. */ + + /* We need to consider some corner cases. It is possible that we + * have a long term certificate (e.g. valid from 2008 to 2033) as + * well as a re-issued (i.e. using the same key material) short term + * certificate (say from 2016 to 2019). Using the short term + * certificate is the proper solution. But we need to take care if + * there is no re-issued new short term certificate (e.g. from 2020 + * to 2023) available. In that case it is better to use the long + * term certificate which is still valid. The code may run into + * minor problems in the case of the chain validation mode. Given + * that this corner case is due to non-diligent PKI management we + * ignore this problem. */ + + /* The most common case is that the found certificate is not expired + * and thus identical to the one found from the list of non-expired + * certs. We can stop here. */ + if (found_cert == ne_found_cert) + goto leave; + /* If we do not have a non expired certificate the actual cert is + * expired and we can also stop here. */ + if (!ne_found_cert) + goto leave; + /* Now we need to see whether the found certificate is expired and + * only in this case we return the certificate found in the list of + * non-expired certs. */ + rc = ksba_cert_get_validity (found_cert, 1, not_after); + if (rc) + { + log_error ("keydb_get_validity() failed: rc=%d\n", rc); + rc = -1; + goto leave; + } + if (*not_after && strcmp (ctrl->current_time, not_after) > 0 ) + { /* CERT has expired. Use the NE_FOUND_CERT. Because we have no + * found state for this we need to search for it again. */ + unsigned char fpr[20]; + + gpgsm_get_fingerprint (ne_found_cert, GCRY_MD_SHA1, fpr, NULL); + keydb_search_reset (kh); + rc = keydb_search_fpr (ctrl, kh, fpr); + if (rc) + { + log_error ("keydb_search_fpr() failed: rc=%d\n", rc); + rc = -1; + goto leave; + } + /* Ready. The NE_FOUND_CERT is availabale via keydb_get_cert. */ + } + + leave: + ksba_cert_release (found_cert); + ksba_cert_release (ne_found_cert); + ksba_cert_release (cert); + xfree (subj); + return rc? -1:0; +} + + +struct find_up_store_certs_s +{ + ctrl_t ctrl; + int count; + unsigned int want_fpr:1; + unsigned int got_fpr:1; + unsigned char fpr[20]; +}; + +static void +find_up_store_certs_cb (void *cb_value, ksba_cert_t cert) +{ + struct find_up_store_certs_s *parm = cb_value; + + if (keydb_store_cert (parm->ctrl, cert, 1, NULL)) + log_error ("error storing issuer certificate as ephemeral\n"); + else if (parm->want_fpr && !parm->got_fpr) + { + if (!gpgsm_get_fingerprint (cert, 0, parm->fpr, NULL)) + log_error (_("failed to get the fingerprint\n")); + else + parm->got_fpr = 1; + } + parm->count++; +} + + +/* 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; + struct find_up_store_certs_s find_up_store_certs_parm; + char *pattern; + const char *s; + + find_up_store_certs_parm.ctrl = ctrl; + find_up_store_certs_parm.want_fpr = 0; + find_up_store_certs_parm.got_fpr = 0; + find_up_store_certs_parm.count = 0; + + 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, NULL, 0, find_up_store_certs_cb, + &find_up_store_certs_parm); + free_strlist (names); + + if (opt.verbose) + log_info (_("number of issuers matching: %d\n"), + find_up_store_certs_parm.count); + if (rc) + { + log_error ("external key lookup failed: %s\n", gpg_strerror (rc)); + rc = -1; + } + else if (!find_up_store_certs_parm.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 (ctrl, kh, issuer, keyid); + else + { + keydb_search_reset (kh); + rc = keydb_search_subject (ctrl, kh, issuer); + } + keydb_set_ephemeral (kh, old); + } + return rc; +} + + +/* Helper for find_up(). Locate the certificate for CERT using the + * caIssuer from the authorityInfoAccess. 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(). If no + * suitable authorityInfoAccess is encoded in the certificate + * GPG_ERR_NOT_FOUND is returned. */ +static gpg_error_t +find_up_via_auth_info_access (ctrl_t ctrl, KEYDB_HANDLE kh, ksba_cert_t cert) +{ + gpg_error_t err; + struct find_up_store_certs_s find_up_store_certs_parm; + char *url, *ldapurl; + int idx, i; + char *oid; + ksba_name_t name; + + find_up_store_certs_parm.ctrl = ctrl; + find_up_store_certs_parm.want_fpr = 1; + find_up_store_certs_parm.got_fpr = 0; + find_up_store_certs_parm.count = 0; + + /* Find suitable URLs; if there is a http scheme we prefer that. */ + url = ldapurl = NULL; + for (idx=0; + !url && !(err = ksba_cert_get_authority_info_access (cert, idx, + &oid, &name)); + idx++) + { + if (!strcmp (oid, oidstr_caIssuers)) + { + for (i=0; !url && ksba_name_enum (name, i); i++) + { + char *p = ksba_name_get_uri (name, i); + if (p) + { + if (!strncmp (p, "http:", 5) || !strncmp (p, "https:", 6)) + url = p; + else if (ldapurl) + xfree (p); /* We already got one. */ + else if (!strncmp (p, "ldap:",5) || !strncmp (p, "ldaps:",6)) + ldapurl = p; + } + else + xfree (p); + } + } + ksba_name_release (name); + ksba_free (oid); + } + if (err && gpg_err_code (err) != GPG_ERR_EOF) + { + log_error (_("can't get authorityInfoAccess: %s\n"), gpg_strerror (err)); + return err; + } + if (!url && ldapurl) + { + /* No HTTP scheme; fallback to LDAP if available. */ + url = ldapurl; + ldapurl = NULL; + } + xfree (ldapurl); + if (!url) + return gpg_error (GPG_ERR_NOT_FOUND); + + if (opt.verbose) + log_info ("looking up issuer via authorityInfoAccess.caIssuers\n"); + + err = gpgsm_dirmngr_lookup (ctrl, NULL, url, 0, find_up_store_certs_cb, + &find_up_store_certs_parm); + + /* Although we might receive several certificates we use only the + * first one. Or more exacty the first one for which we retrieved + * the fingerprint. */ + if (opt.verbose) + log_info ("number of caIssuers found: %d\n", + find_up_store_certs_parm.count); + if (err) + { + log_error ("external URL lookup failed: %s\n", gpg_strerror (err)); + err = gpg_error (GPG_ERR_NOT_FOUND); + } + else if (!find_up_store_certs_parm.got_fpr) + err = gpg_error (GPG_ERR_NOT_FOUND); + else + { + int old; + /* The retrieved certificates are currently stored in the + * ephemeral key DB, so we temporary switch to ephemeral + * mode. */ + old = keydb_set_ephemeral (kh, 1); + keydb_search_reset (kh); + err = keydb_search_fpr (ctrl, kh, find_up_store_certs_parm.fpr); + keydb_set_ephemeral (kh, old); + } + + return err; +} + + +/* 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; + struct find_up_store_certs_s find_up_store_certs_parm; + char *pattern; + + (void)kh; + + find_up_store_certs_parm.ctrl = ctrl; + find_up_store_certs_parm.count = 0; + + 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, NULL, 1, find_up_store_certs_cb, + &find_up_store_certs_parm); + free_strlist (names); + + if (opt.verbose) + log_info (_("number of matching certificates: %d\n"), + find_up_store_certs_parm.count); + if (rc && !opt.quiet) + log_info (_("dirmngr cache-only key lookup failed: %s\n"), + gpg_strerror (rc)); + return (!rc && find_up_store_certs_parm.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 (DBG_X509) + log_debug ("looking for parent certificate\n"); + 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 (ctrl, kh, s, authidno); + if (rc) + keydb_search_reset (kh); + + if (!rc && DBG_X509) + log_debug (" found via authid and sn+issuer\n"); + + /* 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 (ctrl, kh, s, authidno); + if (rc) + keydb_search_reset (kh); + + if (!rc && DBG_X509) + log_debug (" found via authid and sn+issuer (ephem)\n"); + } + 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 AKI.issuer_sn. Lets try the AKI.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 (ctrl, kh, issuer, keyid); + if (!rc && DBG_X509) + log_debug (" found via authid and keyid\n"); + if (rc) + { + int old = keydb_set_ephemeral (kh, 1); + if (!old) + rc = find_up_search_by_keyid (ctrl, kh, issuer, keyid); + if (!rc && DBG_X509) + log_debug (" found via authid and keyid (ephem)\n"); + 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 (ctrl, kh, issuer, keyid); + else + { + keydb_search_reset (kh); + rc = keydb_search_subject (ctrl, kh, issuer); + } + keydb_set_ephemeral (kh, old); + } + if (rc) + rc = -1; /* Need to make sure to have this error code. */ + + if (!rc && DBG_X509) + log_debug (" found via authid and issuer from dirmngr cache\n"); + } + + /* If we still didn't found it, try an external lookup. */ + if (rc == -1 && !find_next && !ctrl->offline) + { + /* We allow AIA also if CRLs are enabled; both can be used + * as a web bug so it does not make sense to not use AIA if + * CRL checks are enabled. */ + if ((opt.auto_issuer_key_retrieve || !opt.no_crl_check) + && !find_up_via_auth_info_access (ctrl, kh, cert)) + { + if (DBG_X509) + log_debug (" found via authorityInfoAccess.caIssuers\n"); + rc = 0; + } + else if (opt.auto_issuer_key_retrieve) + { + rc = find_up_external (ctrl, kh, issuer, keyid); + if (!rc && DBG_X509) + log_debug (" found via authid and external lookup\n"); + } + } + + + /* 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 (ctrl, 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 (ctrl, kh, issuer); + } + keydb_set_ephemeral (kh, old); + + if (!rc && DBG_X509) + log_debug (" found via issuer\n"); + } + + /* Still not found. If enabled, try an external lookup. */ + if (rc == -1 && !find_next && !ctrl->offline) + { + if ((opt.auto_issuer_key_retrieve || !opt.no_crl_check) + && !find_up_via_auth_info_access (ctrl, kh, cert)) + { + if (DBG_X509) + log_debug (" found via authorityInfoAccess.caIssuers\n"); + rc = 0; + } + else if (opt.auto_issuer_key_retrieve) + { + rc = find_up_external (ctrl, kh, issuer, NULL); + if (!rc && DBG_X509) + log_debug (" found via issuer and external lookup\n"); + } + } + + 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 (); + + *r_next = NULL; + if (!kh) + { + log_error (_("failed to allocate 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 (ctrl->offline || (opt.no_crl_check && !ctrl->use_ocsp)) + { + audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK, + gpg_error (GPG_ERR_NOT_ENABLED)); + return 0; + } + + + if (!(force_ocsp || ctrl->use_ocsp) + && !opt.enable_issuer_based_crl_check) + { + err = ksba_cert_get_crl_dist_point (subject_cert, 0, NULL, NULL, NULL); + if (gpg_err_code (err) == GPG_ERR_EOF) + { + /* No DP specified in the certificate. Thus the CA does not + * consider a CRL useful and the user of the certificate + * also does not consider this to be a critical thing. In + * this case we can conclude that the certificate shall not + * be revocable. Note that we reach this point here only if + * no OCSP responder shall be used. */ + audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK, gpg_error (GPG_ERR_TRUE)); + 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 (ctrl, 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 certificate, -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. + VALIDATE_FLAG_STEED - Check according to the STEED 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); + gnupg_copy_time (ctrl->current_time, 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 (); + if (!kh) + { + log_error (_("failed to allocate 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. */ + if (gpgsm_cert_has_well_known_private_key (subject_cert)) + { + memset (rootca_flags, 0, sizeof *rootca_flags); + istrusted_rc = ((flags & VALIDATE_FLAG_STEED) + ? 0 : gpg_error (GPG_ERR_NOT_TRUSTED)); + } + else + 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 (!(flags & VALIDATE_FLAG_STEED) + && 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; + 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; /* Be on the safe side and set RC. */ + } + 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 && !(flags & VALIDATE_FLAG_STEED)) + { + 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 they want to trust the root certificate. We + should do this only if the certificate under question + will then be usable. If the certificate has a well + known private key asking the user does not make any + sense. */ + if ( !any_expired + && !gpgsm_cert_has_well_known_private_key (subject_cert) + && (!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 ((flags & VALIDATE_FLAG_STEED)) + ; /* Fixme: check revocations via DNS. */ + 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. */ + /* No need to set RC because it is not used: + 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) + { + if (gpgsm_cert_has_well_known_private_key (issuer_cert)) + { + memset (rootca_flags, 0, sizeof *rootca_flags); + istrusted_rc = ((flags & VALIDATE_FLAG_STEED) + ? 0 : gpg_error (GPG_ERR_NOT_TRUSTED)); + } + else + 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 ((flags & VALIDATE_FLAG_STEED)) + rc = 0; /* Fixme: XXX */ + 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 (ctrl->offline || (opt.no_crl_check && !ctrl->use_ocsp)) + log_info ("CRLs not checked due to %s option\n", + ctrl->offline ? "offline" : "--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 because it will later be stored + anyway. */ + err = keydb_set_cert_flags (ctrl, 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 && !(flags & VALIDATE_FLAG_STEED)) + { + 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 vits for RETFLAGS are + VALIDATE_FLAG_CHAIN_MODEL and VALIDATE_FLAG_STEED. + + 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 the session requested a certain validation mode make sure the + corresponding flags are set. */ + if (ctrl->validation_model == 1) + flags |= VALIDATE_FLAG_CHAIN_MODEL; + else if (ctrl->validation_model == 2) + flags |= VALIDATE_FLAG_STEED; + + /* If the chain model was forced, set this immediately into + RETFLAGS. */ + *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 (!rc && (flags & VALIDATE_FLAG_STEED)) + { + *retflags |= VALIDATE_FLAG_STEED; + } + else 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_STEED)? + "steed" : + (*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 (); + if (!kh) + { + log_error (_("failed to allocate 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 + hierarchy; 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 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..d6b967c --- /dev/null +++ b/sm/certcheck.c @@ -0,0 +1,616 @@ +/* certcheck.c - check one certificate + * Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc. + * Copyright (C) 2001-2019 Werner Koch + * Copyright (C) 2015-2020 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <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 "../common/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; +} + + +/* Return the hash algorithm's algo id from its name given in the + * non-null termnated string in (buffer,buflen). Returns 0 on failure + * or if the algo is not known. */ +static int +hash_algo_from_buffer (const void *buffer, size_t buflen) +{ + char *string; + int algo; + + string = xtrymalloc (buflen + 1); + if (!string) + { + log_error (_("out of core\n")); + return 0; + } + memcpy (string, buffer, buflen); + string[buflen] = 0; + algo = gcry_md_map_name (string); + if (!algo) + log_error ("unknown digest algorithm '%s' used in certificate\n", string); + xfree (string); + return algo; +} + + +/* Return an unsigned integer from the non-null termnated string + * (buffer,buflen). Returns 0 on failure. */ +static unsigned int +uint_from_buffer (const void *buffer, size_t buflen) +{ + char *string; + unsigned int val; + + string = xtrymalloc (buflen + 1); + if (!string) + { + log_error (_("out of core\n")); + return 0; + } + memcpy (string, buffer, buflen); + string[buflen] = 0; + val = strtoul (string, NULL, 10); + xfree (string); + return val; +} + + +/* Extract the hash algorithm and the salt length from the sigval. */ +static gpg_error_t +extract_pss_params (gcry_sexp_t s_sig, int *r_algo, unsigned int *r_saltlen) +{ + gpg_error_t err; + gcry_buffer_t ioarray[2] = { {0}, {0} }; + + err = gcry_sexp_extract_param (s_sig, "sig-val", + "&'hash-algo''salt-length'", + ioarray+0, ioarray+1, NULL); + if (err) + { + log_error ("extracting params from PSS failed: %s\n", gpg_strerror (err)); + return err; + } + + *r_algo = hash_algo_from_buffer (ioarray[0].data, ioarray[0].len); + *r_saltlen = uint_from_buffer (ioarray[1].data, ioarray[1].len); + xfree (ioarray[0].data); + xfree (ioarray[1].data); + if (*r_saltlen < 20) + { + log_error ("length of PSS salt too short\n"); + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + if (!*r_algo) + { + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + + /* PSS has no hash function firewall like PKCS#1 and thus offers + * a path for hash algorithm replacement. To avoid this it makes + * sense to restrict the allowed hash algorithms and also allow only + * matching salt lengths. According to Peter Gutmann: + * "Beware of bugs in the above signature scheme; + * I have only proved it secure, not implemented it" + * - Apologies to Donald Knuth. + * https://www.metzdowd.com/pipermail/cryptography/2019-November/035449.html + * + * Given the set of supported algorithms currently available in + * Libgcrypt and the extra hash checks we have in some compliance + * modes, it would be hard to trick gpgsm to verify a forged + * signature. However, if eventually someone adds the xor256 hash + * algorithm (1.3.6.1.4.1.3029.3.2) to Libgcrypt we would be doomed. + */ + switch (*r_algo) + { + case GCRY_MD_SHA1: + case GCRY_MD_SHA256: + case GCRY_MD_SHA384: + case GCRY_MD_SHA512: + case GCRY_MD_SHA3_256: + case GCRY_MD_SHA3_384: + case GCRY_MD_SHA3_512: + break; + default: + log_error ("PSS hash algorithm '%s' rejected\n", + gcry_md_algo_name (*r_algo)); + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + + if (gcry_md_get_algo_dlen (*r_algo) != *r_saltlen) + { + log_error ("PSS hash algorithm '%s' rejected due to salt length %u\n", + gcry_md_algo_name (*r_algo), *r_saltlen); + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + + return 0; +} + + +/* 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; + ksba_sexp_t p; + size_t n; + gcry_sexp_t s_sig, s_data, s_pkey; + int use_pss = 0; + unsigned int saltlen; + + algo = gcry_md_map_name ( (algoid=ksba_cert_get_digest_algo (cert))); + if (!algo && algoid && !strcmp (algoid, "1.2.840.113549.1.1.10")) + use_pss = 1; + else if (!algo) + { + log_error ("unknown digest algorithm '%s' used certificate\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); + } + + /* The the signature from the certificate. */ + 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"); + ksba_free (p); + return gpg_error (GPG_ERR_BUG); + } + 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)); + return rc; + } + if (DBG_CRYPTO) + gcry_log_debugsxp ("sigval", s_sig); + + if (use_pss) + { + rc = extract_pss_params (s_sig, &algo, &saltlen); + if (rc) + { + gcry_sexp_release (s_sig); + return rc; + } + } + + + /* Hash the to-be-signed parts of the certificate. */ + 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_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); + + /* Get the public key from the certificate. */ + 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; + } + if (DBG_CRYPTO) + gcry_log_debugsxp ("pubkey:", s_pkey); + + if (use_pss) + { + rc = gcry_sexp_build (&s_data, NULL, + "(data (flags pss)" + "(hash %s %b)" + "(salt-length %u))", + hash_algo_to_string (algo), + (int)gcry_md_get_algo_dlen (algo), + gcry_md_read (md, algo), + saltlen); + if (rc) + BUG (); + } + else + { + /* RSA or DSA: Prepare the hash for verification. */ + gcry_mpi_t frame; + + 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; + } + if ( gcry_sexp_build (&s_data, NULL, "%m", frame) ) + BUG (); + gcry_mpi_release (frame); + } + if (DBG_CRYPTO) + gcry_log_debugsxp ("data:", s_data); + + /* Verify. */ + rc = gcry_pk_verify (s_sig, s_data, 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_data); + gcry_sexp_release (s_pkey); + return rc; +} + + + +int +gpgsm_check_cms_signature (ksba_cert_t cert, gcry_sexp_t s_sig, + gcry_md_hd_t md, int mdalgo, + unsigned int pkalgoflags, int *r_pkalgo) +{ + int rc; + ksba_sexp_t p; + gcry_sexp_t s_hash, s_pkey; + size_t n; + int pkalgo; + int use_pss; + unsigned int saltlen = 0; + + if (r_pkalgo) + *r_pkalgo = 0; + + /* Check whether rsaPSS is needed. This information is indicated in + * the SIG-VAL and already provided to us by the caller so that we + * do not need to parse this out. */ + use_pss = !!(pkalgoflags & PK_ALGO_FLAG_RSAPSS); + if (use_pss) + { + int algo; + + rc = extract_pss_params (s_sig, &algo, &saltlen); + if (rc) + { + gcry_sexp_release (s_sig); + return rc; + } + if (algo != mdalgo) + { + log_error ("PSS hash algo mismatch (%d/%d)\n", mdalgo, algo); + gcry_sexp_release (s_sig); + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + } + + 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); + return gpg_error (GPG_ERR_BUG); + } + if (DBG_CRYPTO) + log_printhex (p, n, "public key: "); + + 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)); + return rc; + } + + pkalgo = pk_algo_from_sexp (s_pkey); + if (r_pkalgo) + *r_pkalgo = pkalgo; + + if (use_pss) + { + rc = gcry_sexp_build (&s_hash, NULL, + "(data (flags pss)" + "(hash %s %b)" + "(salt-length %u))", + hash_algo_to_string (mdalgo), + (int)gcry_md_get_algo_dlen (mdalgo), + gcry_md_read (md, mdalgo), + saltlen); + if (rc) + BUG (); + } + else + { + /* RSA or DSA: Prepare the hash for verification. */ + gcry_mpi_t frame; + + rc = do_encode_md (md, mdalgo, pkalgo, + gcry_pk_get_nbits (s_pkey), s_pkey, &frame); + if (rc) + { + 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_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..57e8112 --- /dev/null +++ b/sm/certdump.c @@ -0,0 +1,940 @@ +/* certdump.c - Dump a certificate for debugging + * Copyright (C) 2001-2010, 2014-2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <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 "../common/i18n.h" +#include "../common/membuf.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); + } +} + + +/* Print the first element of an S-Expression in decimal notation + * assuming it is a non-negative integer. */ +void +gpgsm_print_serial_decimal (estream_t fp, ksba_const_sexp_t sn) +{ + const char *p = (const char *)sn; + unsigned long n, i; + char *endp; + gcry_mpi_t a, r, ten; +#if GCRYPT_VERSION_NUMBER >= 0x010900 /* >= 1.9.0 */ + unsigned int dd; +#else + unsigned char numbuf[10]; +#endif + + 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 if (gcry_mpi_scan (&a, GCRYMPI_FMT_USG, p, n, NULL)) + es_fputs ("[Internal Error - can't convert to decimal]", fp); + else + { + membuf_t mb = MEMBUF_ZERO; + char *buf; + int c; + + ten = gcry_mpi_set_ui (NULL, 10); + r = gcry_mpi_new (0); + + do + { + gcry_mpi_div (a, r, a, ten, 0); +#if GCRYPT_VERSION_NUMBER >= 0x010900 /* >= 1.9.0 */ + gcry_mpi_get_ui (&dd, r); + put_membuf_printf (&mb, "%u", dd); +#else + *numbuf = 0; /* Need to clear because USB format prints + * an empty string for a value of 0. */ + gcry_mpi_print (GCRYMPI_FMT_USG, numbuf, 10, NULL, r); + put_membuf_printf (&mb, "%u", (unsigned int)*numbuf); +#endif + } + while (gcry_mpi_cmp_ui (a, 0)); + + /* Make sure we have at least an empty string, get it, + * reverse it, and print it. */ + put_membuf (&mb, "", 1); + buf = get_membuf (&mb, NULL); + if (!buf) + es_fputs ("[Internal Error - out of core]", fp); + else + { + n = strlen (buf); + for (i=0; i < n/2; i++) + { + c = buf[i]; + buf[i] = buf[n-1-i]; + buf[n-1-i] = c; + } + es_fputs (buf, fp); + xfree (buf); + } + + gcry_mpi_release (r); + gcry_mpi_release (ten); + gcry_mpi_release (a); + } + } +} + + +/* 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 (string, strlen (string), NULL); + 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++) + ; + 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. */ +static void +print_dn_part (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 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) + { + es_fprintf (stream, "/%s=", dn->key); + if (translate) + print_utf8_buffer3 (stream, dn->value, strlen (dn->value), + "/"); + else + es_write_sanitized (stream, dn->value, strlen (dn->value), + "/", NULL); + } + 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 (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 (stream, dn, stdpart[i], translate); + + /* Now print the rest without any specific ordering */ + for (; dn->key; dn++) + print_dn_part (stream, dn, dn->key, translate); +} + + +/* 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); +} + + +/* 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) + print_utf8_buffer (fp, s + 1, s2 - (char*)s - 1); + 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 (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 gpgrt_ssize_t +format_name_writer (void *cookie, const void *buffer, size_t size) +{ + struct format_name_cookie *c = cookie; + char *p; + + if (!buffer) /* Flush. */ + return 0; /* (Actually we could use SIZE because that should be 0 too.) */ + + 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; + gpg_err_set_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; + gpg_err_set_errno (c->error); + return -1; + } + memcpy (p + c->len, buffer, size); + c->len += size; + p[c->len] = 0; /* Terminate string. */ + + return (gpgrt_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 (save_errno)); + gpg_err_set_errno (save_errno); + return NULL; + } + gpgsm_es_print_name2 (fp, name, translate); + es_fclose (fp); + if (cookie.error || !cookie.buffer) + { + xfree (cookie.buffer); + gpg_err_set_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..b1ae58c --- /dev/null +++ b/sm/certlist.c @@ -0,0 +1,618 @@ +/* certlist.c - build list of certificates + * Copyright (C) 2001, 2003, 2004, 2005, 2007, + * 2008, 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "../common/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, int silent) +{ + gpg_error_t err; + unsigned int use; + unsigned int encr_bits, sign_bits; + 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 && !silent) + 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; + if (!silent) + log_info (_("certificate should not have " + "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; + if (!silent) + log_info (_("certificate should not have " + "been used for OCSP response signing\n")); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + encr_bits = (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT); + if ((opt.compat_flags & COMPAT_ALLOW_KA_TO_ENCR)) + encr_bits |= KSBA_KEYUSAGE_KEY_AGREEMENT; + + sign_bits = (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION); + + if ((use & ((mode&1)? encr_bits : sign_bits))) + return 0; + + if (!silent) + log_info + (mode==3? _("certificate should not have been used for encryption\n"): + mode==2? _("certificate should not have 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, int silent) +{ + return cert_usage_p (cert, 0, silent); +} + + +/* 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, 0); +} + +int +gpgsm_cert_use_verify_p (ksba_cert_t cert) +{ + return cert_usage_p (cert, 2, 0); +} + +int +gpgsm_cert_use_decrypt_p (ksba_cert_t cert) +{ + return cert_usage_p (cert, 3, 0); +} + +int +gpgsm_cert_use_cert_p (ksba_cert_t cert) +{ + return cert_usage_p (cert, 4, 0); +} + +int +gpgsm_cert_use_ocsp_p (ksba_cert_t cert) +{ + return cert_usage_p (cert, 5, 0); +} + + +/* Return true if CERT has the well known private key extension. */ +int +gpgsm_cert_has_well_known_private_key (ksba_cert_t cert) +{ + int idx; + const char *oid; + + for (idx=0; !ksba_cert_get_extension (cert, idx, + &oid, NULL, NULL, NULL);idx++) + if (!strcmp (oid, "1.3.6.1.4.1.11591.2.2.2") ) + return 1; /* Yes. */ + return 0; /* No. */ +} + + +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 = classify_user_id (name, &desc, 0); + if (!rc) + { + kh = keydb_new (); + 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 (ctrl, kh, &desc, 1); + if (!rc) + rc = keydb_get_cert (kh, &cert); + if (!rc) + { + if (!first_subject) + { + /* Save 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, 0) + : 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 (ctrl, 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 ambiguous 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,0) + : 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 (ctrl_t ctrl, + const char *name, ksba_sexp_t keyid, ksba_cert_t *r_cert, + int allow_ambiguous) +{ + int rc; + KEYDB_SEARCH_DESC desc; + KEYDB_HANDLE kh = NULL; + + *r_cert = NULL; + rc = classify_user_id (name, &desc, 0); + if (!rc) + { + kh = keydb_new (); + if (!kh) + rc = gpg_error (GPG_ERR_ENOMEM); + else + { + nextone: + rc = keydb_search (ctrl, 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 + ambiguous 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) + { + ksba_isotime_t notbefore = ""; + const unsigned char *image = NULL; + size_t length = 0; + if (allow_ambiguous) + { + /* We want to return the newest certificate */ + if (ksba_cert_get_validity (*r_cert, 0, notbefore)) + *notbefore = '\0'; + image = ksba_cert_get_image (*r_cert, &length); + } + next_ambiguous: + rc = keydb_search (ctrl, kh, &desc, 1); + if (rc == -1) + rc = 0; + else + { + if (!rc) + { + ksba_cert_t cert2 = NULL; + ksba_isotime_t notbefore2 = ""; + const unsigned char *image2 = NULL; + size_t length2 = 0; + int cmp = 0; + + if (!keydb_get_cert (kh, &cert2)) + { + if (gpgsm_certs_identical_p (*r_cert, cert2)) + { + ksba_cert_release (cert2); + goto next_ambiguous; + } + if (allow_ambiguous) + { + if (ksba_cert_get_validity (cert2, 0, notbefore2)) + *notbefore2 = '\0'; + image2 = ksba_cert_get_image (cert2, &length2); + cmp = strcmp (notbefore, notbefore2); + /* use certificate image bits as last resort for stable ordering */ + if (!cmp) + cmp = memcmp (image, image2, length < length2 ? length : length2); + if (!cmp) + cmp = length < length2 ? -1 : length > length2 ? 1 : 0; + if (cmp < 0) + { + ksba_cert_release (*r_cert); + *r_cert = cert2; + strcpy (notbefore, notbefore2); + image = image2; + length = length2; + } + else + 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..d75b017 --- /dev/null +++ b/sm/certreqgen-ui.c @@ -0,0 +1,473 @@ +/* certreqgen-ui.c - Simple user interface for certreqgen.c + * Copyright (C) 2007, 2010, 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> + +#include "../common/i18n.h" +#include "../common/ttyio.h" +#include "../common/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); + + algo = get_pk_algo_from_canon_sexp (public, publiclen); + 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_EDDSA: 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, estream_t output_stream) +{ + 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 = 3072; + const char *keyusage; + char *subject_name; + membuf_t mb_email, mb_dns, mb_uri, mb_result; + char *result = NULL; + const char *s, *s2; + int selfsigned; + + 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++) + { + ksba_sexp_t pkey; + gcry_sexp_t s_pkey; + char *algostr = NULL; + const char *keyref; + int any = 0; + + keyref = strchr (sl->d, ' '); + if (keyref) + { + keyref++; + if (!gpgsm_agent_readkey (ctrl, 1, keyref, &pkey)) + { + if (!gcry_sexp_new (&s_pkey, pkey, 0, 0)) + algostr = pubkey_algo_string (s_pkey, NULL); + gcry_sexp_release (s_pkey); + } + xfree (pkey); + } + tty_printf (" (%d) %s %s", count, sl->d, algostr); + if ((sl->flags & GCRY_PK_USAGE_CERT)) + { + tty_printf ("%scert", any?",":" ("); + any = 1; + } + if ((sl->flags & GCRY_PK_USAGE_SIGN)) + { + tty_printf ("%ssign", any?",":" ("); + any = 1; + } + if ((sl->flags & GCRY_PK_USAGE_AUTH)) + { + tty_printf ("%sauth", any?",":" ("); + any = 1; + } + if ((sl->flags & GCRY_PK_USAGE_ENCR)) + { + tty_printf ("%sencr", any?",":" ("); + any = 1; + } + tty_printf ("%s\n", any?")":""); + xfree (algostr); + } + xfree (answer); + answer = 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_dns, "Name-DNS: "); + + /* URIs. */ + tty_printf (_("Enter URIs")); + tty_printf (_(" (optional; end with an empty line):\n")); + ask_mb_lines (&mb_uri, "Name-URI: "); + + + /* Want a self-signed certificate? */ + selfsigned = tty_get_answer_is_yes + (_("Create self-signed certificate? (y/N) ")); + + + /* 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); + } + if (keygrip) + store_key_value_lf (&mb_result, "Key-Grip: ", keygrip); + store_key_value_lf (&mb_result, "Key-Usage: ", keyusage); + if (selfsigned) + store_key_value_lf (&mb_result, "Serial: ", "random"); + store_key_value_lf (&mb_result, "Name-DN: ", subject_name); + 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 (_("These parameters are used:\n")); + for (s=result; (s2 = strchr (s, '\n')); s = s2+1) + tty_printf (" %.*s\n", (int)(s2-s), s); + tty_printf ("\n"); + + if (!tty_get_answer_is_yes ("Proceed with creation? (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); + if (selfsigned) + tty_printf ("%s", _("Now creating self-signed certificate. ")); + else + tty_printf ("%s", _("Now creating certificate request. ")); + tty_printf ("%s", _("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_stream); + ctrl->create_pem = save_pem; + } + if (!err) + { + if (selfsigned) + tty_printf (_("Ready.\n")); + else + 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..92d6ffe --- /dev/null +++ b/sm/certreqgen.c @@ -0,0 +1,1393 @@ +/* certreqgen.c - Generate a key and a certification [request] + * Copyright (C) 2002, 2003, 2005, 2007, 2010, + * 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +/* + The format of the parameter file is described in the manual under + "Unattended Usage". + + Here is an example: + $ cat >foo <<EOF + %echo Generating a standard key + Key-Type: RSA + Key-Length: 3072 + Name-DN: CN=test cert 1,OU=Aegypten Project,O=g10 Code GmbH,L=Ddorf,C=DE + Name-Email: joe@foo.bar + # Do a commit here, so that we can later print a "done" + %commit + %echo done + EOF + + This parameter file was used to create the STEED CA: + Key-Type: RSA + Key-Length: 1024 + Key-Grip: 68A638998DFABAC510EA645CE34F9686B2EDF7EA + Key-Usage: cert + Serial: 1 + Name-DN: CN=The STEED Self-Signing Nonthority + Not-Before: 2011-11-11 + Not-After: 2106-02-06 + Subject-Key-Id: 68A638998DFABAC510EA645CE34F9686B2EDF7EA + Extension: 2.5.29.19 c 30060101ff020101 + Extension: 1.3.6.1.4.1.11591.2.2.2 n 0101ff + Signing-Key: 68A638998DFABAC510EA645CE34F9686B2EDF7EA + %commit + +*/ + + +#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 "../common/i18n.h" + + +enum para_name + { + pKEYTYPE, + pKEYLENGTH, + pKEYGRIP, + pKEYUSAGE, + pNAMEDN, + pNAMEEMAIL, + pNAMEDNS, + pNAMEURI, + pSERIAL, + pISSUERDN, + pNOTBEFORE, + pNOTAFTER, + pSIGNINGKEY, + pHASHALGO, + pAUTHKEYID, + pSUBJKEYID, + pEXTENSION + }; + +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; +}; + + +static const char oidstr_authorityKeyIdentifier[] = "2.5.29.35"; +static const char oidstr_subjectKeyIdentifier[] = "2.5.29.14"; +static const char oidstr_keyUsage[] = "2.5.29.15"; +static const char oidstr_basicConstraints[] = "2.5.29.19"; +static const char oidstr_standaloneCertificate[] = "1.3.6.1.4.1.11591.2.2.1"; + + +static int proc_parameters (ctrl_t ctrl, + struct para_data_s *para, + estream_t out_fp, + 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, + ksba_const_sexp_t sigkey, + ksba_writer_t writer); + + + +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") + || !ascii_strcasecmp (p, "encr") ) + use |= GCRY_PK_USAGE_ENCR; + else if ( !ascii_strcasecmp (p, "cert") ) + use |= GCRY_PK_USAGE_CERT; + else + { + log_error ("line %d: invalid usage list\n", r?r->lnr:0); + 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, estream_t out_fp) +{ + 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 }, + { "Serial", pSERIAL }, + { "Issuer-DN", pISSUERDN }, + { "Creation-Date", pNOTBEFORE }, + { "Not-Before", pNOTBEFORE }, + { "Expire-Date", pNOTAFTER }, + { "Not-After", pNOTAFTER }, + { "Signing-Key", pSIGNINGKEY }, + { "Hash-Algo", pHASHALGO }, + { "Authority-Key-Id", pAUTHKEYID }, + { "Subject-Key-Id", pSUBJKEYID }, + { "Extension", pEXTENSION, 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)); + + 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 && !ascii_isspace (*p); p++) + ; + if (*p) + *p++ = 0; + for (; ascii_isspace (*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, out_fp, &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, out_fp, &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, out_fp, &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, + estream_t out_fp, struct reqgen_ctrl_s *outctrl) +{ + gpg_error_t err; + struct para_data_s *r; + const char *s, *string; + int i; + unsigned int nbits; + char numbuf[20]; + unsigned char keyparms[100]; + int rc = 0; + ksba_sexp_t public = NULL; + ksba_sexp_t sigkey = NULL; + 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); + if (r) + log_error (_("line %d: invalid algorithm\n"), r?r->lnr:0); + else + log_error ("No Key-Type specified\n"); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* Check the keylength. NOTE: If you change this make sure that it + macthes the gpgconflist item in gpgsm.c */ + if (!get_parameter (para, pKEYLENGTH, 0)) + nbits = 3072; + 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?r->lnr:0, 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?r->lnr:0); + 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?r->lnr:0, (int)errlen, s+erroff); + else + log_error (_("line %d: invalid subject name '%s' at pos %d\n"), + r?r->lnr:0, 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?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + /* Check the optional serial number. */ + string = get_parameter_value (para, pSERIAL, 0); + if (string) + { + if (!strcmp (string, "random")) + ; /* Okay. */ + else + { + for (s=string, i=0; hexdigitp (s); s++, i++) + ; + if (*s) + { + r = get_parameter (para, pSERIAL, 0); + log_error (_("line %d: invalid serial number\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + } + + /* Check the optional issuer DN. */ + string = get_parameter_value (para, pISSUERDN, 0); + if (string) + { + err = ksba_dn_teststr (string, 0, &erroff, &errlen); + if (err) + { + r = get_parameter (para, pISSUERDN, 0); + if (gpg_err_code (err) == GPG_ERR_UNKNOWN_NAME) + log_error (_("line %d: invalid issuer name label '%.*s'\n"), + r?r->lnr:0, (int)errlen, string+erroff); + else + log_error (_("line %d: invalid issuer name '%s' at pos %d\n"), + r?r->lnr:0, string, (int)erroff); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + /* Check the optional creation date. */ + string = get_parameter_value (para, pNOTBEFORE, 0); + if (string && !string2isotime (NULL, string)) + { + r = get_parameter (para, pNOTBEFORE, 0); + log_error (_("line %d: invalid date given\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + + /* Check the optional expire date. */ + string = get_parameter_value (para, pNOTAFTER, 0); + if (string && !string2isotime (NULL, string)) + { + r = get_parameter (para, pNOTAFTER, 0); + log_error (_("line %d: invalid date given\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* Get the optional signing key. */ + string = get_parameter_value (para, pSIGNINGKEY, 0); + if (string) + { + rc = gpgsm_agent_readkey (ctrl, 0, string, &sigkey); + if (rc) + { + r = get_parameter (para, pKEYTYPE, 0); + log_error (_("line %d: error getting signing key by keygrip '%s'" + ": %s\n"), r?r->lnr:0, s, gpg_strerror (rc)); + xfree (cardkeyid); + return rc; + } + } + + /* Check the optional hash-algo. */ + { + int mdalgo; + + string = get_parameter_value (para, pHASHALGO, 0); + if (string && !((mdalgo = gcry_md_map_name (string)) + && (mdalgo == GCRY_MD_SHA1 + || mdalgo == GCRY_MD_SHA256 + || mdalgo == GCRY_MD_SHA384 + || mdalgo == GCRY_MD_SHA512))) + { + r = get_parameter (para, pHASHALGO, 0); + log_error (_("line %d: invalid hash algorithm given\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + /* Check the optional AuthorityKeyId. */ + string = get_parameter_value (para, pAUTHKEYID, 0); + if (string) + { + for (s=string, i=0; hexdigitp (s); s++, i++) + ; + if (*s || (i&1)) + { + r = get_parameter (para, pAUTHKEYID, 0); + log_error (_("line %d: invalid authority-key-id\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + /* Check the optional SubjectKeyId. */ + string = get_parameter_value (para, pSUBJKEYID, 0); + if (string) + { + for (s=string, i=0; hexdigitp (s); s++, i++) + ; + if (*s || (i&1)) + { + r = get_parameter (para, pSUBJKEYID, 0); + log_error (_("line %d: invalid subject-key-id\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + /* Check the optional extensions. */ + for (seq=0; (string=get_parameter_value (para, pEXTENSION, seq)); seq++) + { + int okay = 0; + + s = strpbrk (string, " \t:"); + if (s) + { + s++; + while (spacep (s)) + s++; + if (*s && strchr ("nNcC", *s)) + { + s++; + while (spacep (s)) + s++; + if (*s == ':') + s++; + if (*s) + { + while (spacep (s)) + s++; + for (i=0; hexdigitp (s); s++, i++) + ; + if (!((*s && *s != ':') || !i || (i&1))) + okay = 1; + } + } + } + if (!okay) + { + r = get_parameter (para, pEXTENSION, seq); + log_error (_("line %d: invalid extension syntax\n"), r? r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + /* Create or retrieve the public key. */ + 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?r->lnr:0, cardkeyid, gpg_strerror (rc)); + xfree (sigkey); + 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 (sigkey); + xfree (cardkeyid); + return rc; + } + } + else if (!outctrl->dryrun) /* Generate new key. */ + { + sprintf (numbuf, "%u", nbits); + snprintf ((char*)keyparms, DIM (keyparms), + "(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?r->lnr:0, gpg_strerror (rc), gpg_strsource (rc)); + xfree (sigkey); + xfree (cardkeyid); + return rc; + } + } + + + if (!outctrl->dryrun) + { + gnupg_ksba_io_t b64writer = NULL; + ksba_writer_t writer; + int create_cert ; + + create_cert = !!get_parameter_value (para, pSERIAL, 0); + + ctrl->pem_name = create_cert? "CERTIFICATE" : "CERTIFICATE REQUEST"; + + rc = gnupg_ksba_create_writer + (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) + | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), + ctrl->pem_name, out_fp, &writer); + if (rc) + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + else + { + rc = create_request (ctrl, para, cardkeyid, public, sigkey, writer); + if (!rc) + { + rc = gnupg_ksba_finish_writer (b64writer); + if (rc) + log_error ("write failed: %s\n", gpg_strerror (rc)); + else + { + gpgsm_status (ctrl, STATUS_KEY_CREATED, "P"); + log_info ("certificate%s created\n", + create_cert?"":" request"); + } + } + gnupg_ksba_destroy_writer (b64writer); + } + } + + xfree (sigkey); + 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, + ksba_const_sexp_t sigkey, + ksba_writer_t writer) +{ + ksba_certreq_t cr; + gpg_error_t err; + gcry_md_hd_t md; + ksba_stop_reason_t stopreason; + int rc = 0; + const char *s, *string; + unsigned int use; + int seq; + char *buf, *p; + size_t len; + char numbuf[30]; + ksba_isotime_t atime; + int certmode = 0; + int mdalgo; + + err = ksba_certreq_new (&cr); + if (err) + return err; + + string = get_parameter_value (para, pHASHALGO, 0); + if (string) + mdalgo = gcry_md_map_name (string); + else + mdalgo = GCRY_MD_SHA256; + rc = gcry_md_open (&md, mdalgo, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_debug (md, "cr.cri"); + + ksba_certreq_set_hash_function (cr, HASH_FNC, md); + ksba_certreq_set_writer (cr, 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; + } + + /* Set key usage flags. */ + use = get_parameter_uint (para, pKEYUSAGE); + if (use) + { + unsigned int mask, pos; + unsigned char der[4]; + + der[0] = 0x03; + der[1] = 0x02; + der[2] = 0; + der[3] = 0; + if ((use & GCRY_PK_USAGE_SIGN)) + { + /* For signing only we encode the bits: + KSBA_KEYUSAGE_DIGITAL_SIGNATURE + KSBA_KEYUSAGE_NON_REPUDIATION = 0b11 -> 0b11000000 */ + der[3] |= 0xc0; + } + if ((use & GCRY_PK_USAGE_ENCR)) + { + /* For encrypt only we encode the bits: + KSBA_KEYUSAGE_KEY_ENCIPHERMENT + KSBA_KEYUSAGE_DATA_ENCIPHERMENT = 0b1100 -> 0b00110000 */ + der[3] |= 0x30; + } + if ((use & GCRY_PK_USAGE_CERT)) + { + /* For certify only we encode the bits: + KSBA_KEYUSAGE_KEY_CERT_SIGN + KSBA_KEYUSAGE_CRL_SIGN = 0b1100000 -> 0b00000110 */ + der[3] |= 0x06; + } + + /* Count number of unused bits. */ + for (mask=1, pos=0; pos < 8 * sizeof mask; pos++, mask <<= 1) + { + if ((der[3] & mask)) + break; + der[2]++; + } + + err = ksba_certreq_add_extension (cr, oidstr_keyUsage, 1, der, 4); + if (err) + { + log_error ("error setting the key usage: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + + + /* See whether we want to create an X.509 certificate. */ + string = get_parameter_value (para, pSERIAL, 0); + if (string) + { + certmode = 1; + + /* Store the serial number. */ + if (!strcmp (string, "random")) + { + char snbuf[3+8+1]; + + memcpy (snbuf, "(8:", 3); + gcry_create_nonce (snbuf+3, 8); + /* Clear high bit to guarantee a positive integer. */ + snbuf[3] &= 0x7f; + snbuf[3+8] = ')'; + err = ksba_certreq_set_serial (cr, snbuf); + } + else + { + char *hexbuf; + + /* Allocate a buffer large enough to prefix the string with + a '0' so to have an even number of digits. Prepend two + further '0' so that the binary result will have a leading + 0 byte and thus can't be the representation of a negative + number. Note that ksba_certreq_set_serial strips all + unneeded leading 0 bytes. */ + hexbuf = p = xtrymalloc (2 + 1 + strlen (string) + 1); + if (!hexbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + if ((strlen (string) & 1)) + *p++ = '0'; + *p++ = '0'; + *p++ = '0'; + strcpy (p, string); + for (p=hexbuf, len=0; p[0] && p[1]; p += 2) + ((unsigned char*)hexbuf)[len++] = xtoi_2 (p); + /* Now build the S-expression. */ + snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len); + buf = p = xtrymalloc (1 + strlen (numbuf) + len + 1 + 1); + if (!buf) + { + err = gpg_error_from_syserror (); + xfree (hexbuf); + goto leave; + } + p = stpcpy (stpcpy (buf, "("), numbuf); + memcpy (p, hexbuf, len); + p += len; + strcpy (p, ")"); + xfree (hexbuf); + err = ksba_certreq_set_serial (cr, buf); + xfree (buf); + } + if (err) + { + log_error ("error setting the serial number: %s\n", + gpg_strerror (err)); + goto leave; + } + + + /* Store the issuer DN. If no issuer DN is given and no signing + key has been set we add the standalone extension and the + basic constraints to mark it as a self-signed CA + certificate. */ + string = get_parameter_value (para, pISSUERDN, 0); + if (string) + { + /* Issuer DN given. Note that this may be the same as the + subject DN and thus this could as well be a self-signed + certificate. However the caller needs to explicitly + specify basicConstraints and so forth. */ + err = ksba_certreq_set_issuer (cr, string); + if (err) + { + log_error ("error setting the issuer DN: %s\n", + gpg_strerror (err)); + goto leave; + } + + } + else if (!string && !sigkey) + { + /* Self-signed certificate requested. Add basicConstraints + and the custom GnuPG standalone extension. */ + err = ksba_certreq_add_extension (cr, oidstr_basicConstraints, 1, + "\x30\x03\x01\x01\xff", 5); + if (err) + goto leave; + err = ksba_certreq_add_extension (cr, oidstr_standaloneCertificate, 0, + "\x01\x01\xff", 3); + if (err) + goto leave; + } + + /* Store the creation date. */ + string = get_parameter_value (para, pNOTBEFORE, 0); + if (string) + { + if (!string2isotime (atime, string)) + BUG (); /* We already checked the value. */ + } + else + gnupg_get_isotime (atime); + err = ksba_certreq_set_validity (cr, 0, atime); + if (err) + { + log_error ("error setting the creation date: %s\n", + gpg_strerror (err)); + goto leave; + } + + + /* Store the expire date. If it is not given, libksba inserts a + default value. */ + string = get_parameter_value (para, pNOTAFTER, 0); + if (string) + { + if (!string2isotime (atime, string)) + BUG (); /* We already checked the value. */ + err = ksba_certreq_set_validity (cr, 1, atime); + if (err) + { + log_error ("error setting the expire date: %s\n", + gpg_strerror (err)); + goto leave; + } + } + + + /* Figure out the signing algorithm. If no sigkey has been + given we set it to the public key to create a self-signed + certificate. */ + if (!sigkey) + sigkey = public; + + { + unsigned char *siginfo; + + err = transform_sigval (sigkey, + gcry_sexp_canon_len (sigkey, 0, NULL, NULL), + mdalgo, &siginfo, NULL); + if (!err) + { + err = ksba_certreq_set_siginfo (cr, siginfo); + xfree (siginfo); + } + if (err) + { + log_error ("error setting the siginfo: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + + /* Insert the AuthorityKeyId. */ + string = get_parameter_value (para, pAUTHKEYID, 0); + if (string) + { + char *hexbuf; + + /* Allocate a buffer for in-place conversion. We also add 4 + extra bytes space for the tags and lengths fields. */ + hexbuf = xtrymalloc (4 + strlen (string) + 1); + if (!hexbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + strcpy (hexbuf+4, string); + for (p=hexbuf+4, len=0; p[0] && p[1]; p += 2) + ((unsigned char*)hexbuf)[4+len++] = xtoi_2 (p); + if (len > 125) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + xfree (hexbuf); + goto leave; + } + hexbuf[0] = 0x30; /* Tag for a Sequence. */ + hexbuf[1] = len+2; + hexbuf[2] = 0x80; /* Context tag for an implicit Octet string. */ + hexbuf[3] = len; + err = ksba_certreq_add_extension (cr, oidstr_authorityKeyIdentifier, + 0, + hexbuf, 4+len); + xfree (hexbuf); + if (err) + { + log_error ("error setting the authority-key-id: %s\n", + gpg_strerror (err)); + goto leave; + } + } + + /* Insert the SubjectKeyId. */ + string = get_parameter_value (para, pSUBJKEYID, 0); + if (string) + { + char *hexbuf; + + /* Allocate a buffer for in-place conversion. We also add 2 + extra bytes space for the tag and length field. */ + hexbuf = xtrymalloc (2 + strlen (string) + 1); + if (!hexbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + strcpy (hexbuf+2, string); + for (p=hexbuf+2, len=0; p[0] && p[1]; p += 2) + ((unsigned char*)hexbuf)[2+len++] = xtoi_2 (p); + if (len > 127) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + xfree (hexbuf); + goto leave; + } + hexbuf[0] = 0x04; /* Tag for an Octet string. */ + hexbuf[1] = len; + err = ksba_certreq_add_extension (cr, oidstr_subjectKeyIdentifier, 0, + hexbuf, 2+len); + xfree (hexbuf); + if (err) + { + log_error ("error setting the subject-key-id: %s\n", + gpg_strerror (err)); + goto leave; + } + } + + /* Insert additional extensions. */ + for (seq=0; (string = get_parameter_value (para, pEXTENSION, seq)); seq++) + { + char *hexbuf; + char *oidstr; + int crit = 0; + + s = strpbrk (string, " \t:"); + if (!s) + { + err = gpg_error (GPG_ERR_INTERNAL); + goto leave; + } + + oidstr = xtrymalloc (s - string + 1); + if (!oidstr) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (oidstr, string, (s-string)); + oidstr[(s-string)] = 0; + + s++; + while (spacep (s)) + s++; + if (!*s) + { + err = gpg_error (GPG_ERR_INTERNAL); + xfree (oidstr); + goto leave; + } + + if (strchr ("cC", *s)) + crit = 1; + s++; + while (spacep (s)) + s++; + if (*s == ':') + s++; + while (spacep (s)) + s++; + + hexbuf = xtrystrdup (s); + if (!hexbuf) + { + err = gpg_error_from_syserror (); + xfree (oidstr); + goto leave; + } + for (p=hexbuf, len=0; p[0] && p[1]; p += 2) + ((unsigned char*)hexbuf)[len++] = xtoi_2 (p); + err = ksba_certreq_add_extension (cr, oidstr, crit, + hexbuf, len); + xfree (oidstr); + xfree (hexbuf); + } + } + else + sigkey = public; + + 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, *newsigval; + size_t siglen; + + n = gcry_sexp_canon_len (sigkey, 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*)sigkey, 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 the %s for key: &%s\n", + certmode? "certificate":"CSR", hexgrip); + + if (carddirect && !certmode) + rc = gpgsm_scd_pksign (ctrl, carddirect, NULL, + gcry_md_read (md, mdalgo), + gcry_md_get_algo_dlen (mdalgo), + mdalgo, + &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, mdalgo), + gcry_md_get_algo_dlen (mdalgo), + mdalgo, + &sigval, &siglen); + xfree (desc); + } + if (rc) + { + log_error ("signing failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + err = transform_sigval (sigval, siglen, mdalgo, + &newsigval, NULL); + xfree (sigval); + if (!err) + { + err = ksba_certreq_set_sig_val (cr, newsigval); + xfree (newsigval); + } + 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, estream_t out_stream) +{ + int rc; + + rc = read_parameters (ctrl, in_stream, out_stream); + if (rc) + { + log_error ("error creating certificate request: %s <%s>\n", + gpg_strerror (rc), gpg_strsource (rc)); + goto leave; + } + + leave: + return rc; +} diff --git a/sm/decrypt.c b/sm/decrypt.c new file mode 100644 index 0000000..2aa716f --- /dev/null +++ b/sm/decrypt.c @@ -0,0 +1,1166 @@ +/* decrypt.c - Decrypt a message + * Copyright (C) 2001, 2003, 2010 Free Software Foundation, Inc. + * Copyright (C) 2001-2019 Werner Koch + * Copyright (C) 2015-2021 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#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 "../common/i18n.h" +#include "../common/compliance.h" +#include "../common/tlv.h" + +/* We can provide an enum value which is only availabale with KSBA + * 1.6.0 so that we can compile even against older versions. Some + * calls will of course return an error in this case. This value is + * currently not used because the cipher mode is sufficient here. */ +/* #if KSBA_VERSION_NUMBER < 0x010600 /\* 1.6.0 *\/ */ +/* # define KSBA_CT_AUTHENVELOPED_DATA 10 */ +/* #endif */ + + +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; /* did 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; + int is_de_vs; /* Helper to track CO_DE_VS state. */ +}; + + +/* Return the hash algorithm's algo id from its name given in the + * non-null termnated string in (buffer,buflen). Returns 0 on failure + * or if the algo is not known. */ +static char * +string_from_gcry_buffer (gcry_buffer_t *buffer) +{ + char *string; + + string = xtrymalloc (buffer->len + 1); + if (!string) + return NULL; + memcpy (string, buffer->data, buffer->len); + string[buffer->len] = 0; + return string; +} + + +/* Helper for pwri_decrypt to parse the derive info. + * Example data for (DER,DERLEN): + * SEQUENCE { + * OCTET STRING + * 60 76 4B E9 5E DF 3C F8 B2 F9 B6 C2 7D 5A FB 90 + * 23 B6 47 DF + * INTEGER 10000 + * SEQUENCE { + * OBJECT IDENTIFIER + * hmacWithSHA512 (1 2 840 113549 2 11) + * NULL + * } + * } + */ +static gpg_error_t +pwri_parse_pbkdf2 (const unsigned char *der, size_t derlen, + unsigned char const **r_salt, unsigned int *r_saltlen, + unsigned long *r_iterations, + enum gcry_md_algos *r_digest) +{ + gpg_error_t err; + size_t objlen, hdrlen; + int class, tag, constructed, ndef; + char *oidstr; + + err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > derlen || tag != TAG_SEQUENCE + || !constructed || ndef)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + return err; + derlen = objlen; + + err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > derlen || tag != TAG_OCTET_STRING + || constructed || ndef)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + return err; + *r_salt = der; + *r_saltlen = objlen; + der += objlen; + derlen -= objlen; + + err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > derlen || tag != TAG_INTEGER + || constructed || ndef)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + return err; + *r_iterations = 0; + for (; objlen; objlen--) + { + *r_iterations <<= 8; + *r_iterations |= (*der++) & 0xff; + derlen--; + } + + err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > derlen || tag != TAG_SEQUENCE + || !constructed || ndef)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + return err; + derlen = objlen; + + err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > derlen || tag != TAG_OBJECT_ID + || constructed || ndef)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + return err; + + oidstr = ksba_oid_to_str (der, objlen); + if (!oidstr) + return gpg_error_from_syserror (); + *r_digest = gcry_md_map_name (oidstr); + if (*r_digest) + ; + else if (!strcmp (oidstr, "1.2.840.113549.2.7")) + *r_digest = GCRY_MD_SHA1; + else if (!strcmp (oidstr, "1.2.840.113549.2.8")) + *r_digest = GCRY_MD_SHA224; + else if (!strcmp (oidstr, "1.2.840.113549.2.9")) + *r_digest = GCRY_MD_SHA256; + else if (!strcmp (oidstr, "1.2.840.113549.2.10")) + *r_digest = GCRY_MD_SHA384; + else if (!strcmp (oidstr, "1.2.840.113549.2.11")) + *r_digest = GCRY_MD_SHA512; + else + err = gpg_error (GPG_ERR_DIGEST_ALGO); + ksba_free (oidstr); + + return err; +} + + +/* Password based decryption. + * ENC_VAL has the form: + * (enc-val + * (pwri + * (derive-algo <oid>) --| both are optional + * (derive-parm <der>) --| + * (encr-algo <oid>) + * (encr-parm <iv>) + * (encr-key <key>))) -- this is the encrypted session key + * + */ +static gpg_error_t +pwri_decrypt (ctrl_t ctrl, gcry_sexp_t enc_val, + unsigned char **r_result, unsigned int *r_resultlen, + struct decrypt_filter_parm_s *parm) +{ + gpg_error_t err; + gcry_buffer_t ioarray[5] = { {0} }; + char *derive_algo_str = NULL; + char *encr_algo_str = NULL; + const unsigned char *dparm; /* Alias for ioarray[1]. */ + unsigned int dparmlen; + const unsigned char *eparm; /* Alias for ioarray[3]. */ + unsigned int eparmlen; + const unsigned char *ekey; /* Alias for ioarray[4]. */ + unsigned int ekeylen; + unsigned char kek[32]; + unsigned int keklen; + enum gcry_cipher_algos encr_algo; + enum gcry_cipher_modes encr_mode; + gcry_cipher_hd_t encr_hd = NULL; + unsigned char *result = NULL; + unsigned int resultlen; + unsigned int blklen; + const unsigned char *salt; /* Points int dparm. */ + unsigned int saltlen; + unsigned long iterations; + enum gcry_md_algos digest_algo; + char *passphrase = NULL; + + + *r_resultlen = 0; + *r_result = NULL; + + err = gcry_sexp_extract_param (enc_val, "enc-val!pwri", + "&'derive-algo'?'derive-parm'?" + "'encr-algo''encr-parm''encr-key'", + ioarray+0, ioarray+1, + ioarray+2, ioarray+3, ioarray+4, NULL); + if (err) + { + /* If this is not pwri element, it is likly a kekri element + * which we do not yet support. Change the error back to the + * original as returned by ksba_cms_get_issuer. */ + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = gpg_error (GPG_ERR_UNSUPPORTED_CMS_OBJ); + else + log_error ("extracting PWRI parameter failed: %s\n", + gpg_strerror (err)); + goto leave; + } + + if (ioarray[0].data) + { + derive_algo_str = string_from_gcry_buffer (ioarray+0); + if (!derive_algo_str) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + dparm = ioarray[1].data; + dparmlen = ioarray[1].len; + encr_algo_str = string_from_gcry_buffer (ioarray+2); + if (!encr_algo_str) + { + err = gpg_error_from_syserror (); + goto leave; + } + eparm = ioarray[3].data; + eparmlen = ioarray[3].len; + ekey = ioarray[4].data; + ekeylen = ioarray[4].len; + + /* Check parameters. */ + if (DBG_CRYPTO) + { + if (derive_algo_str) + { + log_debug ("derive algo: %s\n", derive_algo_str); + log_printhex (dparm, dparmlen, "derive parm:"); + } + log_debug ("encr algo .: %s\n", encr_algo_str); + log_printhex (eparm, eparmlen, "encr parm .:"); + log_printhex (ekey, ekeylen, "encr key .:"); + } + + if (!derive_algo_str) + { + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + log_info ("PWRI with no key derivation detected\n"); + goto leave; + } + if (strcmp (derive_algo_str, "1.2.840.113549.1.5.12")) + { + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + log_info ("PWRI does not use PBKDF2 (but %s)\n", derive_algo_str); + goto leave; + } + + digest_algo = 0; /*(silence cc warning)*/ + err = pwri_parse_pbkdf2 (dparm, dparmlen, + &salt, &saltlen, &iterations, &digest_algo); + if (err) + { + log_error ("parsing PWRI parameter failed: %s\n", gpg_strerror (err)); + goto leave; + } + + parm->is_de_vs = (parm->is_de_vs + && gnupg_digest_is_compliant (CO_DE_VS, digest_algo)); + + + encr_algo = gcry_cipher_map_name (encr_algo_str); + encr_mode = gcry_cipher_mode_from_oid (encr_algo_str); + if (!encr_algo || !encr_mode) + { + log_error ("PWRI uses unknown algorithm %s\n", encr_algo_str); + err = gpg_error (GPG_ERR_CIPHER_ALGO); + goto leave; + } + + parm->is_de_vs = + (parm->is_de_vs + && gnupg_cipher_is_compliant (CO_DE_VS, encr_algo, encr_mode)); + + keklen = gcry_cipher_get_algo_keylen (encr_algo); + blklen = gcry_cipher_get_algo_blklen (encr_algo); + if (!keklen || keklen > sizeof kek || blklen != 16 ) + { + log_error ("PWRI algorithm %s cannot be used\n", encr_algo_str); + err = gpg_error (GPG_ERR_INV_KEYLEN); + goto leave; + } + if ((ekeylen % blklen) || (ekeylen / blklen < 2)) + { + /* Note that we need at least two full blocks. */ + log_error ("PWRI uses a wrong length of encrypted key\n"); + err = gpg_error (GPG_ERR_INV_KEYLEN); + goto leave; + } + + err = gpgsm_agent_ask_passphrase + (ctrl, + i18n_utf8 (N_("Please enter the passphrase for decryption.")), + 0, &passphrase); + if (err) + goto leave; + + err = gcry_kdf_derive (passphrase, strlen (passphrase), + GCRY_KDF_PBKDF2, digest_algo, + salt, saltlen, iterations, + keklen, kek); + if (passphrase) + { + wipememory (passphrase, strlen (passphrase)); + xfree (passphrase); + passphrase = NULL; + } + if (err) + { + log_error ("deriving key from passphrase failed: %s\n", + gpg_strerror (err)); + goto leave; + } + + if (DBG_CRYPTO) + log_printhex (kek, keklen, "KEK .......:"); + + /* Unwrap the key. */ + resultlen = ekeylen; + result = xtrymalloc_secure (resultlen); + if (!result) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = gcry_cipher_open (&encr_hd, encr_algo, encr_mode, 0); + if (err) + { + log_error ("PWRI failed to open cipher: %s\n", gpg_strerror (err)); + goto leave; + } + + err = gcry_cipher_setkey (encr_hd, kek, keklen); + wipememory (kek, sizeof kek); + if (!err) + err = gcry_cipher_setiv (encr_hd, ekey + ekeylen - 2 * blklen, blklen); + if (!err) + err = gcry_cipher_decrypt (encr_hd, result + ekeylen - blklen, blklen, + ekey + ekeylen - blklen, blklen); + if (!err) + err = gcry_cipher_setiv (encr_hd, result + ekeylen - blklen, blklen); + if (!err) + err = gcry_cipher_decrypt (encr_hd, result, ekeylen - blklen, + ekey, ekeylen - blklen); + /* (We assume that that eparm is the octet string with the IV) */ + if (!err) + err = gcry_cipher_setiv (encr_hd, eparm, eparmlen); + if (!err) + err = gcry_cipher_decrypt (encr_hd, result, resultlen, NULL, 0); + + if (err) + { + log_error ("KEK decryption failed for PWRI: %s\n", gpg_strerror (err)); + goto leave; + } + + if (DBG_CRYPTO) + log_printhex (result, resultlen, "Frame .....:"); + + if (result[0] < 8 /* At least 64 bits */ + || (result[0] % 8) /* Multiple of 64 bits */ + || result[0] > resultlen - 4 /* Not more than the size of the input */ + || ( (result[1] ^ result[4]) /* Matching check bytes. */ + & (result[2] ^ result[5]) + & (result[3] ^ result[6]) ) != 0xff) + { + err = gpg_error (GPG_ERR_BAD_PASSPHRASE); + goto leave; + } + + *r_resultlen = result[0]; + *r_result = memmove (result, result + 4, result[0]); + result = NULL; + + leave: + if (result) + { + wipememory (result, resultlen); + xfree (result); + } + if (passphrase) + { + wipememory (passphrase, strlen (passphrase)); + xfree (passphrase); + } + gcry_cipher_close (encr_hd); + xfree (derive_algo_str); + xfree (encr_algo_str); + xfree (ioarray[0].data); + xfree (ioarray[1].data); + xfree (ioarray[2].data); + xfree (ioarray[3].data); + xfree (ioarray[4].data); + return err; +} + + +/* 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 pwri = !hexkeygrip; + int rc; + + if (DBG_CRYPTO) + log_printcanon ("decrypting:", enc_val, 0); + + if (!pwri) + { + 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; + } + } + + n=0; + if (pwri) /* Password based encryption. */ + { + gcry_sexp_t s_enc_val; + unsigned char *decrypted; + unsigned int decryptedlen; + + rc = gcry_sexp_sscan (&s_enc_val, NULL, enc_val, + gcry_sexp_canon_len (enc_val, 0, NULL, NULL)); + if (rc) + goto leave; + + rc = pwri_decrypt (ctrl, s_enc_val, &decrypted, &decryptedlen, parm); + gcry_sexp_release (s_enc_val); + if (rc) + goto leave; + xfree (seskey); + seskey = decrypted; + seskeylen = decryptedlen; + } + else if (seskeylen == 32 || seskeylen == 24 || seskeylen == 16) + { + /* Smells like an AES-128, 3-DES, or AES-256 key. This might + * happen because a SC has already done the unpacking. A better + * solution would be to test for this only after we triggered + * the GPG_ERR_INV_SESSION_KEY. */ + } + 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 (seskey+n, seskeylen-n, "session key:"); + + if (opt.verbose) + log_info (_("%s.%s encrypted data\n"), + gcry_cipher_algo_name (parm->algo), + cipher_mode_to_string (parm->mode)); + + 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; + } + + rc = gcry_cipher_setiv (parm->hd, parm->iv, parm->ivlen); + if (rc) + { + log_error("IV setup failed: %s\n", gpg_strerror(rc) ); + goto leave; + } + + if (parm->mode == GCRY_CIPHER_MODE_GCM) + { + /* GCM mode really sucks in CMS. We need to know the AAD before + * we start decrypting but CMS puts the AAD after the content. + * Thus temporary files are required. Let's hope that no real + * messages with actual AAD are ever used. OCB Rules! */ + } + + 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; +} + + +/* This is the GCM version of decrypt_filter. */ +static gpg_error_t +decrypt_gcm_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; + + if (!inlen) + return gpg_error (GPG_ERR_BUG); + + if (maxoutlen < parm->blklen) + return gpg_error (GPG_ERR_BUG); + + if (inlen > maxoutlen) + inlen = maxoutlen; + + *inused = inlen; + if (inlen) + { + gcry_cipher_decrypt (parm->hd, outbuf, inlen, inbuf, inlen); + *outlen = inlen; + parm->any_data = 1; + } + else + *outlen = 0; + return 0; +} + + + +/* Perform a decrypt operation. */ +int +gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp) +{ + int rc; + gnupg_ksba_io_t b64reader = NULL; + gnupg_ksba_io_t 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; + estream_t 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 (); + if (!kh) + { + log_error (_("failed to allocate keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + in_fp = es_fdopen_nc (in_fd, "rb"); + if (!in_fp) + { + rc = gpg_error_from_syserror (); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + rc = gnupg_ksba_create_reader + (&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0) + | (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0) + | (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0)), + in_fp, &reader); + if (rc) + { + log_error ("can't create reader: %s\n", gpg_strerror (rc)); + goto leave; + } + + rc = gnupg_ksba_create_writer + (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) + | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), + ctrl->pem_name, out_fp, &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; + } + + /* Check compliance. */ + if (! gnupg_cipher_is_allowed (opt.compliance, 0, algo, mode)) + { + log_error (_("cipher algorithm '%s'" + " may not be used in %s mode\n"), + gcry_cipher_algo_name (algo), + gnupg_compliance_option_string (opt.compliance)); + rc = gpg_error (GPG_ERR_CIPHER_ALGO); + goto leave; + } + + /* For CMS, CO_DE_VS demands CBC mode. */ + dfparm.is_de_vs = gnupg_cipher_is_compliant (CO_DE_VS, algo, mode); + + 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 *pkalgostr = NULL; + char *pkfpr = NULL; + char *desc = NULL; + char kidbuf[16+1]; + int tmp_rc; + ksba_cert_t cert = NULL; + unsigned int nbits; + int pk_algo = 0; + int maybe_pwri = 0; + + *kidbuf = 0; + + tmp_rc = ksba_cms_get_issuer_serial (cms, recp, &issuer, &serial); + if (tmp_rc == -1 && recp) + break; /* no more recipients */ + audit_log_i (ctrl->audit, AUDIT_NEW_RECP, recp); + if (gpg_err_code (tmp_rc) == GPG_ERR_UNSUPPORTED_CMS_OBJ) + { + maybe_pwri = 1; + } + else if (tmp_rc) + { + log_error ("recp %d - error getting info: %s\n", + recp, gpg_strerror (tmp_rc)); + } + else + { + if (opt.verbose) + { + 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 (ctrl, 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); + pkfpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + pkalgostr = gpgsm_pubkey_algo_string (cert, NULL); + pk_algo = gpgsm_get_key_algo_info (cert, &nbits); + if (!opt.quiet) + log_info (_("encrypted to %s key %s\n"), pkalgostr, pkfpr); + + /* Check compliance. */ + if (!gnupg_pk_is_allowed (opt.compliance, + PK_USE_DECRYPTION, + pk_algo, 0, NULL, nbits, NULL)) + { + char kidstr[10+1]; + + snprintf (kidstr, sizeof kidstr, "0x%08lX", + gpgsm_get_short_fingerprint (cert, NULL)); + log_info (_("key %s is not suitable for decryption" + " in %s mode\n"), + kidstr, + gnupg_compliance_option_string(opt.compliance)); + rc = gpg_error (GPG_ERR_PUBKEY_ALGO); + goto oops; + } + + /* Check that all certs are compliant with CO_DE_VS. */ + dfparm.is_de_vs = + (dfparm.is_de_vs + && gnupg_pk_is_compliant (CO_DE_VS, pk_algo, 0, + NULL, nbits, NULL)); + + oops: + if (rc) + { + /* We cannot check compliance of certs that we + * don't have. */ + dfparm.is_de_vs = 0; + } + xfree (issuer); + xfree (serial); + ksba_cert_release (cert); + } + + if ((!hexkeygrip || !pk_algo) && !maybe_pwri) + ; + else if (!(enc_val = ksba_cms_get_enc_val (cms, recp))) + { + log_error ("recp %d - error getting encrypted session key\n", + recp); + if (maybe_pwri) + log_info ("(possibly unsupported KEK info)\n"); + } + else + { + if (maybe_pwri && opt.verbose) + log_info ("recp %d - KEKRI or PWRI\n", recp); + + 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, + dfparm.mode == GCRY_CIPHER_MODE_GCM? + decrypt_gcm_filter : decrypt_filter, + &dfparm); + + if (dfparm.is_de_vs + && gnupg_gcrypt_is_compliant (CO_DE_VS)) + gpgsm_status (ctrl, STATUS_DECRYPTION_COMPLIANCE_MODE, + gnupg_status_compliance_flag (CO_DE_VS)); + else if (opt.require_compliance + && opt.compliance == CO_DE_VS) + { + log_error (_("operation forced to fail due to" + " unfulfilled compliance rules\n")); + gpgsm_errors_seen = 1; + } + } + audit_log_ok (ctrl->audit, AUDIT_RECP_RESULT, rc); + } + xfree (pkalgostr); + xfree (pkfpr); + 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.mode == GCRY_CIPHER_MODE_GCM) + { + /* Nothing yet to do. We wait for the ready event. */ + } + else 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; + } + } + } + } + else if (stopreason == KSBA_SR_READY) + { + if (dfparm.mode == GCRY_CIPHER_MODE_GCM) + { + char *authtag; + size_t authtaglen; + + rc = ksba_cms_get_message_digest (cms, 0, &authtag, &authtaglen); + if (rc) + { + log_error ("error getting authtag: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_CRYPTO) + log_printhex (authtag, authtaglen, "Authtag ...:"); + rc = gcry_cipher_checktag (dfparm.hd, authtag, authtaglen); + xfree (authtag); + if (rc) + log_error ("data is not authentic: %s\n", gpg_strerror (rc)); + goto leave; + } + } + } + while (stopreason != KSBA_SR_READY); + + rc = gnupg_ksba_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); + gnupg_ksba_destroy_reader (b64reader); + gnupg_ksba_destroy_writer (b64writer); + keydb_release (kh); + es_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..56d5b1f --- /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 <https://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 "../common/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 = classify_user_id (username, &desc, 0); + 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 (); + 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 (ctrl, 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 (ctrl, 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 (ctrl, 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..587b568 --- /dev/null +++ b/sm/encrypt.c @@ -0,0 +1,589 @@ +/* encrypt.c - Encrypt a message + * Copyright (C) 2001, 2003, 2004, 2007, 2008, + * 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <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 "../common/i18n.h" +#include "../common/compliance.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; + + +/* Callback parameters for the encryption. */ +struct encrypt_cb_parm_s +{ + estream_t 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", + gnupg_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) + { + gcry_sexp_release (s_pkey); + 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. */ + if (!rc) + { + 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 = es_getc (parm->fp); + if (c == EOF) + { + if (es_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, estream_t out_fp) +{ + int rc = 0; + gnupg_ksba_io_t 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; + estream_t data_fp = NULL; + certlist_t cl; + int count; + int compliant; + + 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 (); + if (!kh) + { + log_error (_("failed to allocate keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + /* Fixme: We should use the unlocked version of the es functions. */ + data_fp = es_fdopen_nc (data_fd, "rb"); + if (!data_fp) + { + rc = gpg_error_from_syserror (); + 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 = gnupg_ksba_create_writer + (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) + | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), + ctrl->pem_name, out_fp, &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; + } + + /* Check compliance. */ + if (!gnupg_cipher_is_allowed + (opt.compliance, 1, gcry_cipher_map_name (opt.def_cipher_algoid), + gcry_cipher_mode_from_oid (opt.def_cipher_algoid))) + { + log_error (_("cipher algorithm '%s' may not be used in %s mode\n"), + opt.def_cipher_algoid, + gnupg_compliance_option_string (opt.compliance)); + rc = gpg_error (GPG_ERR_CIPHER_ALGO); + goto leave; + } + + if (!gnupg_rng_is_compliant (opt.compliance)) + { + rc = gpg_error (GPG_ERR_FORBIDDEN); + log_error (_("%s is not compliant with %s mode\n"), + "RNG", + gnupg_compliance_option_string (opt.compliance)); + gpgsm_status_with_error (ctrl, STATUS_ERROR, + "random-compliance", rc); + 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); + + compliant = gnupg_cipher_is_compliant (CO_DE_VS, dek->algo, + GCRY_CIPHER_MODE_CBC); + + /* 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; + unsigned int nbits; + int pk_algo; + + /* Check compliance. */ + pk_algo = gpgsm_get_key_algo_info (cl->cert, &nbits); + if (!gnupg_pk_is_compliant (opt.compliance, pk_algo, 0, + NULL, nbits, NULL)) + { + char kidstr[10+1]; + + snprintf (kidstr, sizeof kidstr, "0x%08lX", + gpgsm_get_short_fingerprint (cl->cert, NULL)); + log_info (_("WARNING: key %s is not suitable for encryption" + " in %s mode\n"), + kidstr, + gnupg_compliance_option_string (opt.compliance)); + } + + /* Fixme: When adding ECC we need to provide the curvename and + * the key to gnupg_pk_is_compliant. */ + if (compliant + && !gnupg_pk_is_compliant (CO_DE_VS, pk_algo, 0, NULL, nbits, NULL)) + compliant = 0; + + 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; + } + } + + if (compliant && gnupg_gcrypt_is_compliant (CO_DE_VS)) + gpgsm_status (ctrl, STATUS_ENCRYPTION_COMPLIANCE_MODE, + gnupg_status_compliance_flag (CO_DE_VS)); + else if (opt.require_compliance + && opt.compliance == CO_DE_VS) + { + log_error (_("operation forced to fail due to" + " unfulfilled compliance rules\n")); + gpgsm_errors_seen = 1; + rc = gpg_error (GPG_ERR_FORBIDDEN); + 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 = gnupg_ksba_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + audit_log (ctrl->audit, AUDIT_ENCRYPTION_DONE); + if (!opt.quiet) + log_info ("encrypted data created\n"); + + leave: + ksba_cms_release (cms); + gnupg_ksba_destroy_writer (b64writer); + ksba_reader_release (reader); + keydb_release (kh); + xfree (dek); + es_fclose (data_fp); + xfree (encparm.buffer); + return rc; +} diff --git a/sm/export.c b/sm/export.c new file mode 100644 index 0000000..3da06d7 --- /dev/null +++ b/sm/export.c @@ -0,0 +1,756 @@ +/* export.c - Export certificates and private keys. + * Copyright (C) 2002, 2003, 2004, 2007, 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 <https://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 "../common/exechelp.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "minip12.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, 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, + int rawmode, + void **r_result, size_t *r_resultlen); + + +/* 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] & ~(~0U << 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. The output + is written to STREAM. */ +void +gpgsm_export (ctrl_t ctrl, strlist_t names, estream_t stream) +{ + KEYDB_HANDLE hd = NULL; + KEYDB_SEARCH_DESC *desc = NULL; + int ndesc; + gnupg_ksba_io_t 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 (); + 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 = classify_user_id (sl->d, desc+ndesc, 0); + 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 (ctrl, 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) + es_putc ('\n', stream); + print_short_info (cert, stream); + es_putc ('\n', stream); + } + count++; + + if (!b64writer) + { + ctrl->pem_name = "CERTIFICATE"; + rc = gnupg_ksba_create_writer + (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) + | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 :0)), + ctrl->pem_name, 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 = gnupg_ksba_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + gnupg_ksba_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 = gnupg_ksba_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + } + + leave: + gnupg_ksba_destroy_writer (b64writer); + ksba_cert_release (cert); + xfree (desc); + keydb_release (hd); + destroy_duptable (dtable); +} + + +/* Export a certificate and its private key. RAWMODE controls the + actual output: + 0 - Private key and certifciate in PKCS#12 format + 1 - Only unencrypted private key in PKCS#8 format + 2 - Only unencrypted private key in PKCS#1 format + */ +void +gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream, int rawmode) +{ + gpg_error_t err = 0; + KEYDB_HANDLE hd; + KEYDB_SEARCH_DESC *desc = NULL; + gnupg_ksba_io_t b64writer = NULL; + ksba_writer_t writer; + ksba_cert_t cert = NULL; + const unsigned char *image; + size_t imagelen; + char *keygrip = NULL; + char *prompt; + void *data; + size_t datalen; + + hd = keydb_new (); + 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; + } + + err = classify_user_id (name, desc, 0); + if (err) + { + log_error ("key '%s' not found: %s\n", + name, gpg_strerror (err)); + goto leave; + } + + /* Lookup the certificate and make sure that it is unique. */ + err = keydb_search (ctrl, hd, desc, 1); + if (!err) + { + err = keydb_get_cert (hd, &cert); + if (err) + { + log_error ("keydb_get_cert failed: %s\n", gpg_strerror (err)); + goto leave; + } + + next_ambiguous: + err = keydb_search (ctrl, hd, desc, 1); + if (!err) + { + 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); + } + err = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + } + else if (err == -1 || gpg_err_code (err) == GPG_ERR_EOF) + err = 0; + if (err) + { + log_error ("key '%s' not found: %s\n", + name, gpg_strerror (err)); + goto leave; + } + } + + keygrip = gpgsm_get_keygrip_hexstring (cert); + if (!keygrip || gpgsm_agent_havekey (ctrl, keygrip)) + { + /* Note, that the !keygrip case indicates a bad certificate. */ + err = gpg_error (GPG_ERR_NO_SECKEY); + log_error ("can't export key '%s': %s\n", name, gpg_strerror (err)); + 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, stream); + es_putc ('\n', stream); + } + + if (opt.p12_charset && ctrl->create_pem && !rawmode) + { + es_fprintf (stream, "The passphrase is %s encoded.\n\n", + opt.p12_charset); + } + + if (rawmode == 0) + ctrl->pem_name = "PKCS12"; + else if (rawmode == 1) + ctrl->pem_name = "PRIVATE KEY"; + else + ctrl->pem_name = "RSA PRIVATE KEY"; + err = gnupg_ksba_create_writer + (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) + | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), + ctrl->pem_name, stream, &writer); + if (err) + { + log_error ("can't create writer: %s\n", gpg_strerror (err)); + goto leave; + } + + prompt = gpgsm_format_keydesc (cert); + err = export_p12 (ctrl, image, imagelen, prompt, keygrip, rawmode, + &data, &datalen); + xfree (prompt); + if (err) + goto leave; + err = ksba_writer_write (writer, data, datalen); + xfree (data); + if (err) + { + log_error ("write failed: %s\n", gpg_strerror (err)); + goto leave; + } + + if (ctrl->create_pem) + { + /* We want one certificate per PEM block */ + err = gnupg_ksba_finish_writer (b64writer); + if (err) + { + log_error ("write failed: %s\n", gpg_strerror (err)); + goto leave; + } + gnupg_ksba_destroy_writer (b64writer); + b64writer = NULL; + } + + ksba_cert_release (cert); + cert = NULL; + + leave: + gnupg_ksba_destroy_writer (b64writer); + ksba_cert_release (cert); + xfree (keygrip); + xfree (desc); + keydb_release (hd); +} + + +/* Print some info about the certifciate CERT to FP or STREAM */ +static void +print_short_info (ksba_cert_t cert, estream_t stream) +{ + char *p; + ksba_sexp_t sexp; + int idx; + + for (idx=0; (p = ksba_cert_get_issuer (cert, idx)); idx++) + { + es_fputs ((!idx + ? "Issuer ...: " + : "\n aka ...: "), stream); + gpgsm_es_print_name (stream, p); + xfree (p); + } + es_putc ('\n', stream); + + es_fputs ("Serial ...: ", 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 == ':') + es_write_hexstring (stream, s+1, len, 0, NULL); + } + xfree (sexp); + } + es_putc ('\n', stream); + + for (idx=0; (p = ksba_cert_get_subject (cert, idx)); idx++) + { + es_fputs ((!idx + ? "Subject ..: " + : "\n aka ..: "), stream); + gpgsm_es_print_name (stream, p); + xfree (p); + } + es_putc ('\n', stream); + + p = gpgsm_get_keygrip_hexstring (cert); + if (p) + { + es_fprintf (stream, "Keygrip ..: %s\n", p); + xfree (p); + } +} + + + +/* Parse a private key S-expression and return a malloced array with + the RSA parameters in pkcs#12 order. The caller needs to + deep-release this array. */ +static gcry_mpi_t * +sexp_to_kparms (gcry_sexp_t sexp) +{ + gcry_sexp_t list, l2; + const char *name; + const char *s; + size_t n; + int idx; + const char *elems; + gcry_mpi_t *array; + + list = gcry_sexp_find_token (sexp, "private-key", 0 ); + if(!list) + return NULL; + l2 = gcry_sexp_cadr (list); + gcry_sexp_release (list); + list = l2; + name = gcry_sexp_nth_data (list, 0, &n); + if(!name || n != 3 || memcmp (name, "rsa", 3)) + { + gcry_sexp_release (list); + return NULL; + } + + /* Parameter names used with RSA in the pkcs#12 order. */ + elems = "nedqp--u"; + array = xtrycalloc (strlen(elems) + 1, sizeof *array); + if (!array) + { + gcry_sexp_release (list); + return NULL; + } + for (idx=0, s=elems; *s; s++, idx++ ) + { + if (*s == '-') + continue; /* Computed below */ + l2 = gcry_sexp_find_token (list, s, 1); + if (l2) + { + array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG); + gcry_sexp_release (l2); + } + if (!array[idx]) /* Required parameter not found or invalid. */ + { + for (idx=0; array[idx]; idx++) + gcry_mpi_release (array[idx]); + xfree (array); + gcry_sexp_release (list); + return NULL; + } + } + gcry_sexp_release (list); + + array[5] = gcry_mpi_snew (0); /* compute d mod (q-1) */ + gcry_mpi_sub_ui (array[5], array[3], 1); + gcry_mpi_mod (array[5], array[2], array[5]); + + array[6] = gcry_mpi_snew (0); /* compute d mod (p-1) */ + gcry_mpi_sub_ui (array[6], array[4], 1); + gcry_mpi_mod (array[6], array[2], array[6]); + + return array; +} + + +static gpg_error_t +export_p12 (ctrl_t ctrl, const unsigned char *certimg, size_t certimglen, + const char *prompt, const char *keygrip, int rawmode, + void **r_result, size_t *r_resultlen) +{ + gpg_error_t err = 0; + void *kek = NULL; + size_t keklen; + unsigned char *wrappedkey = NULL; + size_t wrappedkeylen; + gcry_cipher_hd_t cipherhd = NULL; + gcry_sexp_t s_skey = NULL; + gcry_mpi_t *kparms = NULL; + unsigned char *key = NULL; + size_t keylen; + char *passphrase = NULL; + unsigned char *result = NULL; + size_t resultlen; + int i; + + *r_result = NULL; + + /* Get the current KEK. */ + err = gpgsm_agent_keywrap_key (ctrl, 1, &kek, &keklen); + if (err) + { + log_error ("error getting the KEK: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Receive the wrapped key from the agent. */ + err = gpgsm_agent_export_key (ctrl, keygrip, prompt, + &wrappedkey, &wrappedkeylen); + if (err) + goto leave; + + + /* Unwrap the key. */ + err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_AESWRAP, 0); + if (err) + goto leave; + err = gcry_cipher_setkey (cipherhd, kek, keklen); + if (err) + goto leave; + xfree (kek); + kek = NULL; + + if (wrappedkeylen < 24) + { + err = gpg_error (GPG_ERR_INV_LENGTH); + goto leave; + } + keylen = wrappedkeylen - 8; + key = xtrymalloc_secure (keylen); + if (!key) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen); + if (err) + goto leave; + xfree (wrappedkey); + wrappedkey = NULL; + gcry_cipher_close (cipherhd); + cipherhd = NULL; + + + /* Convert to a gcrypt S-expression. */ + err = gcry_sexp_create (&s_skey, key, keylen, 0, xfree_fnc); + if (err) + goto leave; + key = NULL; /* Key is now owned by S_KEY. */ + + /* Get the parameters from the S-expression. */ + kparms = sexp_to_kparms (s_skey); + gcry_sexp_release (s_skey); + s_skey = NULL; + if (!kparms) + { + log_error ("error converting key parameters\n"); + err = GPG_ERR_BAD_SECKEY; + goto leave; + } + + if (rawmode) + { + /* Export in raw mode, that is only the pkcs#1/#8 private key. */ + result = p12_raw_build (kparms, rawmode, &resultlen); + if (!result) + err = gpg_error (GPG_ERR_GENERAL); + } + else + { + err = gpgsm_agent_ask_passphrase + (ctrl, + i18n_utf8 (N_("Please enter the passphrase to protect the " + "new PKCS#12 object.")), + 1, &passphrase); + if (err) + goto leave; + + result = p12_build (kparms, certimg, certimglen, passphrase, + opt.p12_charset, &resultlen); + xfree (passphrase); + passphrase = NULL; + if (!result) + err = gpg_error (GPG_ERR_GENERAL); + } + + leave: + xfree (key); + gcry_sexp_release (s_skey); + if (kparms) + { + for (i=0; kparms[i]; i++) + gcry_mpi_release (kparms[i]); + xfree (kparms); + } + gcry_cipher_close (cipherhd); + xfree (wrappedkey); + xfree (kek); + + if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE) + { + /* 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"); + } + + if (err) + { + xfree (result); + } + else + { + *r_result = result; + *r_resultlen = resultlen; + } + return err; +} diff --git a/sm/fingerprint.c b/sm/fingerprint.c new file mode 100644 index 0000000..2e01cf1 --- /dev/null +++ b/sm/fingerprint.c @@ -0,0 +1,380 @@ +/* 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 <https://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 "../common/host2net.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 = buf32_to_ulong (digest+12); + return buf32_to_ulong (digest + 16); +} + + +/* 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) + { + log_error ("can't calculate keygrip\n"); + return NULL; + } + if (DBG_X509) + log_printhex (array, 20, "keygrip="); + + 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); +} + + +/* This is a wrapper around pubkey_algo_string which takes a KSBA + * certificate instead of a Gcrypt public key. Note that this + * function may return NULL on error. */ +char * +gpgsm_pubkey_algo_string (ksba_cert_t cert, int *r_algoid) +{ + gpg_error_t err; + gcry_sexp_t s_pkey; + ksba_sexp_t p; + size_t n; + enum gcry_pk_algos algoid; + char *algostr; + + p = ksba_cert_get_public_key (cert); + if (!p) + return NULL; + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + { + xfree (p); + return NULL; + } + err = gcry_sexp_sscan (&s_pkey, NULL, (char *)p, n); + xfree (p); + if (err) + return NULL; + + algostr = pubkey_algo_string (s_pkey, r_algoid? &algoid : NULL); + if (algostr && r_algoid) + *r_algoid = algoid; + + gcry_sexp_release (s_pkey); + return algostr; +} + + + +/* 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-w32info.rc b/sm/gpgsm-w32info.rc new file mode 100644 index 0000000..537afdb --- /dev/null +++ b/sm/gpgsm-w32info.rc @@ -0,0 +1,52 @@ +/* gpgsm-w32info.rc -*- c -*- + * Copyright (C) 2013 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s X.509/CMS tool\0" + VALUE "InternalName", "gpgsm\0" + VALUE "OriginalFilename", "gpgsm.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "gpgsm.w32-manifest" diff --git a/sm/gpgsm.c b/sm/gpgsm.c new file mode 100644 index 0000000..2716890 --- /dev/null +++ b/sm/gpgsm.c @@ -0,0 +1,2262 @@ +/* gpgsm.c - GnuPG for S/MIME + * Copyright (C) 2001-2020 Free Software Foundation, Inc. + * Copyright (C) 2001-2019 Werner Koch + * Copyright (C) 2015-2020 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <fcntl.h> + +#define INCLUDED_BY_MAIN_MODULE 1 + +#include "gpgsm.h" +#include <gcrypt.h> +#include <assuan.h> /* malloc hooks */ + +#include "passphrase.h" +#include "../common/shareddefs.h" +#include "../kbx/keybox.h" /* malloc hooks */ +#include "../common/i18n.h" +#include "keydb.h" +#include "../common/sysutils.h" +#include "../common/gc-opt-flags.h" +#include "../common/asshelp.h" +#include "../common/init.h" +#include "../common/compliance.h" +#include "minip12.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, + aExportSecretKeyP8, + aExportSecretKeyRaw, + aServer, + aLearnCard, + aCallDirmngr, + aCallProtectTool, + aPasswd, + aGPGConfList, + aGPGConfTest, + aDumpKeys, + aDumpChain, + aDumpSecretKeys, + aDumpExternalKeys, + aKeydbClearSomeCertFlags, + aFingerprint, + + oOptions, + oDebug, + oDebugLevel, + oDebugAll, + oDebugNone, + oDebugWait, + oDebugAllowCoreDump, + oDebugNoChainValidation, + oDebugIgnoreExpiration, + oLogFile, + oNoLogFile, + oAuditLog, + oHtmlAuditLog, + + oEnableSpecialFilenames, + + oAgentProgram, + oDisplay, + oTTYname, + oTTYtype, + oLCctype, + oLCmessages, + oXauthority, + + oPreferSystemDirmngr, + oDirmngrProgram, + oDisableDirmngr, + oProtectToolProgram, + oFakedSystemTime, + + oPassphraseFD, + oPinentryMode, + oRequestOrigin, + + oAssumeArmor, + oAssumeBase64, + oAssumeBinary, + + oBase64, + oNoArmor, + oP12Charset, + + oCompliance, + + oDisableCRLChecks, + oEnableCRLChecks, + oDisableTrustedCertCRLCheck, + oEnableTrustedCertCRLCheck, + oForceCRLRefresh, + oEnableIssuerBasedCRLCheck, + + oDisableOCSP, + oEnableOCSP, + + oIncludeCerts, + oPolicyFile, + oDisablePolicyChecks, + oEnablePolicyChecks, + oAutoIssuerKeyRetrieve, + oMinRSALength, + + oWithFingerprint, + oWithMD5Fingerprint, + oWithKeygrip, + oWithSecret, + 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, + oKeyServer_deprecated, + oEncryptTo, + oNoEncryptTo, + oLoggerFD, + oDisableCipherAlgo, + oDisablePubkeyAlgo, + oIgnoreTimeConflict, + oNoRandomSeedFile, + oNoCommonCertsImport, + oIgnoreCertExtension, + oIgnoreCertWithOID, + oRequireCompliance, + oCompatibilityFlags, + oNoAutostart + }; + + +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, "generate-key", N_("generate a new key pair")), + ARGPARSE_c (aKeygen, "gen-key", "@"), + ARGPARSE_c (aDeleteKey, "delete-keys", + N_("remove keys from the public keyring")), +/*ARGPARSE_c (aSendKeys, "send-keys", N_("export keys to a keyserver")),*/ +/*ARGPARSE_c (aRecvKeys, "recv-keys", N_("import keys from a keyserver")),*/ + ARGPARSE_c (aImport, "import", N_("import certificates")), + ARGPARSE_c (aExport, "export", N_("export certificates")), + + /* We use -raw and not -p1 for pkcs#1 secret key export so that it + won't accidentally be used in case -p12 was intended. */ + ARGPARSE_c (aExportSecretKeyP12, "export-secret-key-p12", "@"), + ARGPARSE_c (aExportSecretKeyP8, "export-secret-key-p8", "@"), + ARGPARSE_c (aExportSecretKeyRaw, "export-secret-key-raw", "@"), + + 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, "change-passphrase", N_("change a passphrase")), + ARGPARSE_c (aPasswd, "passwd", "@"), + 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_header ("Monitor", N_("Options controlling the diagnostic output")), + + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oNoVerbose, "no-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_n (oNoGreeting, "no-greeting", "@"), + ARGPARSE_s_s (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 (oLogFile, "log-file", + N_("|FILE|write server mode logs to FILE")), + ARGPARSE_s_n (oNoLogFile, "no-log-file", "@"), + ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), + ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"), + + + ARGPARSE_header ("Configuration", + N_("Options controlling the configuration")), + + ARGPARSE_s_s (oHomedir, "homedir", "@"), + ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), + ARGPARSE_s_n (oPreferSystemDirmngr,"prefer-system-dirmngr", "@"), + 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_s (oCompliance, "compliance", "@"), + ARGPARSE_p_u (oMinRSALength, "min-rsa-length", "@"), + ARGPARSE_s_n (oNoCommonCertsImport, "no-common-certs-import", "@"), + ARGPARSE_s_s (oIgnoreCertExtension, "ignore-cert-extension", "@"), + ARGPARSE_s_s (oIgnoreCertWithOID, "ignore-cert-with-oid", "@"), + ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), + ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), + ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"), + ARGPARSE_s_s (oProtectToolProgram, "protect-tool-program", "@"), + + + ARGPARSE_header ("Input", N_("Options controlling the input")), + + 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_header ("Output", N_("Options controlling the output")), + + ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")), + ARGPARSE_s_n (oArmor, "armour", "@"), + ARGPARSE_s_n (oNoArmor, "no-armor", "@"), + ARGPARSE_s_n (oNoArmor, "no-armour", "@"), + ARGPARSE_s_n (oBase64, "base64", N_("create base-64 encoded output")), + ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), + + + ARGPARSE_header (NULL, N_("Options to specify keys")), + + ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), + ARGPARSE_s_s (oUser, "local-user", + N_("|USER-ID|use USER-ID to sign or decrypt")), + ARGPARSE_s_s (oDefaultKey, "default-key", + N_("|USER-ID|use USER-ID as default secret key")), + ARGPARSE_s_s (oEncryptTo, "encrypt-to", + N_("|NAME|encrypt to user ID NAME as well")), + ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"), + /* 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 (oKeyring, "keyring", + N_("|FILE|add keyring to the list of keyrings")), + ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"), + ARGPARSE_s_s (oKeyServer_deprecated, "ldapserver", "@"), + ARGPARSE_s_s (oKeyServer, "keyserver", "@"), + + ARGPARSE_header ("ImportExport", + N_("Options controlling key import and export")), + + ARGPARSE_s_n (oDisableDirmngr, "disable-dirmngr", + N_("disable all access to the dirmngr")), + ARGPARSE_s_n (oAutoIssuerKeyRetrieve, "auto-issuer-key-retrieve", + N_("fetch missing issuer certificates")), + ARGPARSE_s_s (oP12Charset, "p12-charset", + N_("|NAME|use encoding NAME for PKCS#12 passphrases")), + + + ARGPARSE_header ("Keylist", N_("Options controlling key listings")), + + 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_n (oWithKeygrip, "with-keygrip", "@"), + ARGPARSE_s_n (oWithSecret, "with-secret", "@"), + + ARGPARSE_header ("Security", N_("Options controlling the security")), + + 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", + N_("do not check CRLs for root certificates")), + ARGPARSE_s_n (oEnableTrustedCertCRLCheck, + "enable-trusted-cert-crl-check", "@"), + ARGPARSE_s_n (oDisableOCSP, "disable-ocsp", "@"), + ARGPARSE_s_n (oEnableOCSP, "enable-ocsp", N_("check validity using OCSP")), + ARGPARSE_s_n (oDisablePolicyChecks, "disable-policy-checks", + N_("do not check certificate policies")), + ARGPARSE_s_n (oEnablePolicyChecks, "enable-policy-checks", "@"), + 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_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 (oRequireCompliance, "require-compliance", "@"), + + + ARGPARSE_header (NULL, N_("Options for unattended use")), + + ARGPARSE_s_n (oBatch, "batch", N_("batch mode: never ask")), + ARGPARSE_s_n (oNoBatch, "no-batch", "@"), + ARGPARSE_s_n (oAnswerYes, "yes", N_("assume yes on most questions")), + ARGPARSE_s_n (oAnswerNo, "no", N_("assume no on most questions")), + ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"), + ARGPARSE_s_i (oPassphraseFD, "passphrase-fd", "@"), + ARGPARSE_s_s (oPinentryMode, "pinentry-mode", "@"), + + + ARGPARSE_header (NULL, N_("Other options")), + + ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")), + ARGPARSE_noconffile (oNoOptions, "no-options", "@"), + ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), + ARGPARSE_s_s (oRequestOrigin, "request-origin", "@"), + ARGPARSE_s_n (oForceCRLRefresh, "force-crl-refresh", "@"), + ARGPARSE_s_n (oEnableIssuerBasedCRLCheck, "enable-issuer-based-crl-check", + "@"), + ARGPARSE_s_s (oAuditLog, "audit-log", + N_("|FILE|write an audit log to FILE")), + ARGPARSE_s_s (oHtmlAuditLog, "html-audit-log", "@"), + 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 (oCompatibilityFlags, "compatibility-flags", "@"), + + ARGPARSE_header (NULL, ""), /* Stop the header group. */ + + + /* Command aliases. */ + ARGPARSE_c (aListKeys, "list-key", "@"), + ARGPARSE_c (aListChain, "list-signatures", "@"), + ARGPARSE_c (aListChain, "list-sigs", "@"), + ARGPARSE_c (aListChain, "check-signatures", "@"), + ARGPARSE_c (aListChain, "check-sigs", "@"), + ARGPARSE_c (aDeleteKey, "delete-key", "@"), + + ARGPARSE_group (302, N_( + "@\n(See the man page for a complete listing of all commands and options)\n" + )), + + ARGPARSE_end () +}; + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_X509_VALUE , "x509" }, + { DBG_MPI_VALUE , "mpi" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_MEMORY_VALUE , "memory" }, + { DBG_CACHE_VALUE , "cache" }, + { DBG_MEMSTAT_VALUE, "memstat" }, + { DBG_HASHING_VALUE, "hashing" }, + { DBG_IPC_VALUE , "ipc" }, + { 0, NULL } + }; + + +/* The list of compatibility flags. */ +static struct compatibility_flags_s compatibility_flags [] = + { + { COMPAT_ALLOW_KA_TO_ENCR, "allow-ka-to-encr" }, + { 0, NULL } + }; + + +/* 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; + +/* Default value for include-certs. We need an extra macro for + gpgconf-list because the variable will be changed by the command + line option. + + It is often cumbersome to locate intermediate certificates, thus by + default we include all certificates in the chain. However we leave + out the root certificate because that would make it too easy for + the recipient to import that root certificate. A root certificate + should be installed only after due checks and thus it won't help to + send it along with each message. */ +#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; + +/* The default cipher algo. */ +#define DEFAULT_CIPHER_ALGO "AES" + + +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 open_read (const char *filename); +static estream_t open_es_fread (const char *filename, const char *mode); +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 9: p = "GPL-3.0-or-later"; break; + case 11: p = "@GPGSM@ (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: p = _("Usage: @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 = gnupg_homedir (); break; + case 33: p = _("\nSupported algorithms:\n"); break; + case 34: + if (!ciphers) + ciphers = build_list ("Cipher: ", gnupg_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) + strcpy (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) +{ + fprintf (stderr, _("usage: %s [options] %s\n"), GPGSM_NAME, text); + 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_IPC_VALUE; + else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) + opt.debug = DBG_IPC_VALUE|DBG_X509_VALUE; + else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) + opt.debug = (DBG_IPC_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) + parse_debug_flag (NULL, &opt.debug, debug_flags); + + /* minip12.c may be used outside of GnuPG, thus we don't have the + * opt structure over there. */ + p12_set_verbosity (opt.verbose); +} + + + +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; +} + + + +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; + char *last_configname = NULL; + const char *configname = NULL; /* NULL or points to last_configname. + * NULL also indicates that we are + * processing options from the cmdline. */ + int debug_argparser = 0; + int no_more_options = 0; + 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; + int pwfd = -1; + + static const char *homedirvalue; + + early_system_init (); + gnupg_reopen_std (GPGSM_NAME); + /* trap_unaligned ();*/ + gnupg_rl_initialize (); + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + + /* Please note that we may running SUID(ROOT), so be very CAREFUL + when adding any stuff between here and the call to secmem_init() + somewhere after the option parsing */ + log_set_prefix (GPGSM_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY); + + /* Make sure that our subsystems are ready. */ + i18n_init (); + init_common_subsystems (&argc, &argv); + + /* Check that the libraries are suitable. Do it here because the + option parse may need services of the library */ + 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); + + dotlock_create (NULL, 0); /* Register lockfile cleanup. */ + + /* Tell the compliance module who we are. */ + gnupg_initialize_compliance (GNUPG_MODULE_NAME_GPGSM); + + opt.autostart = 1; + 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 = DEFAULT_CIPHER_ALGO; + + + /* 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= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + while (gnupg_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oDebug: + case oDebugAll: + debug_argparser++; + break; + + case oNoOptions: + /* Set here here because the homedir would otherwise be + * created before main option parsing starts. */ + opt.no_homedir_creation = 1; + break; + + case oHomedir: + homedirvalue = pargs.r.ret_str; + break; + + case aCallProtectTool: + /* Make sure that --version and --help are passed to the + * protect-tool. */ + goto leave_cmdline_parser; + } + } + leave_cmdline_parser: + /* Reset the flags. */ + pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + + + /* 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_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + setup_libassuan_logging (&opt.debug, NULL); + + /* Set homedir. */ + gnupg_set_homedir (homedirvalue); + + /* 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 policy file */ + opt.policy_file = make_filename (gnupg_homedir (), "policies.txt", NULL); + + /* The configuraton directories for use by gpgrt_argparser. */ + gnupg_set_confdir (GNUPG_CONFDIR_SYS, gnupg_sysconfdir ()); + gnupg_set_confdir (GNUPG_CONFDIR_USER, gnupg_homedir ()); + + /* We are re-using the struct, thus the reset flag. We OR the + * flags so that the internal intialized flag won't be cleared. */ + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags |= (ARGPARSE_FLAG_RESET + | ARGPARSE_FLAG_KEEP + | ARGPARSE_FLAG_SYS + | ARGPARSE_FLAG_USER); + + while (!no_more_options + && gnupg_argparser (&pargs, opts, GPGSM_NAME EXTSEP_S "conf")) + { + switch (pargs.r_opt) + { + case ARGPARSE_CONFFILE: + if (debug_argparser) + log_info (_("reading options from '%s'\n"), + pargs.r_type? pargs.r.ret_str: "[cmdline]"); + if (pargs.r_type) + { + xfree (last_configname); + last_configname = xstrdup (pargs.r.ret_str); + configname = last_configname; + } + else + configname = NULL; + break; + + case aGPGConfList: + case aGPGConfTest: + set_cmd (&cmd, pargs.r_opt); + do_not_setup_keys = 1; + default_keyring = 0; + 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 aExportSecretKeyP8: + case aExportSecretKeyRaw: + 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; + + case oPassphraseFD: + pwfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); + break; + + case oPinentryMode: + opt.pinentry_mode = parse_pinentry_mode (pargs.r.ret_str); + if (opt.pinentry_mode == -1) + log_error (_("invalid pinentry mode '%s'\n"), pargs.r.ret_str); + break; + + case oRequestOrigin: + opt.request_origin = parse_request_origin (pargs.r.ret_str); + if (opt.request_origin == -1) + log_error (_("invalid request origin '%s'\n"), 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 oEnableIssuerBasedCRLCheck: + opt.enable_issuer_based_crl_check = 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: + if (parse_debug_flag (pargs.r.ret_str, &debug_value, debug_flags)) + { + pargs.r_opt = ARGPARSE_INVALID_ARG; + pargs.err = ARGPARSE_PRINT_ERROR; + } + 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 oCompatibilityFlags: + if (parse_compatibility_flags (pargs.r.ret_str, &opt.compat_flags, + compatibility_flags)) + { + pargs.r_opt = ARGPARSE_INVALID_ARG; + pargs.err = ARGPARSE_PRINT_ERROR; + } + break; + + case oStatusFD: + ctrl.status_fd = translate_sys2libc_fd_int (pargs.r.ret_int, 1); + break; + case oLoggerFD: + log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); + break; + case oWithMD5Fingerprint: + opt.with_md5_fingerprint=1; /*fall through*/ + case oWithFingerprint: + with_fpr=1; /*fall through*/ + case aFingerprint: + opt.fingerprint++; + break; + + case oWithKeygrip: + opt.with_keygrip = 1; + break; + + case oHomedir: gnupg_set_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: /* Obsolete */; 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 through */ + case oWithColons: ctrl.with_colons = 1; break; + case oWithSecret: ctrl.with_secret = 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: + enable_special_filenames (); + break; + + case oValidationModel: parse_validation_model (pargs.r.ret_str); break; + + case oKeyServer: + append_to_strlist (&opt.keyserver, pargs.r.ret_str); + break; + + case oKeyServer_deprecated: + obsolete_option (configname, pargs.lineno, "ldapserver"); + break; + + case oIgnoreCertExtension: + add_to_strlist (&opt.ignored_cert_extensions, pargs.r.ret_str); + break; + + case oIgnoreCertWithOID: + add_to_strlist (&opt.ignore_cert_with_oid, pargs.r.ret_str); + break; + + case oNoAutostart: opt.autostart = 0; break; + + case oCompliance: + { + struct gnupg_compliance_option compliance_options[] = + { + { "gnupg", CO_GNUPG }, + { "de-vs", CO_DE_VS } + }; + int compliance = gnupg_parse_compliance_option (pargs.r.ret_str, + compliance_options, + DIM (compliance_options), + opt.quiet); + if (compliance < 0) + log_inc_errorcount (); /* Force later termination. */ + opt.compliance = compliance; + } + break; + + case oMinRSALength: opt.min_rsa_length = pargs.r.ret_ulong; break; + + case oRequireCompliance: opt.require_compliance = 1; break; + + default: + if (configname) + pargs.err = ARGPARSE_PRINT_WARNING; + else + { + pargs.err = ARGPARSE_PRINT_ERROR; + /* The argparse function calls a plain exit and thus we + * need to print a status here. */ + gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser", + gpg_error (GPG_ERR_GENERAL)); + } + break; + } + } + + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (!last_configname) + opt.config_filename = make_filename (gnupg_homedir (), + GPGSM_NAME EXTSEP_S "conf", + NULL); + else + opt.config_filename = last_configname; + + if (log_get_errorcount(0)) + { + gpgsm_status_with_error (&ctrl, STATUS_FAILURE, + "option-parser", gpg_error (GPG_ERR_GENERAL)); + gpgsm_exit(2); + } + + if (pwfd != -1) /* Read the passphrase now. */ + read_passphrase_from_fd (pwfd); + + /* 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) + { + es_fprintf (es_stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + es_fprintf (es_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 officially 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, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); + } + + 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"); + } + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (_("Note: '%s' is not considered an option\n"), argv[i]); + } + +/*FIXME if (opt.batch) */ +/* tty_batchmode (1); */ + + gcry_control (GCRYCTL_RESUME_SECMEM_WARN); + + set_debug (); + if (opt.verbose) /* Print the compatibility flags. */ + parse_compatibility_flags (NULL, &opt.compat_flags, compatibility_flags); + gnupg_set_compliance_extra_info (opt.min_rsa_length); + + /* Although we always 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, "AES192") ) + opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.22"; + 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, "SERPENT256") ) + 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")); + } + } + + /* Check our chosen algorithms against the list of allowed + * algorithms in the current compliance mode, and fail hard if it is + * not. This is us being nice to the user informing her early that + * the chosen algorithms are not available. We also check and + * enforce this right before the actual operation. */ + if (! gnupg_cipher_is_allowed (opt.compliance, + cmd == aEncr || cmd == aSignEncr, + gcry_cipher_map_name (opt.def_cipher_algoid), + GCRY_CIPHER_MODE_NONE) + && ! gnupg_cipher_is_allowed (opt.compliance, + cmd == aEncr || cmd == aSignEncr, + gcry_cipher_mode_from_oid + (opt.def_cipher_algoid), + GCRY_CIPHER_MODE_NONE)) + log_error (_("cipher algorithm '%s' may not be used in %s mode\n"), + opt.def_cipher_algoid, + gnupg_compliance_option_string (opt.compliance)); + + if (forced_digest_algo + && ! gnupg_digest_is_allowed (opt.compliance, + cmd == aSign + || cmd == aSignEncr + || cmd == aClearsign, + opt.forced_digest_algo)) + log_error (_("digest algorithm '%s' may not be used in %s mode\n"), + forced_digest_algo, + gnupg_compliance_option_string (opt.compliance)); + + if (extra_digest_algo + && ! gnupg_digest_is_allowed (opt.compliance, + cmd == aSign + || cmd == aSignEncr + || cmd == aClearsign, + opt.extra_digest_algo)) + log_error (_("digest algorithm '%s' may not be used in %s mode\n"), + extra_digest_algo, + gnupg_compliance_option_string (opt.compliance)); + + if (log_get_errorcount(0)) + { + gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-postprocessing", + gpg_error (GPG_ERR_GENERAL)); + gpgsm_exit (2); + } + + /* Set the random seed file. */ + if (use_random_seed) + { + char *p = make_filename (gnupg_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 (&ctrl, "pubring.kbx", 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 (!gnupg_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 (&ctrl, sl->d, 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) + { + int errcount = log_get_errorcount (0); + + 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 keeping 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); + } + + /* We do not require a recipient for decryption but because + * recipients and signers are always checked and log_error is + * sometimes used (for failed signing keys or due to a failed + * CRL checking) that would have bumbed up the error counter. + * We clear the counter in the decryption case because there is + * no reason to force decryption to fail. */ + if (cmd == aDecrypt && !errcount) + log_get_errorcount (1); /* clear counter */ + } + + 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. */ + + es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT); + es_printf ("include-certs:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, + DEFAULT_INCLUDE_CERTS); + es_printf ("cipher-algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, + DEFAULT_CIPHER_ALGO); + es_printf ("p12-charset:%lu:\n", GC_OPT_FLAG_DEFAULT); + es_printf ("default-key:%lu:\n", GC_OPT_FLAG_DEFAULT); + es_printf ("encrypt-to:%lu:\n", GC_OPT_FLAG_DEFAULT); + + /* The next one is an info only item and should match what + proc_parameters actually implements. */ + es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, + "RSA-3072"); + } + 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. */ + { + estream_t fp = open_es_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]"); + + es_fclose (fp); + } + break; + + case aSign: /* Sign the given file. */ + { + estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); + + /* Fixme: We should also allow concatenation of 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]"); + + es_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: + { + estream_t 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_es_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]]"); + + es_fclose (fp); + } + break; + + case aDecrypt: + { + estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); + gpg_error_t err; + + set_binary (stdin); + if (!argc) + err = gpgsm_decrypt (&ctrl, 0, fp); /* from stdin */ + else if (argc == 1) + err = gpgsm_decrypt (&ctrl, open_read (*argv), fp); /* from file */ + else + wrong_args ("--decrypt [filename]"); + +#if GPGRT_VERSION_NUMBER >= 0x012700 /* 1.39 */ + if (err) + gpgrt_fcancel (fp); + else +#endif + es_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; /* fall through */ + 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; + estream_t fpout; + + if (opt.batch) + { + if (!argc) /* Create from stdin. */ + fpin = open_es_fread ("-", "r"); + else if (argc == 1) /* From file. */ + fpin = open_es_fread (*argv, "r"); + else + wrong_args ("--generate-key --batch [parmfile]"); + } + + fpout = open_es_fwrite (opt.outfile?opt.outfile:"-"); + + if (fpin) + gpgsm_genkey (&ctrl, fpin, fpout); + else + gpgsm_gencertreq_tty (&ctrl, fpout); + + es_fclose (fpout); + } + break; + + + case aImport: + gpgsm_import_files (&ctrl, argc, argv, open_read); + break; + + case aExport: + { + estream_t fp; + + fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_export (&ctrl, sl, fp); + free_strlist(sl); + es_fclose (fp); + } + break; + + case aExportSecretKeyP12: + { + estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); + + if (argc == 1) + gpgsm_p12_export (&ctrl, *argv, fp, 0); + else + wrong_args ("--export-secret-key-p12 KEY-ID"); + if (fp != es_stdout) + es_fclose (fp); + } + break; + + case aExportSecretKeyP8: + { + estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); + + if (argc == 1) + gpgsm_p12_export (&ctrl, *argv, fp, 1); + else + wrong_args ("--export-secret-key-p8 KEY-ID"); + if (fp != es_stdout) + es_fclose (fp); + } + break; + + case aExportSecretKeyRaw: + { + estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); + + if (argc == 1) + gpgsm_p12_export (&ctrl, *argv, fp, 2); + else + wrong_args ("--export-secret-key-raw KEY-ID"); + if (fp != es_stdout) + es_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 ("--change-passphrase <key-Id>"); + else + { + int rc; + ksba_cert_t cert = NULL; + char *grip = NULL; + + rc = gpgsm_find_cert (&ctrl, *argv, NULL, &cert, 0); + 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 */ + free_strlist (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; + ctrl->offline = opt.disable_dirmngr; +} + + +int +gpgsm_parse_validation_model (const char *model) +{ + if (!ascii_strcasecmp (model, "shell") ) + return 0; + else if ( !ascii_strcasecmp (model, "chain") ) + return 1; + else if ( !ascii_strcasecmp (model, "steed") ) + return 2; + else + return -1; +} + + + +/* Open the FILENAME for read and return the file descriptor. 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, 0); + if (fd != -1) + return fd; + fd = gnupg_open (filename, O_RDONLY | O_BINARY, 0); + 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, const char *mode) +{ + int fd; + estream_t fp; + + if (filename[0] == '-' && !filename[1]) + fd = fileno (stdin); + else + fd = check_special_filename (filename, 0, 0); + if (fd != -1) + { + fp = es_fdopen_nc (fd, mode); + if (!fp) + { + log_error ("es_fdopen(%d) failed: %s\n", fd, strerror (errno)); + gpgsm_exit (2); + } + return fp; + } + fp = es_fopen (filename, mode); + 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, 0); + 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) +{ +#ifdef HAVE_W32_SYSTEM + (void)argc; + (void)argv; +#else + 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..53ef165 --- /dev/null +++ b/sm/gpgsm.h @@ -0,0 +1,467 @@ +/* gpgsm.h - Global definitions for GpgSM + * Copyright (C) 2001, 2003, 2004, 2007, 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 <https://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/audit.h" +#include "../common/session-env.h" +#include "../common/ksba-io-support.h" +#include "../common/compliance.h" + + +#define MAX_DIGEST_LEN 64 + + +/* A large struct named "opt" to keep global flags. */ +EXTERN_UNLESS_MAIN_MODULE +struct +{ + unsigned int debug; /* debug flags (DBG_foo_VALUE) */ + int verbose; /* verbosity level */ + int quiet; /* be as quiet as possible */ + int 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 */ + int no_homedir_creation; + + 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; + + int autostart; + const char *dirmngr_program; + 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 with_keygrip; /* Option --with-keygrip active. */ + + int pinentry_mode; + int request_origin; + + 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_issuer_based_crl_check; /* Backward compatibility hack. */ + 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. */ + + 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. */ + + unsigned int min_rsa_length; /* Used for compliance checks. */ + + strlist_t 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; + + /* A list of OIDs which will be used to ignore certificates with + * sunch an OID during --learn-card. */ + strlist_t ignore_cert_with_oid; + + /* The current compliance mode. */ + enum gnupg_compliance_mode compliance; + + /* Fail if an operation can't be done in the requested compliance + * mode. */ + int require_compliance; + + /* Compatibility flags (COMPAT_FLAG_xxxx). */ + unsigned int compat_flags; +} 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_IPC_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_IPC (opt.debug & DBG_IPC_VALUE) + + +/* Compatibility flags */ +/* Telesec RSA cards produced for NRW in 2022 came with only the + * keyAgreement bit set. This flag allows there use for encryption + * anyway. Example cert: + * Issuer: /CN=DOI CA 10a/OU=DOI/O=PKI-1-Verwaltung/C=DE + * key usage: digitalSignature nonRepudiation keyAgreement + * policies: 1.3.6.1.4.1.7924.1.1:N: + */ +#define COMPAT_ALLOW_KA_TO_ENCR 1 + + +/* 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_secret; /* Mark secret keys in a public key listing. */ + 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; /* 0 := standard model (shell), + 1 := chain model, + 2 := STEED model. */ + int offline; /* If true gpgsm won't do any network access. */ + + /* The current time. Used as a helper in certchain.c. */ + ksba_isotime_t current_time; +}; + + +/* 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 --*/ +extern int gpgsm_errors_seen; + +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, ...) GPGRT_ATTR_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_status_with_error (ctrl_t ctrl, int no, const char *text, + gpg_error_t err); +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_pubkey_algo_string (ksba_cert_t cert, int *r_algoid); +char *gpgsm_get_certid (ksba_cert_t cert); + + +/*-- certdump.c --*/ +void gpgsm_print_serial (estream_t fp, ksba_const_sexp_t p); +void gpgsm_print_serial_decimal (estream_t fp, ksba_const_sexp_t sn); +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, gcry_sexp_t sigval, + gcry_md_hd_t md, + int hash_algo, unsigned int pkalgoflags, + 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 +#define VALIDATE_FLAG_STEED 4 + +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 silent); +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_cert_has_well_known_private_key (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 (ctrl_t ctrl, const char *name, ksba_sexp_t keyid, + ksba_cert_t *r_cert, int allow_ambiguous); + +/*-- 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, estream_t stream); +void gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream, + int rawmode); + +/*-- 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, estream_t 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, estream_t out_fp); + +/*-- encrypt.c --*/ +int gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, + int in_fd, estream_t out_fp); + +/*-- decrypt.c --*/ +int gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp); + +/*-- certreqgen.c --*/ +int gpgsm_genkey (ctrl_t ctrl, estream_t in_stream, estream_t out_stream); + +/*-- certreqgen-ui.c --*/ +void gpgsm_gencertreq_tty (ctrl_t ctrl, estream_t out_stream); + + +/*-- 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); +gpg_error_t gpgsm_agent_ask_passphrase (ctrl_t ctrl, const char *desc_msg, + int repeat, char **r_passphrase); +gpg_error_t gpgsm_agent_keywrap_key (ctrl_t ctrl, int forexport, + void **r_kek, size_t *r_keklen); +gpg_error_t gpgsm_agent_import_key (ctrl_t ctrl, + const void *key, size_t keylen); +gpg_error_t gpgsm_agent_export_key (ctrl_t ctrl, const char *keygrip, + const char *desc, + unsigned char **r_result, + size_t *r_resultlen); + +/*-- 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, const char *uri, + 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); +gpg_error_t transform_sigval (const unsigned char *sigval, size_t sigvallen, + int mdalgo, + unsigned char **r_newsigval, + size_t *r_newsigvallen); +gcry_sexp_t gpgsm_ksba_cms_get_sig_val (ksba_cms_t cms, int idx); +int gpgsm_get_hash_algo_from_sigval (gcry_sexp_t sigval, + unsigned int *r_pkalgo_flags); + + + +#endif /*GPGSM_H*/ diff --git a/sm/gpgsm.w32-manifest.in b/sm/gpgsm.w32-manifest.in new file mode 100644 index 0000000..3055788 --- /dev/null +++ b/sm/gpgsm.w32-manifest.in @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<description>GNU Privacy Guard (X409/CMS tool)</description> +<assemblyIdentity + type="win32" + name="GnuPG.gpgsm" + version="@BUILD_VERSION@" + /> +<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- 10 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><!-- 8.1 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><!-- 8 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><!-- 7 --> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><!-- Vista --> + </application> +</compatibility> +</assembly> diff --git a/sm/import.c b/sm/import.c new file mode 100644 index 0000000..8a75a2b --- /dev/null +++ b/sm/import.c @@ -0,0 +1,941 @@ +/* import.c - Import certificates + * Copyright (C) 2001, 2003, 2004, 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 <https://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 "../common/exechelp.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */ +#include "../common/membuf.h" +#include "minip12.h" + +/* The arbitrary limit of one PKCS#12 object. */ +#define MAX_P12OBJ_SIZE 128 /*kb*/ + + +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; + }; + + +struct rsa_secret_key_s +{ + gcry_mpi_t n; /* public modulus */ + gcry_mpi_t e; /* public exponent */ + gcry_mpi_t d; /* exponent */ + gcry_mpi_t p; /* prime p. */ + gcry_mpi_t q; /* prime q. */ + gcry_mpi_t u; /* inverse of p mod q. */ +}; + + +static gpg_error_t parse_p12 (ctrl_t ctrl, ksba_reader_t reader, + 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 (ctrl, 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; + gnupg_ksba_io_t b64reader = NULL; + ksba_reader_t reader; + ksba_cert_t cert = NULL; + ksba_cms_t cms = NULL; + estream_t fp = NULL; + ksba_content_type_t ct; + int any = 0; + + fp = es_fdopen_nc (in_fd, "rb"); + if (!fp) + { + rc = gpg_error_from_syserror (); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + rc = gnupg_ksba_create_reader + (&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0) + | (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0) + | (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0) + | GNUPG_KSBA_IO_MULTIPEM), + fp, &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. */ + rc = parse_p12 (ctrl, reader, stats); + if (!rc) + any = 1; + } + 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 (!gnupg_ksba_reader_eof_seen (b64reader)); + + leave: + if (any && gpg_err_code (rc) == GPG_ERR_EOF) + rc = 0; + ksba_cms_release (cms); + ksba_cert_release (cert); + gnupg_ksba_destroy_reader (b64reader); + es_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 (); + 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 = classify_user_id (line, &desc, 0); + if (err) + { + print_import_problem (ctrl, NULL, 0); + stats->not_imported++; + continue; + } + + keydb_search_reset (kh); + err = keydb_search (ctrl, 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 (ctrl, 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; +} + + +/* Check that the RSA secret key SKEY is valid. Swap parameters to + the libgcrypt standard. */ +static gpg_error_t +rsa_key_check (struct rsa_secret_key_s *skey) +{ + int err = 0; + gcry_mpi_t t = gcry_mpi_snew (0); + gcry_mpi_t t1 = gcry_mpi_snew (0); + gcry_mpi_t t2 = gcry_mpi_snew (0); + gcry_mpi_t phi = gcry_mpi_snew (0); + + /* Check that n == p * q. */ + gcry_mpi_mul (t, skey->p, skey->q); + if (gcry_mpi_cmp( t, skey->n) ) + { + log_error ("RSA oops: n != p * q\n"); + err++; + } + + /* Check that p is less than q. */ + if (gcry_mpi_cmp (skey->p, skey->q) > 0) + { + gcry_mpi_t tmp; + + log_info ("swapping secret primes\n"); + tmp = gcry_mpi_copy (skey->p); + gcry_mpi_set (skey->p, skey->q); + gcry_mpi_set (skey->q, tmp); + gcry_mpi_release (tmp); + /* Recompute u. */ + gcry_mpi_invm (skey->u, skey->p, skey->q); + } + + /* Check that e divides neither p-1 nor q-1. */ + gcry_mpi_sub_ui (t, skey->p, 1 ); + gcry_mpi_div (NULL, t, t, skey->e, 0); + if (!gcry_mpi_cmp_ui( t, 0) ) + { + log_error ("RSA oops: e divides p-1\n"); + err++; + } + gcry_mpi_sub_ui (t, skey->q, 1); + gcry_mpi_div (NULL, t, t, skey->e, 0); + if (!gcry_mpi_cmp_ui( t, 0)) + { + log_info ("RSA oops: e divides q-1\n" ); + err++; + } + + /* Check that d is correct. */ + gcry_mpi_sub_ui (t1, skey->p, 1); + gcry_mpi_sub_ui (t2, skey->q, 1); + gcry_mpi_mul (phi, t1, t2); + gcry_mpi_invm (t, skey->e, phi); + if (gcry_mpi_cmp (t, skey->d)) + { + /* No: try universal exponent. */ + gcry_mpi_gcd (t, t1, t2); + gcry_mpi_div (t, NULL, phi, t, 0); + gcry_mpi_invm (t, skey->e, t); + if (gcry_mpi_cmp (t, skey->d)) + { + log_error ("RSA oops: bad secret exponent\n"); + err++; + } + } + + /* Check for correctness of u. */ + gcry_mpi_invm (t, skey->p, skey->q); + if (gcry_mpi_cmp (t, skey->u)) + { + log_info ("RSA oops: bad u parameter\n"); + err++; + } + + if (err) + log_info ("RSA secret key check failed\n"); + + gcry_mpi_release (t); + gcry_mpi_release (t1); + gcry_mpi_release (t2); + gcry_mpi_release (phi); + + return err? gpg_error (GPG_ERR_BAD_SECKEY):0; +} + + +/* Object passed to store_cert_cb. */ +struct store_cert_parm_s +{ + gpg_error_t err; /* First error seen. */ + struct stats_s *stats; /* The stats object. */ + ctrl_t ctrl; /* The control object. */ +}; + +/* Helper to store the DER encoded certificate CERTDATA of length + CERTDATALEN. */ +static void +store_cert_cb (void *opaque, + const unsigned char *certdata, size_t certdatalen) +{ + struct store_cert_parm_s *parm = opaque; + gpg_error_t err; + ksba_cert_t cert; + + err = ksba_cert_new (&cert); + if (err) + { + if (!parm->err) + parm->err = err; + return; + } + + err = ksba_cert_init_from_mem (cert, certdata, certdatalen); + if (err) + { + log_error ("failed to parse a certificate: %s\n", gpg_strerror (err)); + if (!parm->err) + parm->err = err; + } + else + check_and_store (parm->ctrl, parm->stats, cert, 0); + ksba_cert_release (cert); +} + + +/* Assume that the reader is at a pkcs#12 message and try to import + certificates from that stupid format. We will transfer secret + keys to the agent. */ +static gpg_error_t +parse_p12 (ctrl_t ctrl, ksba_reader_t reader, struct stats_s *stats) +{ + gpg_error_t err = 0; + char buffer[1024]; + size_t ntotal, nread; + membuf_t p12mbuf; + char *p12buffer = NULL; + size_t p12buflen; + size_t p12bufoff; + gcry_mpi_t *kparms = NULL; + struct rsa_secret_key_s sk; + char *passphrase = NULL; + unsigned char *key = NULL; + size_t keylen; + void *kek = NULL; + size_t keklen; + unsigned char *wrappedkey = NULL; + size_t wrappedkeylen; + gcry_cipher_hd_t cipherhd = NULL; + gcry_sexp_t s_key = NULL; + unsigned char grip[20]; + int bad_pass = 0; + int i; + struct store_cert_parm_s store_cert_parm; + + memset (&store_cert_parm, 0, sizeof store_cert_parm); + store_cert_parm.ctrl = ctrl; + store_cert_parm.stats = stats; + + init_membuf (&p12mbuf, 4096); + ntotal = 0; + while (!(err = ksba_reader_read (reader, buffer, sizeof buffer, &nread))) + { + if (ntotal >= MAX_P12OBJ_SIZE*1024) + { + /* Arbitrary limit to avoid DoS attacks. */ + err = gpg_error (GPG_ERR_TOO_LARGE); + log_error ("pkcs#12 object is larger than %dk\n", MAX_P12OBJ_SIZE); + break; + } + put_membuf (&p12mbuf, buffer, nread); + ntotal += nread; + } + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; + if (!err) + { + p12buffer = get_membuf (&p12mbuf, &p12buflen); + if (!p12buffer) + err = gpg_error_from_syserror (); + } + if (err) + { + log_error (_("error reading input: %s\n"), gpg_strerror (err)); + goto leave; + } + + /* GnuPG 2.0.4 accidentally created binary P12 files with the string + "The passphrase is %s encoded.\n\n" prepended to the ASN.1 data. + We fix that here. */ + if (p12buflen > 29 && !memcmp (p12buffer, "The passphrase is ", 18)) + { + for (p12bufoff=18; + p12bufoff < p12buflen && p12buffer[p12bufoff] != '\n'; + p12bufoff++) + ; + p12bufoff++; + if (p12bufoff < p12buflen && p12buffer[p12bufoff] == '\n') + p12bufoff++; + } + else + p12bufoff = 0; + + + err = gpgsm_agent_ask_passphrase + (ctrl, + i18n_utf8 (N_("Please enter the passphrase to unprotect the PKCS#12 object.")), + 0, &passphrase); + if (err) + goto leave; + + kparms = p12_parse (p12buffer + p12bufoff, p12buflen - p12bufoff, + passphrase, store_cert_cb, &store_cert_parm, + &bad_pass, NULL); + + xfree (passphrase); + passphrase = NULL; + + if (!kparms) + { + log_error ("error parsing or decrypting the PKCS#12 file\n"); + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + +/* print_mpi (" n", kparms[0]); */ +/* print_mpi (" e", kparms[1]); */ +/* print_mpi (" d", kparms[2]); */ +/* print_mpi (" p", kparms[3]); */ +/* print_mpi (" q", kparms[4]); */ +/* print_mpi ("dmp1", kparms[5]); */ +/* print_mpi ("dmq1", kparms[6]); */ +/* print_mpi (" u", kparms[7]); */ + + sk.n = kparms[0]; + sk.e = kparms[1]; + sk.d = kparms[2]; + sk.q = kparms[3]; + sk.p = kparms[4]; + sk.u = kparms[7]; + err = rsa_key_check (&sk); + if (err) + goto leave; +/* print_mpi (" n", sk.n); */ +/* print_mpi (" e", sk.e); */ +/* print_mpi (" d", sk.d); */ +/* print_mpi (" p", sk.p); */ +/* print_mpi (" q", sk.q); */ +/* print_mpi (" u", sk.u); */ + + /* Create an S-expression from the parameters. */ + err = gcry_sexp_build (&s_key, NULL, + "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))", + sk.n, sk.e, sk.d, sk.p, sk.q, sk.u, NULL); + for (i=0; i < 8; i++) + gcry_mpi_release (kparms[i]); + gcry_free (kparms); + kparms = NULL; + if (err) + { + log_error ("failed to create S-expression from key: %s\n", + gpg_strerror (err)); + goto leave; + } + + /* Compute the keygrip. */ + if (!gcry_pk_get_keygrip (s_key, grip)) + { + err = gpg_error (GPG_ERR_GENERAL); + log_error ("can't calculate keygrip\n"); + goto leave; + } + if (DBG_X509) + log_printhex (grip, 20, "keygrip="); + + /* Convert to canonical encoding using a function which pads it to a + multiple of 64 bits. We need this padding for AESWRAP. */ + err = make_canon_sexp_pad (s_key, 1, &key, &keylen); + if (err) + { + log_error ("error creating canonical S-expression\n"); + goto leave; + } + gcry_sexp_release (s_key); + s_key = NULL; + + /* Get the current KEK. */ + err = gpgsm_agent_keywrap_key (ctrl, 0, &kek, &keklen); + if (err) + { + log_error ("error getting the KEK: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Wrap the key. */ + err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_AESWRAP, 0); + if (err) + goto leave; + err = gcry_cipher_setkey (cipherhd, kek, keklen); + if (err) + goto leave; + xfree (kek); + kek = NULL; + + wrappedkeylen = keylen + 8; + wrappedkey = xtrymalloc (wrappedkeylen); + if (!wrappedkey) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen); + if (err) + goto leave; + xfree (key); + key = NULL; + gcry_cipher_close (cipherhd); + cipherhd = NULL; + + /* Send the wrapped key to the agent. */ + err = gpgsm_agent_import_key (ctrl, wrappedkey, wrappedkeylen); + if (!err) + { + stats->count++; + stats->secret_read++; + stats->secret_imported++; + } + else if ( gpg_err_code (err) == GPG_ERR_EEXIST ) + { + err = 0; + stats->count++; + stats->secret_read++; + stats->secret_dups++; + } + + /* If we did not get an error from storing the secret key we return + a possible error from parsing the certificates. We do this after + storing the secret keys so that a bad certificate does not + inhibit our chance to store the secret key. */ + if (!err && store_cert_parm.err) + err = store_cert_parm.err; + + leave: + if (kparms) + { + for (i=0; i < 8; i++) + gcry_mpi_release (kparms[i]); + gcry_free (kparms); + kparms = NULL; + } + xfree (key); + gcry_sexp_release (s_key); + xfree (passphrase); + gcry_cipher_close (cipherhd); + xfree (wrappedkey); + xfree (kek); + xfree (get_membuf (&p12mbuf, NULL)); + xfree (p12buffer); + + 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..564d449 --- /dev/null +++ b/sm/keydb.c @@ -0,0 +1,1322 @@ +/* keydb.c - key database dispatcher + * Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc. + * Copyright (C) 2014 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <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 "../common/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; + dotlock_t lockhandle; +}; + +static struct resource_item all_resources[MAX_KEYDB_RESOURCES]; +static int used_resources; + +/* Whether we have successfully registered any resource. */ +static int any_registered; + + +struct keydb_handle { + int found; + int saved_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); + + +static void +try_make_homedir (const char *fname) +{ + if ( opt.dry_run || opt.no_homedir_creation ) + return; + + gnupg_maybe_make_homedir (fname, opt.quiet); +} + + +/* Handle the creation of a keybox if it does not yet exist. Take + into acount that other processes might have the keybox already + locked. This lock check does not work if the directory itself is + not yet available. If R_CREATED is not NULL it will be set to true + if the function created a new keybox. */ +static gpg_error_t +maybe_create_keybox (char *filename, int force, int *r_created) +{ + gpg_err_code_t ec; + dotlock_t lockhd = NULL; + estream_t fp; + int rc; + mode_t oldmask; + char *last_slash_in_filename; + int save_slash; + + if (r_created) + *r_created = 0; + + /* A quick test whether the filename already exists. */ + if (!gnupg_access (filename, F_OK)) + return !gnupg_access (filename, R_OK)? 0 : gpg_error (GPG_ERR_EACCES); + + /* If we don't want to create a new file at all, there is no need to + go any further - bail out right here. */ + if (!force) + return gpg_error (GPG_ERR_ENOENT); + + /* First of all we try to create the home directory. Note, that we + don't do any locking here because any sane application of gpg + would create the home directory by itself and not rely on gpg's + tricky auto-creation which is anyway only done for some home + directory name patterns. */ + last_slash_in_filename = strrchr (filename, DIRSEP_C); +#if HAVE_W32_SYSTEM + { + /* Windows may either have a slash or a backslash. Take care of it. */ + char *p = strrchr (filename, '/'); + if (!last_slash_in_filename || p > last_slash_in_filename) + last_slash_in_filename = p; + } +#endif /*HAVE_W32_SYSTEM*/ + if (!last_slash_in_filename) + return gpg_error (GPG_ERR_ENOENT); /* No slash at all - should + not happen though. */ + save_slash = *last_slash_in_filename; + *last_slash_in_filename = 0; + if (gnupg_access(filename, F_OK)) + { + static int tried; + + if (!tried) + { + tried = 1; + try_make_homedir (filename); + } + if ((ec = gnupg_access (filename, F_OK))) + { + rc = gpg_error (ec); + *last_slash_in_filename = save_slash; + goto leave; + } + } + *last_slash_in_filename = save_slash; + + /* To avoid races with other instances of gpg/gpgsm trying to create or + update the keybox (it is removed during an update for a short + time), we do the next stuff in a locked state. */ + lockhd = dotlock_create (filename, 0); + if (!lockhd) + { + /* A reason for this to fail is that the directory is not + writable. However, this whole locking stuff does not make + sense if this is the case. An empty non-writable directory + with no keyring is not really useful at all. */ + if (opt.verbose) + log_info ("can't allocate lock for '%s'\n", filename ); + + if (!force) + return gpg_error (GPG_ERR_ENOENT); + else + return gpg_error (GPG_ERR_GENERAL); + } + + if ( dotlock_take (lockhd, -1) ) + { + /* This is something bad. Probably a stale lockfile. */ + log_info ("can't lock '%s'\n", filename); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + /* Now the real test while we are locked. */ + if (!gnupg_access(filename, F_OK)) + { + rc = 0; /* Okay, we may access the file now. */ + goto leave; + } + + /* The file does not yet exist, create it now. */ + oldmask = umask (077); + fp = es_fopen (filename, "wb"); + if (!fp) + { + rc = gpg_error_from_syserror (); + umask (oldmask); + log_error (_("error creating keybox '%s': %s\n"), + filename, gpg_strerror (rc)); + goto leave; + } + umask (oldmask); + + /* Make sure that at least one record is in a new keybox file, so + that the detection magic for OpenPGP keyboxes works the next time + it is used. */ + rc = _keybox_write_header_blob (fp, 0); + if (rc) + { + es_fclose (fp); + log_error (_("error creating keybox '%s': %s\n"), + filename, gpg_strerror (rc)); + goto leave; + } + + if (!opt.quiet) + log_info (_("keybox '%s' created\n"), filename); + if (r_created) + *r_created = 1; + + es_fclose (fp); + rc = 0; + + leave: + if (lockhd) + { + dotlock_release (lockhd); + dotlock_destroy (lockhd); + } + return rc; +} + + +/* + * 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 new keybox. + */ +gpg_error_t +keydb_add_resource (ctrl_t ctrl, const char *url, int force, int *auto_created) +{ + const char *resname = url; + char *filename = NULL; + gpg_error_t err = 0; + 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 it 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 ); + err = 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 (gnupg_homedir (), resname, NULL); + } + else + filename = xstrdup (resname); + + if (!force) + force = !any_registered; + + /* see whether we can determine the filetype */ + if (rt == KEYDB_RESOURCE_TYPE_NONE) + { + estream_t fp; + + fp = es_fopen( filename, "rb" ); + if (fp) + { + u32 magic; + + /* FIXME: check for the keybox magic */ + if (es_fread (&magic, 4, 1, fp) == 1 ) + { + if (magic == 0x13579ace || magic == 0xce9a5713) + ; /* GDBM magic - no more support */ + else + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + } + else /* maybe empty: assume keybox */ + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + + es_fclose (fp); + } + else /* no file yet: create keybox */ + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + } + + switch (rt) + { + case KEYDB_RESOURCE_TYPE_NONE: + log_error ("unknown type of key resource '%s'\n", url ); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + + case KEYDB_RESOURCE_TYPE_KEYBOX: + err = maybe_create_keybox (filename, force, auto_created); + if (err) + goto leave; + /* Now register the file */ + { + void *token; + + err = keybox_register_file (filename, 0, &token); + if (gpg_err_code (err) == GPG_ERR_EEXIST) + ; /* Already registered - ignore. */ + else if (err) + ; /* Other error. */ + else if (used_resources >= MAX_KEYDB_RESOURCES) + err = 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].lockhandle + = dotlock_create (filename, 0); + 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 (!dotlock_take (all_resources[used_resources].lockhandle, 0)) + { + KEYBOX_HANDLE kbxhd = keybox_new_x509 (token, 0); + + if (kbxhd) + { + keybox_compress (kbxhd); + keybox_release (kbxhd); + } + dotlock_release (all_resources[used_resources].lockhandle); + } + + used_resources++; + } + } + break; + + default: + log_error ("resource type of '%s' not supported\n", url); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + /* fixme: check directory permissions and print a warning */ + + leave: + if (err) + { + log_error ("keyblock resource '%s': %s\n", filename, gpg_strerror (err)); + gpgsm_status_with_error (ctrl, STATUS_ERROR, + "add_keyblock_resource", err); + } + else + any_registered = 1; + xfree (filename); + return err; +} + + +/* This is a helper requyired under Windows to close all files so that + * a rename will work. */ +void +keydb_close_all_files (void) +{ +#ifdef HAVE_W32_SYSTEM + int i; + + log_assert (used_resources <= MAX_KEYDB_RESOURCES); + for (i=0; i < used_resources; i++) + if (all_resources[i].type == KEYDB_RESOURCE_TYPE_KEYBOX) + keybox_close_all_files (all_resources[i].token); +#endif +} + + + +KEYDB_HANDLE +keydb_new (void) +{ + KEYDB_HANDLE hd; + int i, j; + + hd = xcalloc (1, sizeof *hd); + hd->found = -1; + hd->saved_found = -1; + + assert (used_resources <= MAX_KEYDB_RESOURCES); + for (i=j=0; i < used_resources; i++) + { + 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].lockhandle = all_resources[i].lockhandle; + hd->active[j].u.kr = keybox_new_x509 (all_resources[i].token, 0); + 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 original 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; On Windows it is + always required to disallow other processes to open the file which + in turn would inhibit our copy+update+rename method. The lock is + released with keydb_released. */ +gpg_error_t +keydb_lock (KEYDB_HANDLE hd) +{ + if (!hd) + return gpg_error (GPG_ERR_INV_HANDLE); + return lock_all (hd); +} + + +/* Same as keydb_lock but no check for an invalid 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 = dotlock_take (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) + dotlock_release (hd->active[i].lockhandle); + break; + } + } + } + + /* 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; + + 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) + dotlock_release (hd->active[i].lockhandle); + break; + } + } +} + + + +/* Push the last found state if any. */ +void +keydb_push_found_state (KEYDB_HANDLE hd) +{ + if (!hd) + return; + + if (hd->found < 0 || hd->found >= hd->used) + { + hd->saved_found = -1; + return; + } + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + keybox_push_found_state (hd->active[hd->found].u.kr); + break; + } + + hd->saved_found = hd->found; + hd->found = -1; +} + + +/* Pop the last found state. */ +void +keydb_pop_found_state (KEYDB_HANDLE hd) +{ + if (!hd) + return; + + hd->found = hd->saved_found; + hd->saved_found = -1; + if (hd->found < 0 || hd->found >= hd->used) + return; + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + keybox_pop_found_state (hd->active[hd->found].u.kr); + break; + } +} + + + +/* + 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 (!dotlock_is_locked (hd->active[hd->found].lockhandle)) + 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 (!dotlock_is_locked (hd->active[idx].lockhandle)) + 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; +} + + +/* + * 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 (!dotlock_is_locked (hd->active[hd->found].lockhandle)) + 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++) + { + 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 + */ +gpg_error_t +keydb_search_reset (KEYDB_HANDLE hd) +{ + int i; + gpg_error_t 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; +} + +/* + * 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 (ctrl_t ctrl, KEYDB_HANDLE hd, + KEYDB_SEARCH_DESC *desc, size_t ndesc) +{ + int rc; + unsigned long skipped; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if (!any_registered) + { + gpgsm_status_with_error (ctrl, STATUS_ERROR, "keydb_search", + gpg_error (GPG_ERR_KEYRING_OPEN)); + return gpg_error (GPG_ERR_NOT_FOUND); + } + + rc = lock_all (hd); + if (rc) + return rc; + rc = -1; + + 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, + KEYBOX_BLOBTYPE_X509, + NULL, &skipped); + break; + } + if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) + { /* EOF -> switch to next resource */ + hd->current++; + } + else if (!rc) + hd->found = hd->current; + } + + return rc; +} + + +int +keydb_search_first (ctrl_t ctrl, KEYDB_HANDLE hd) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_FIRST; + return keydb_search (ctrl, hd, &desc, 1); +} + +int +keydb_search_next (ctrl_t ctrl, KEYDB_HANDLE hd) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_NEXT; + return keydb_search (ctrl, hd, &desc, 1); +} + +int +keydb_search_kid (ctrl_t ctrl, 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 (ctrl, hd, &desc, 1); +} + +int +keydb_search_fpr (ctrl_t ctrl, 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 (ctrl, hd, &desc, 1); +} + +int +keydb_search_issuer (ctrl_t ctrl, 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 (ctrl, hd, &desc, 1); + return rc; +} + +int +keydb_search_issuer_sn (ctrl_t ctrl, 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 (ctrl, hd, &desc, 1); + return rc; +} + +int +keydb_search_subject (ctrl_t ctrl, 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 (ctrl, hd, &desc, 1); + return rc; +} + + + +/* 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 (ctrl_t ctrl, 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 (); + if (!kh) + { + log_error (_("failed to allocate keyDB handle\n")); + return gpg_error (GPG_ERR_ENOMEM);; + } + + /* Set the ephemeral flag so that the search looks at all + records. */ + keydb_set_ephemeral (kh, 1); + + keydb_close_all_files (); + rc = lock_all (kh); + if (rc) + return rc; + + rc = keydb_search_fpr (ctrl, kh, fpr); + if (rc != -1) + { + keydb_release (kh); + if (!rc) + { + if (existed) + *existed = 1; + if (!ephemeral) + { + /* Remove ephemeral flags from existing certificate to "store" + it permanently. */ + rc = keydb_set_cert_flags (ctrl, cert, 1, KEYBOX_FLAG_BLOB, 0, + KEYBOX_FLAG_BLOB_EPHEMERAL, 0); + if (rc) + { + log_error ("clearing ephemeral flag failed: %s\n", + gpg_strerror (rc)); + return rc; + } + } + return 0; /* okay */ + } + log_error (_("problem looking for existing certificate: %s\n"), + gpg_strerror (rc)); + return rc; + } + + /* Reset the ephemeral flag if not requested. */ + if (!ephemeral) + keydb_set_ephemeral (kh, 0); + + 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 (ctrl_t ctrl, 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 (); + if (!kh) + { + log_error (_("failed to allocate keyDB handle\n")); + return gpg_error (GPG_ERR_ENOMEM);; + } + + if (ephemeral) + keydb_set_ephemeral (kh, 1); + + keydb_close_all_files (); + err = lock_all (kh); + if (err) + { + log_error (_("error locking keybox: %s\n"), gpg_strerror (err)); + keydb_release (kh); + return err; + } + + err = keydb_search_fpr (ctrl, 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 (); + 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 = classify_user_id (sl->d, desc+ndesc, 0); + if (rc) + log_error ("key '%s' not found: %s\n", sl->d, gpg_strerror (rc)); + else + ndesc++; + } + } + + keydb_close_all_files (); + err = lock_all (hd); + if (err) + { + log_error (_("error locking keybox: %s\n"), gpg_strerror (err)); + goto leave; + } + + while (!(rc = keydb_search (ctrl, 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 ("%s failed: %s\n", __func__, 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..226cac2 --- /dev/null +++ b/sm/keydb.h @@ -0,0 +1,79 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#ifndef GNUPG_KEYDB_H +#define GNUPG_KEYDB_H + +#include <ksba.h> + +#include "../common/userids.h" + +typedef struct keydb_handle *KEYDB_HANDLE; + +/* Flag value used with KEYBOX_FLAG_VALIDITY. */ +#define VALIDITY_REVOKED (1<<5) + + +/*-- keydb.c --*/ +gpg_error_t keydb_add_resource (ctrl_t ctrl, const char *url, + int force, int *auto_created); +void keydb_close_all_files (void); + +KEYDB_HANDLE keydb_new (void); +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); + +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); +void keydb_push_found_state (KEYDB_HANDLE hd); +void keydb_pop_found_state (KEYDB_HANDLE hd); +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_delete (KEYDB_HANDLE hd, int unlock); + +int keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved); +void keydb_rebuild_caches (void); + +gpg_error_t keydb_search_reset (KEYDB_HANDLE hd); +int keydb_search (ctrl_t ctrl, KEYDB_HANDLE hd, + KEYDB_SEARCH_DESC *desc, size_t ndesc); +int keydb_search_first (ctrl_t ctrl, KEYDB_HANDLE hd); +int keydb_search_next (ctrl_t ctrl, KEYDB_HANDLE hd); +int keydb_search_kid (ctrl_t ctrl, KEYDB_HANDLE hd, u32 *kid); +int keydb_search_fpr (ctrl_t ctrl, KEYDB_HANDLE hd, const byte *fpr); +int keydb_search_issuer (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer); +int keydb_search_issuer_sn (ctrl_t ctrl, KEYDB_HANDLE hd, + const char *issuer, const unsigned char *serial); +int keydb_search_subject (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer); + +int keydb_store_cert (ctrl_t ctrl, ksba_cert_t cert, int ephemeral, + int *existed); +gpg_error_t keydb_set_cert_flags (ctrl_t ctrl, 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..2d51aa7 --- /dev/null +++ b/sm/keylist.c @@ -0,0 +1,1686 @@ +/* keylist.c - Print certificates in various formats. + * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2008, 2009, + * 2010, 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <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 "../common/i18n.h" +#include "../common/tlv.h" +#include "../common/compliance.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 functions. */ +#define OID_FLAG_SKIP 1 +/* The extension is a simple UTF8String and should be printed. */ +#define OID_FLAG_UTF8 2 +/* The extension can be trnted as a hex string. */ +#define OID_FLAG_HEX 4 + +/* 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" }, + { "1.3.6.1.4.1.11591.2.2.1", "standaloneCertificate" }, + { "1.3.6.1.4.1.11591.2.2.2", "wellKnownPrivateKey" }, + + /* Extensions used by the Bundesnetzagentur. */ + { "1.3.6.1.4.1.8301.3.5", "validityModel" }, + + /* Yubikey extensions for attestation certificates. */ + { "1.3.6.1.4.1.41482.3.3", "yubikey-firmware-version", OID_FLAG_HEX }, + { "1.3.6.1.4.1.41482.3.7", "yubikey-serial-number", OID_FLAG_HEX }, + { "1.3.6.1.4.1.41482.3.8", "yubikey-pin-touch-policy", OID_FLAG_HEX }, + { "1.3.6.1.4.1.41482.3.9", "yubikey-formfactor", OID_FLAG_HEX }, + + { 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; + unsigned int is_encr, is_sign, is_cert; + 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; + } + + is_encr = is_sign = is_cert = 0; + + if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) + is_encr = 1; + if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) + is_sign = 1; + if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN)) + is_cert = 1; + + /* We need to returned the faked key usage to frontends so that they + * can select the right key. Note that we don't do this for the + * human readable keyUsage. */ + if ((opt.compat_flags & COMPAT_ALLOW_KA_TO_ENCR) + && (use & KSBA_KEYUSAGE_KEY_AGREEMENT)) + is_encr = 1; + + if (is_encr) + es_putc ('e', fp); + if (is_sign) + es_putc ('s', fp); + if (is_cert) + es_putc ('c', fp); + if (is_encr) + es_putc ('E', fp); + if (is_sign) + es_putc ('S', fp); + if (is_cert) + es_putc ('C', 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; +} + + +/* Print the compliance flags to field 18. ALGO is the gcrypt algo + * number. NBITS is the length of the key in bits. */ +static void +print_compliance_flags (ksba_cert_t cert, int algo, unsigned int nbits, + estream_t fp) +{ + int hashalgo; + + /* Note that we do not need to test for PK_ALGO_FLAG_RSAPSS because + * that is not a property of the key but one of the created + * signature. */ + if (gnupg_pk_is_compliant (CO_DE_VS, algo, 0, NULL, nbits, NULL)) + { + hashalgo = gcry_md_map_name (ksba_cert_get_digest_algo (cert)); + if (gnupg_digest_is_compliant (CO_DE_VS, hashalgo)) + { + es_fputs (gnupg_status_compliance_flag (CO_DE_VS), fp); + } + } +} + + +/* 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) + { + if (gpgsm_cert_has_well_known_private_key (cert)) + *truststring = 'w'; /* Well, this is dummy CA. */ + else + *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; + + if (gpgsm_cert_has_well_known_private_key (cert)) + *truststring = 'w'; /* Well, this is dummy CA. */ + else + { + 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); + + 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); + es_putc (':', fp); + /* Field 13, not used: */ + es_putc (':', fp); + /* Field 14, not used: */ + es_putc (':', fp); + if (have_secret || ctrl->with_secret) + { + char *cardsn; + + p = gpgsm_get_keygrip_hexstring (cert); + if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn) + && (cardsn || ctrl->with_secret)) + { + /* Field 15: Token serial number or secret key indicator. */ + if (cardsn) + es_fputs (cardsn, fp); + else if (ctrl->with_secret) + es_putc ('+', fp); + } + xfree (cardsn); + xfree (p); + } + es_putc (':', fp); /* End of field 15. */ + es_putc (':', fp); /* End of field 16. */ + es_putc (':', fp); /* End of field 17. */ + print_compliance_flags (cert, algo, nbits, fp); + es_putc (':', fp); /* End of field 18. */ + 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; + /* SHA256 FPR record */ + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA256); + es_fprintf (fp, "fp2:::::::::%s::::\n", fpr); + xfree (fpr); fpr = NULL; + + /* Always print the keygrip. */ + if ( (p = gpgsm_get_keygrip_hexstring (cert))) + { + es_fprintf (fp, "grp:::::::::%s:\n", p); + xfree (p); + } + + if (opt.with_key_data) + 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); +} + + +/* Print the extension described by (DER,DERLEN) in hex. */ +static void +print_hex_extn (estream_t fp, int indent, + const unsigned char *der, size_t derlen) +{ + if (indent < 0) + indent = - indent; + + es_fprintf (fp, "%*s(", indent, ""); + for (; derlen; der++, derlen--) + es_fprintf (fp, "%02X%s", *der, derlen > 1? " ":""); + es_fprintf (fp, ")\n"); +} + + +/* 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); + es_putc ('\n', fp); + es_fputs (" (dec): ", fp); + gpgsm_print_serial_decimal (fp, sexp); + es_putc ('\n', fp); + ksba_free (sexp); + + 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, GCRY_MD_SHA256); + es_fprintf (fp, " sha2_fpr: %s\n", dn?dn:"error"); + xfree (dn); + + 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", + i? "critExtn":" extn", + oid, s?" (":"", s?s:"", s?")":""); + if ((flag & OID_FLAG_UTF8)) + { + if (!cert_der) + cert_der = ksba_cert_get_image (cert, NULL); + log_assert (cert_der); + es_fprintf (fp, "\n"); + print_utf8_extn_raw (fp, -15, cert_der+off, len); + } + else if ((flag & OID_FLAG_HEX)) + { + if (!cert_der) + cert_der = ksba_cert_get_image (cert, NULL); + log_assert (cert_der); + es_fprintf (fp, "\n"); + print_hex_extn (fp, -15, cert_der+off, len); + } + else + es_fprintf (fp, " [%d octets]\n", (int)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); + es_putc ('\n', fp); + es_fputs (" (dec): ", fp); + gpgsm_print_serial_decimal (fp, sexp); + es_putc ('\n', fp); + ksba_free (sexp); + + 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); + + dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA256); + es_fprintf (fp, " sha2 fpr: %s\n", dn?dn:"error"); + xfree (dn); + + if (opt.with_keygrip) + { + dn = gpgsm_get_keygrip_hexstring (cert); + if (dn) + { + es_fprintf (fp, " keygrip: %s\n", dn); + 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 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; + int depth = 0; + + 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)) + { + es_fputs ("Certified by\n", fp); + if (++depth > 50) + { + es_fputs (_("certificate chain too long\n"), fp); + break; + } + ksba_cert_release (cert); + 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 (); + 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 = classify_user_id (sl->d, desc+ndesc, 0); + 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 (ctrl, 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 (parm->ctrl, 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, NULL, 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/minip12.c b/sm/minip12.c new file mode 100644 index 0000000..29b4898 --- /dev/null +++ b/sm/minip12.c @@ -0,0 +1,3048 @@ +/* minip12.c - A minimal pkcs-12 implementation. + * Copyright (C) 2002, 2003, 2004, 2006, 2011 Free Software Foundation, Inc. + * Copyright (C) 2014 Werner Koch + * Copyright (C) 2022 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/* References: + * RFC-7292 - PKCS #12: Personal Information Exchange Syntax v1.1 + * RFC-8351 - The PKCS #8 EncryptedPrivateKeyInfo Media Type + * RFC-5958 - Asymmetric Key Packages + * RFC-3447 - PKCS #1: RSA Cryptography Specifications Version 2.1 + * RFC-5915 - Elliptic Curve Private Key Structure + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <gcrypt.h> +#include <errno.h> + +#include <ksba.h> + +#include "../common/util.h" +#include "../common/logging.h" +#include "../common/utf8conv.h" +#include "../common/tlv.h" +#include "../common/openpgpdefs.h" /* Only for openpgp_curve_to_oid. */ +#include "minip12.h" + +#ifndef DIM +#define DIM(v) (sizeof(v)/sizeof((v)[0])) +#endif + + + +static unsigned char const oid_data[9] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01 }; +static unsigned char const oid_encryptedData[9] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x06 }; +static unsigned char const oid_pkcs_12_keyBag[11] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x01 }; +static unsigned char const oid_pkcs_12_pkcs_8ShroudedKeyBag[11] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x02 }; +static unsigned char const oid_pkcs_12_CertBag[11] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x03 }; +static unsigned char const oid_pkcs_12_CrlBag[11] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x04 }; + +static unsigned char const oid_pbeWithSHAAnd3_KeyTripleDES_CBC[10] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03 }; +static unsigned char const oid_pbeWithSHAAnd40BitRC2_CBC[10] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06 }; +static unsigned char const oid_x509Certificate_for_pkcs_12[10] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x16, 0x01 }; + +static unsigned char const oid_pkcs5PBKDF2[9] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0C }; +static unsigned char const oid_pkcs5PBES2[9] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0D }; +static unsigned char const oid_aes128_CBC[9] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x02 }; + +static unsigned char const oid_rsaEncryption[9] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }; +static unsigned char const oid_pcPublicKey[7] = { + 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }; + +static unsigned char const data_3desiter2048[30] = { + 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86, + 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03, 0x30, 0x0E, + 0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 }; +#define DATA_3DESITER2048_SALT_OFF 18 + +static unsigned char const data_rc2iter2048[30] = { + 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86, + 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06, 0x30, 0x0E, + 0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 }; +#define DATA_RC2ITER2048_SALT_OFF 18 + +static unsigned char const data_mactemplate[51] = { + 0x30, 0x31, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, + 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, + 0x14, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x08, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, + 0x02, 0x08, 0x00 }; +#define DATA_MACTEMPLATE_MAC_OFF 17 +#define DATA_MACTEMPLATE_SALT_OFF 39 + +static unsigned char const data_attrtemplate[106] = { + 0x31, 0x7c, 0x30, 0x55, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x14, 0x31, + 0x48, 0x1e, 0x46, 0x00, 0x47, 0x00, 0x6e, 0x00, + 0x75, 0x00, 0x50, 0x00, 0x47, 0x00, 0x20, 0x00, + 0x65, 0x00, 0x78, 0x00, 0x70, 0x00, 0x6f, 0x00, + 0x72, 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, + 0x20, 0x00, 0x63, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, + 0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, + 0x20, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, + 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, + 0x66, 0x30, 0x23, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x15, 0x31, 0x16, + 0x04, 0x14 }; /* Need to append SHA-1 digest. */ +#define DATA_ATTRTEMPLATE_KEYID_OFF 73 + +struct buffer_s +{ + unsigned char *buffer; + size_t length; +}; + + +struct tag_info +{ + int class; + int is_constructed; + unsigned long tag; + unsigned long length; /* length part of the TLV */ + int nhdr; + int ndef; /* It is an indefinite length */ +}; + +/* Parser communication object. */ +struct p12_parse_ctx_s +{ + /* The callback for parsed certificates and its arg. */ + void (*certcb)(void*, const unsigned char*, size_t); + void *certcbarg; + + /* The supplied parseword. */ + const char *password; + + /* Set to true if the password was wrong. */ + int badpass; + + /* Malloced name of the curve. */ + char *curve; + + /* The private key as an MPI array. */ + gcry_mpi_t *privatekey; +}; + + +static int opt_verbose; + + +void +p12_set_verbosity (int verbose) +{ + opt_verbose = verbose; +} + + +/* static void */ +/* dump_tag_info (struct tag_info *ti) */ +/* { */ +/* log_debug ("p12_parse: ti.class=%d tag=%lu len=%lu nhdr=%d %s%s\n", */ +/* ti->class, ti->tag, ti->length, ti->nhdr, */ +/* ti->is_constructed?" cons":"", */ +/* ti->ndef?" ndef":""); */ +/* } */ + + +/* Wrapper around tlv_builder_add_ptr to add an OID. When we + * eventually put the whole tlv_builder stuff into Libksba, we can add + * such a function there. Right now we don't do this to avoid a + * dependency on Libksba. Function return 1 on error. */ +static int +builder_add_oid (tlv_builder_t tb, int class, const char *oid) +{ + gpg_error_t err; + unsigned char *der; + size_t derlen; + + err = ksba_oid_from_str (oid, &der, &derlen); + if (err) + { + log_error ("%s: error converting '%s' to DER: %s\n", + __func__, oid, gpg_strerror (err)); + return 1; + } + + tlv_builder_add_val (tb, class, TAG_OBJECT_ID, der, derlen); + ksba_free (der); + return 0; +} + + +/* Wrapper around tlv_builder_add_ptr to add an MPI. TAG may either + * be OCTET_STRING or BIT_STRING. When we eventually put the whole + * tlv_builder stuff into Libksba, we can add such a function there. + * Right now we don't do this to avoid a dependency on Libksba. + * Function return 1 on error. STRIP is a hack to remove the first + * octet from the value. */ +static int +builder_add_mpi (tlv_builder_t tb, int class, int tag, gcry_mpi_t mpi, + int strip) +{ + int returncode; + gpg_error_t err; + const unsigned char *s; + unsigned char *freethis = NULL; + unsigned char *freethis2 = NULL; + unsigned int nbits; + size_t n; + + if (gcry_mpi_get_flag (mpi, GCRYMPI_FLAG_OPAQUE)) + { + s = gcry_mpi_get_opaque (mpi, &nbits); + n = (nbits+7)/8; + } + else + { + err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &freethis, &n, mpi); + if (err) + { + log_error ("%s: error converting MPI: %s\n", + __func__, gpg_strerror (err)); + returncode = 1; + goto leave; + } + s = freethis; + } + + if (tag == TAG_BIT_STRING) + { + freethis2 = xtrymalloc_secure (n + 1); + if (!freethis2) + { + err = gpg_error_from_syserror (); + log_error ("%s: error converting MPI: %s\n", + __func__, gpg_strerror (err)); + returncode = 1; + goto leave; + } + freethis2[0] = 0; + memcpy (freethis2+1, s, n); + s = freethis2; + n++; + } + + strip = !!strip; + if (strip && n < 2) + strip = 0; + + tlv_builder_add_val (tb, class, tag, s+strip, n-strip); + returncode = 0; + + leave: + xfree (freethis); + xfree (freethis2); + return returncode; +} + + +/* Parse the buffer at the address BUFFER which is of SIZE and return + the tag and the length part from the TLV triplet. Update BUFFER + and SIZE on success. Checks that the encoded length does not + exhaust the length of the provided buffer. */ +static int +parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti) +{ + int c; + unsigned long tag; + const unsigned char *buf = *buffer; + size_t length = *size; + + ti->length = 0; + ti->ndef = 0; + ti->nhdr = 0; + + /* Get the tag */ + if (!length) + return -1; /* premature eof */ + c = *buf++; length--; + ti->nhdr++; + + ti->class = (c & 0xc0) >> 6; + ti->is_constructed = !!(c & 0x20); + tag = c & 0x1f; + + if (tag == 0x1f) + { + tag = 0; + do + { + tag <<= 7; + if (!length) + return -1; /* premature eof */ + c = *buf++; length--; + ti->nhdr++; + tag |= c & 0x7f; + } + while (c & 0x80); + } + ti->tag = tag; + + /* Get the length */ + if (!length) + return -1; /* prematureeof */ + c = *buf++; length--; + ti->nhdr++; + + if ( !(c & 0x80) ) + ti->length = c; + else if (c == 0x80) + ti->ndef = 1; + else if (c == 0xff) + return -1; /* forbidden length value */ + else + { + unsigned long len = 0; + int count = c & 0x7f; + + for (; count; count--) + { + len <<= 8; + if (!length) + return -1; /* premature_eof */ + c = *buf++; length--; + ti->nhdr++; + len |= c & 0xff; + } + ti->length = len; + } + + if (ti->class == CLASS_UNIVERSAL && !ti->tag) + ti->length = 0; + + if (ti->length > length) + return -1; /* data larger than buffer. */ + + *buffer = buf; + *size = length; + return 0; +} + + +/* Given an ASN.1 chunk of a structure like: + + 24 NDEF: OCTET STRING -- This is not passed to us + 04 1: OCTET STRING -- INPUT point s to here + : 30 + 04 1: OCTET STRING + : 80 + [...] + 04 2: OCTET STRING + : 00 00 + : } -- This denotes a Null tag and are the last + -- two bytes in INPUT. + + Create a new buffer with the content of that octet string. INPUT + is the original buffer with a length as stored at LENGTH. Returns + NULL on error or a new malloced buffer with the length of this new + buffer stored at LENGTH and the number of bytes parsed from input + are added to the value stored at INPUT_CONSUMED. INPUT_CONSUMED is + allowed to be passed as NULL if the caller is not interested in + this value. */ +static unsigned char * +cram_octet_string (const unsigned char *input, size_t *length, + size_t *input_consumed) +{ + const unsigned char *s = input; + size_t n = *length; + unsigned char *output, *d; + struct tag_info ti; + + /* Allocate output buf. We know that it won't be longer than the + input buffer. */ + d = output = gcry_malloc (n); + if (!output) + goto bailout; + + while (n) + { + if (parse_tag (&s, &n, &ti)) + goto bailout; + if (ti.class == CLASS_UNIVERSAL && ti.tag == TAG_OCTET_STRING + && !ti.ndef && !ti.is_constructed) + { + memcpy (d, s, ti.length); + s += ti.length; + d += ti.length; + n -= ti.length; + } + else if (ti.class == CLASS_UNIVERSAL && !ti.tag && !ti.is_constructed) + break; /* Ready */ + else + goto bailout; + } + + + *length = d - output; + if (input_consumed) + *input_consumed += s - input; + return output; + + bailout: + if (input_consumed) + *input_consumed += s - input; + gcry_free (output); + return NULL; +} + + + +static int +string_to_key (int id, char *salt, size_t saltlen, int iter, const char *pw, + int req_keylen, unsigned char *keybuf) +{ + int rc, i, j; + gcry_md_hd_t md; + gcry_mpi_t num_b1 = NULL; + int pwlen; + unsigned char hash[20], buf_b[64], buf_i[128], *p; + size_t cur_keylen; + size_t n; + + cur_keylen = 0; + pwlen = strlen (pw); + if (pwlen > 63/2) + { + log_error ("password too long\n"); + return -1; + } + + if (saltlen < 8) + { + log_error ("salt too short\n"); + return -1; + } + + /* Store salt and password in BUF_I */ + p = buf_i; + for(i=0; i < 64; i++) + *p++ = salt [i%saltlen]; + for(i=j=0; i < 64; i += 2) + { + *p++ = 0; + *p++ = pw[j]; + if (++j > pwlen) /* Note, that we include the trailing zero */ + j = 0; + } + + for (;;) + { + rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); + if (rc) + { + log_error ( "gcry_md_open failed: %s\n", gpg_strerror (rc)); + return rc; + } + for(i=0; i < 64; i++) + gcry_md_putc (md, id); + gcry_md_write (md, buf_i, 128); + memcpy (hash, gcry_md_read (md, 0), 20); + gcry_md_close (md); + for (i=1; i < iter; i++) + gcry_md_hash_buffer (GCRY_MD_SHA1, hash, hash, 20); + + for (i=0; i < 20 && cur_keylen < req_keylen; i++) + keybuf[cur_keylen++] = hash[i]; + if (cur_keylen == req_keylen) + { + gcry_mpi_release (num_b1); + return 0; /* ready */ + } + + /* need more bytes. */ + for(i=0; i < 64; i++) + buf_b[i] = hash[i % 20]; + rc = gcry_mpi_scan (&num_b1, GCRYMPI_FMT_USG, buf_b, 64, &n); + if (rc) + { + log_error ( "gcry_mpi_scan failed: %s\n", gpg_strerror (rc)); + return -1; + } + gcry_mpi_add_ui (num_b1, num_b1, 1); + for (i=0; i < 128; i += 64) + { + gcry_mpi_t num_ij; + + rc = gcry_mpi_scan (&num_ij, GCRYMPI_FMT_USG, buf_i + i, 64, &n); + if (rc) + { + log_error ( "gcry_mpi_scan failed: %s\n", + gpg_strerror (rc)); + return -1; + } + gcry_mpi_add (num_ij, num_ij, num_b1); + gcry_mpi_clear_highbit (num_ij, 64*8); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, buf_i + i, 64, &n, num_ij); + if (rc) + { + log_error ( "gcry_mpi_print failed: %s\n", + gpg_strerror (rc)); + return -1; + } + gcry_mpi_release (num_ij); + } + } +} + + +static int +set_key_iv (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter, + const char *pw, int keybytes) +{ + unsigned char keybuf[24]; + int rc; + + log_assert (keybytes == 5 || keybytes == 24); + if (string_to_key (1, salt, saltlen, iter, pw, keybytes, keybuf)) + return -1; + rc = gcry_cipher_setkey (chd, keybuf, keybytes); + if (rc) + { + log_error ( "gcry_cipher_setkey failed: %s\n", gpg_strerror (rc)); + return -1; + } + + if (string_to_key (2, salt, saltlen, iter, pw, 8, keybuf)) + return -1; + rc = gcry_cipher_setiv (chd, keybuf, 8); + if (rc) + { + log_error ("gcry_cipher_setiv failed: %s\n", gpg_strerror (rc)); + return -1; + } + return 0; +} + + +static int +set_key_iv_pbes2 (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter, + const void *iv, size_t ivlen, const char *pw, int algo) +{ + unsigned char *keybuf; + size_t keylen; + int rc; + + keylen = gcry_cipher_get_algo_keylen (algo); + if (!keylen) + return -1; + keybuf = gcry_malloc_secure (keylen); + if (!keybuf) + return -1; + + rc = gcry_kdf_derive (pw, strlen (pw), + GCRY_KDF_PBKDF2, GCRY_MD_SHA1, + salt, saltlen, iter, keylen, keybuf); + if (rc) + { + log_error ("gcry_kdf_derive failed: %s\n", gpg_strerror (rc)); + gcry_free (keybuf); + return -1; + } + + rc = gcry_cipher_setkey (chd, keybuf, keylen); + gcry_free (keybuf); + if (rc) + { + log_error ("gcry_cipher_setkey failed: %s\n", gpg_strerror (rc)); + return -1; + } + + + rc = gcry_cipher_setiv (chd, iv, ivlen); + if (rc) + { + log_error ("gcry_cipher_setiv failed: %s\n", gpg_strerror (rc)); + return -1; + } + return 0; +} + + +static void +crypt_block (unsigned char *buffer, size_t length, char *salt, size_t saltlen, + int iter, const void *iv, size_t ivlen, + const char *pw, int cipher_algo, int encrypt) +{ + gcry_cipher_hd_t chd; + int rc; + + rc = gcry_cipher_open (&chd, cipher_algo, GCRY_CIPHER_MODE_CBC, 0); + if (rc) + { + log_error ( "gcry_cipher_open failed: %s\n", gpg_strerror(rc)); + wipememory (buffer, length); + return; + } + + if (cipher_algo == GCRY_CIPHER_AES128 + ? set_key_iv_pbes2 (chd, salt, saltlen, iter, iv, ivlen, pw, cipher_algo) + : set_key_iv (chd, salt, saltlen, iter, pw, + cipher_algo == GCRY_CIPHER_RFC2268_40? 5:24)) + { + wipememory (buffer, length); + goto leave; + } + + rc = encrypt? gcry_cipher_encrypt (chd, buffer, length, NULL, 0) + : gcry_cipher_decrypt (chd, buffer, length, NULL, 0); + + if (rc) + { + wipememory (buffer, length); + log_error ("%scrytion failed (%zu bytes): %s\n", + encrypt?"en":"de", length, gpg_strerror (rc)); + goto leave; + } + + leave: + gcry_cipher_close (chd); +} + + +/* Decrypt a block of data and try several encodings of the key. + CIPHERTEXT is the encrypted data of size LENGTH bytes; PLAINTEXT is + a buffer of the same size to receive the decryption result. SALT, + SALTLEN, ITER and PW are the information required for decryption + and CIPHER_ALGO is the algorithm id to use. CHECK_FNC is a + function called with the plaintext and used to check whether the + decryption succeeded; i.e. that a correct passphrase has been + given. That function shall return true if the decryption has likely + succeeded. */ +static void +decrypt_block (const void *ciphertext, unsigned char *plaintext, size_t length, + char *salt, size_t saltlen, + int iter, const void *iv, size_t ivlen, + const char *pw, int cipher_algo, + int (*check_fnc) (const void *, size_t)) +{ + static const char * const charsets[] = { + "", /* No conversion - use the UTF-8 passphrase direct. */ + "ISO-8859-1", + "ISO-8859-15", + "ISO-8859-2", + "ISO-8859-3", + "ISO-8859-4", + "ISO-8859-5", + "ISO-8859-6", + "ISO-8859-7", + "ISO-8859-8", + "ISO-8859-9", + "KOI8-R", + "IBM437", + "IBM850", + "EUC-JP", + "BIG5", + NULL + }; + int charsetidx = 0; + char *convertedpw = NULL; /* Malloced and converted password or NULL. */ + size_t convertedpwsize = 0; /* Allocated length. */ + + for (charsetidx=0; charsets[charsetidx]; charsetidx++) + { + if (*charsets[charsetidx]) + { + jnlib_iconv_t cd; + const char *inptr; + char *outptr; + size_t inbytes, outbytes; + + if (!convertedpw) + { + /* We assume one byte encodings. Thus we can allocate + the buffer of the same size as the original + passphrase; the result will actually be shorter + then. */ + convertedpwsize = strlen (pw) + 1; + convertedpw = gcry_malloc_secure (convertedpwsize); + if (!convertedpw) + { + log_info ("out of secure memory while" + " converting passphrase\n"); + break; /* Give up. */ + } + } + + cd = jnlib_iconv_open (charsets[charsetidx], "utf-8"); + if (cd == (jnlib_iconv_t)(-1)) + continue; + + inptr = pw; + inbytes = strlen (pw); + outptr = convertedpw; + outbytes = convertedpwsize - 1; + if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes, + &outptr, &outbytes) == (size_t)-1) + { + jnlib_iconv_close (cd); + continue; + } + *outptr = 0; + jnlib_iconv_close (cd); + log_info ("decryption failed; trying charset '%s'\n", + charsets[charsetidx]); + } + memcpy (plaintext, ciphertext, length); + crypt_block (plaintext, length, salt, saltlen, iter, iv, ivlen, + convertedpw? convertedpw:pw, cipher_algo, 0); + if (check_fnc (plaintext, length)) + break; /* Decryption succeeded. */ + } + gcry_free (convertedpw); +} + + +/* Return true if the decryption of an bag_encrypted_data object has + likely succeeded. */ +static int +bag_decrypted_data_p (const void *plaintext, size_t length) +{ + struct tag_info ti; + const unsigned char *p = plaintext; + size_t n = length; + + /* { */ + /* # warning debug code is enabled */ + /* FILE *fp = fopen ("tmp-minip12-plain-data.der", "wb"); */ + /* if (!fp || fwrite (p, n, 1, fp) != 1) */ + /* exit (2); */ + /* fclose (fp); */ + /* } */ + + if (parse_tag (&p, &n, &ti)) + return 0; + if (ti.class || ti.tag != TAG_SEQUENCE) + return 0; + if (parse_tag (&p, &n, &ti)) + return 0; + + return 1; +} + + +static int +parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, + const unsigned char *buffer, size_t length, + int startoffset, size_t *r_consumed) +{ + struct tag_info ti; + const unsigned char *p = buffer; + const unsigned char *p_start = buffer; + size_t n = length; + const char *where; + char salt[20]; + size_t saltlen; + char iv[16]; + unsigned int iter; + unsigned char *plain = NULL; + unsigned char *cram_buffer = NULL; + size_t consumed = 0; /* Number of bytes consumed from the original buffer. */ + int is_3des = 0; + int is_pbes2 = 0; + int keyelem_count; + + where = "start"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CLASS_CONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_SEQUENCE) + goto bailout; + + where = "bag.encryptedData.version"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 0) + goto bailout; + p++; n--; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_SEQUENCE) + goto bailout; + + where = "bag.encryptedData.data"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data) + || memcmp (p, oid_data, DIM(oid_data))) + goto bailout; + p += DIM(oid_data); + n -= DIM(oid_data); + + where = "bag.encryptedData.keyinfo"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!ti.class && ti.tag == TAG_OBJECT_ID + && ti.length == DIM(oid_pbeWithSHAAnd40BitRC2_CBC) + && !memcmp (p, oid_pbeWithSHAAnd40BitRC2_CBC, + DIM(oid_pbeWithSHAAnd40BitRC2_CBC))) + { + p += DIM(oid_pbeWithSHAAnd40BitRC2_CBC); + n -= DIM(oid_pbeWithSHAAnd40BitRC2_CBC); + } + else if (!ti.class && ti.tag == TAG_OBJECT_ID + && ti.length == DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC) + && !memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC, + DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC))) + { + p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); + n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); + is_3des = 1; + } + else if (!ti.class && ti.tag == TAG_OBJECT_ID + && ti.length == DIM(oid_pkcs5PBES2) + && !memcmp (p, oid_pkcs5PBES2, ti.length)) + { + p += ti.length; + n -= ti.length; + is_pbes2 = 1; + } + else + goto bailout; + + if (is_pbes2) + { + where = "pkcs5PBES2-params"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!(!ti.class && ti.tag == TAG_OBJECT_ID + && ti.length == DIM(oid_pkcs5PBKDF2) + && !memcmp (p, oid_pkcs5PBKDF2, ti.length))) + goto bailout; /* Not PBKDF2. */ + p += ti.length; + n -= ti.length; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!(!ti.class && ti.tag == TAG_OCTET_STRING + && ti.length >= 8 && ti.length < sizeof salt)) + goto bailout; /* No salt or unsupported length. */ + saltlen = ti.length; + memcpy (salt, p, saltlen); + p += saltlen; + n -= saltlen; + + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!(!ti.class && ti.tag == TAG_INTEGER && ti.length)) + goto bailout; /* No valid iteration count. */ + for (iter=0; ti.length; ti.length--) + { + iter <<= 8; + iter |= (*p++) & 0xff; + n--; + } + /* Note: We don't support the optional parameters but assume + that the algorithmIdentifier follows. */ + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!(!ti.class && ti.tag == TAG_OBJECT_ID + && ti.length == DIM(oid_aes128_CBC) + && !memcmp (p, oid_aes128_CBC, ti.length))) + goto bailout; /* Not AES-128. */ + p += ti.length; + n -= ti.length; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!(!ti.class && ti.tag == TAG_OCTET_STRING && ti.length == sizeof iv)) + goto bailout; /* Bad IV. */ + memcpy (iv, p, sizeof iv); + p += sizeof iv; + n -= sizeof iv; + } + else + { + where = "rc2or3des-params"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING + || ti.length < 8 || ti.length > 20 ) + goto bailout; + saltlen = ti.length; + memcpy (salt, p, saltlen); + p += saltlen; + n -= saltlen; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_INTEGER || !ti.length ) + goto bailout; + for (iter=0; ti.length; ti.length--) + { + iter <<= 8; + iter |= (*p++) & 0xff; + n--; + } + } + + where = "rc2or3desoraes-ciphertext"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + + consumed = p - p_start; + if (ti.class == CLASS_CONTEXT && ti.tag == 0 && ti.is_constructed && ti.ndef) + { + /* Mozilla exported certs now come with single byte chunks of + octet strings. (Mozilla Firefox 1.0.4). Arghh. */ + where = "cram-rc2or3des-ciphertext"; + cram_buffer = cram_octet_string ( p, &n, &consumed); + if (!cram_buffer) + goto bailout; + p = p_start = cram_buffer; + if (r_consumed) + *r_consumed = consumed; + r_consumed = NULL; /* Donot update that value on return. */ + ti.length = n; + } + else if (ti.class == CLASS_CONTEXT && ti.tag == 0 && ti.is_constructed) + { + where = "octets-rc2or3des-ciphertext"; + n = ti.length; + cram_buffer = cram_octet_string ( p, &n, &consumed); + if (!cram_buffer) + goto bailout; + p = p_start = cram_buffer; + if (r_consumed) + *r_consumed = consumed; + r_consumed = NULL; /* Do not update that value on return. */ + ti.length = n; + } + else if (ti.class == CLASS_CONTEXT && ti.tag == 0 && ti.length ) + ; + else + goto bailout; + + if (opt_verbose) + log_info ("%lu bytes of %s encrypted text\n",ti.length, + is_pbes2?"AES128":is_3des?"3DES":"RC2"); + + plain = gcry_malloc_secure (ti.length); + if (!plain) + { + log_error ("error allocating decryption buffer\n"); + goto bailout; + } + decrypt_block (p, plain, ti.length, salt, saltlen, iter, + iv, is_pbes2?16:0, ctx->password, + is_pbes2 ? GCRY_CIPHER_AES128 : + is_3des ? GCRY_CIPHER_3DES : GCRY_CIPHER_RFC2268_40, + bag_decrypted_data_p); + n = ti.length; + startoffset = 0; + p_start = p = plain; + + where = "outer.outer.seq"; + if (parse_tag (&p, &n, &ti)) + { + ctx->badpass = 1; + goto bailout; + } + if (ti.class || ti.tag != TAG_SEQUENCE) + { + ctx->badpass = 1; + goto bailout; + } + + if (parse_tag (&p, &n, &ti)) + { + ctx->badpass = 1; + goto bailout; + } + + /* Loop over all certificates inside the bag. */ + while (n) + { + int iscrlbag = 0; + int iskeybag = 0; + + where = "certbag.nextcert"; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + + where = "certbag.objectidentifier"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OBJECT_ID) + goto bailout; + if ( ti.length == DIM(oid_pkcs_12_CertBag) + && !memcmp (p, oid_pkcs_12_CertBag, DIM(oid_pkcs_12_CertBag))) + { + p += DIM(oid_pkcs_12_CertBag); + n -= DIM(oid_pkcs_12_CertBag); + } + else if ( ti.length == DIM(oid_pkcs_12_CrlBag) + && !memcmp (p, oid_pkcs_12_CrlBag, DIM(oid_pkcs_12_CrlBag))) + { + p += DIM(oid_pkcs_12_CrlBag); + n -= DIM(oid_pkcs_12_CrlBag); + iscrlbag = 1; + } + else if ( ti.length == DIM(oid_pkcs_12_keyBag) + && !memcmp (p, oid_pkcs_12_keyBag, DIM(oid_pkcs_12_keyBag))) + { + /* The TrustedMIME plugin for MS Outlook started to create + files with just one outer 3DES encrypted container and + inside the certificates as well as the key. */ + p += DIM(oid_pkcs_12_keyBag); + n -= DIM(oid_pkcs_12_keyBag); + iskeybag = 1; + } + else + goto bailout; + + where = "certbag.before.certheader"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CLASS_CONTEXT || ti.tag) + goto bailout; + if (iscrlbag) + { + log_info ("skipping unsupported crlBag\n"); + p += ti.length; + n -= ti.length; + } + else if (iskeybag && ctx->privatekey) + { + log_info ("one keyBag already processed; skipping this one\n"); + p += ti.length; + n -= ti.length; + } + else if (iskeybag) + { + int len; + + if (opt_verbose) + log_info ("processing simple keyBag\n"); + + /* Fixme: This code is duplicated from parse_bag_data. */ + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER + || ti.length != 1 || *p) + goto bailout; + p++; n--; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + len = ti.length; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (len < ti.nhdr) + goto bailout; + len -= ti.nhdr; + if (ti.class || ti.tag != TAG_OBJECT_ID + || ti.length != DIM(oid_rsaEncryption) + || memcmp (p, oid_rsaEncryption, + DIM(oid_rsaEncryption))) + goto bailout; + p += DIM (oid_rsaEncryption); + n -= DIM (oid_rsaEncryption); + if (len < ti.length) + goto bailout; + len -= ti.length; + if (n < len) + goto bailout; + p += len; + n -= len; + if ( parse_tag (&p, &n, &ti) + || ti.class || ti.tag != TAG_OCTET_STRING) + goto bailout; + if ( parse_tag (&p, &n, &ti) + || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + len = ti.length; + + log_assert (!ctx->privatekey); + ctx->privatekey = gcry_calloc (10, sizeof *ctx->privatekey); + if (!ctx->privatekey) + { + log_error ("error allocating private key element array\n"); + goto bailout; + } + keyelem_count = 0; + + where = "reading.keybag.key-parameters"; + for (keyelem_count = 0; len && keyelem_count < 9;) + { + if ( parse_tag (&p, &n, &ti) + || ti.class || ti.tag != TAG_INTEGER) + goto bailout; + if (len < ti.nhdr) + goto bailout; + len -= ti.nhdr; + if (len < ti.length) + goto bailout; + len -= ti.length; + if (!keyelem_count && ti.length == 1 && !*p) + ; /* ignore the very first one if it is a 0 */ + else + { + int rc; + + rc = gcry_mpi_scan (ctx->privatekey+keyelem_count, + GCRYMPI_FMT_USG, p, + ti.length, NULL); + if (rc) + { + log_error ("error parsing key parameter: %s\n", + gpg_strerror (rc)); + goto bailout; + } + keyelem_count++; + } + p += ti.length; + n -= ti.length; + } + if (len) + goto bailout; + } + else + { + if (opt_verbose) + log_info ("processing certBag\n"); + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OBJECT_ID + || ti.length != DIM(oid_x509Certificate_for_pkcs_12) + || memcmp (p, oid_x509Certificate_for_pkcs_12, + DIM(oid_x509Certificate_for_pkcs_12))) + goto bailout; + p += DIM(oid_x509Certificate_for_pkcs_12); + n -= DIM(oid_x509Certificate_for_pkcs_12); + + where = "certbag.before.octetstring"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CLASS_CONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING || ti.ndef) + goto bailout; + + /* Return the certificate. */ + if (ctx->certcb) + ctx->certcb (ctx->certcbarg, p, ti.length); + + p += ti.length; + n -= ti.length; + } + + /* Ugly hack to cope with the padding: Forget about the rest if + that is less or equal to the cipher's block length. We can + reasonable assume that all valid data will be longer than + just one block. */ + if (n <= (is_pbes2? 16:8)) + n = 0; + + /* Skip the optional SET with the pkcs12 cert attributes. */ + if (n) + { + where = "bag.attributes"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!ti.class && ti.tag == TAG_SEQUENCE) + ; /* No attributes. */ + else if (!ti.class && ti.tag == TAG_SET && !ti.ndef) + { /* The optional SET. */ + p += ti.length; + n -= ti.length; + if (n <= (is_pbes2?16:8)) + n = 0; + if (n && parse_tag (&p, &n, &ti)) + goto bailout; + } + else + goto bailout; + } + } + + if (r_consumed) + *r_consumed = consumed; + gcry_free (plain); + gcry_free (cram_buffer); + return 0; + + bailout: + if (r_consumed) + *r_consumed = consumed; + gcry_free (plain); + gcry_free (cram_buffer); + log_error ("encryptedData error at \"%s\", offset %u\n", + where, (unsigned int)((p - p_start)+startoffset)); + if (ctx->badpass) + { + /* Note, that the following string might be used by other programs + to check for a bad passphrase; it should therefore not be + translated or changed. */ + log_error ("possibly bad passphrase given\n"); + } + return -1; +} + + +/* Return true if the decryption of a bag_data object has likely + succeeded. */ +static int +bag_data_p (const void *plaintext, size_t length) +{ + struct tag_info ti; + const unsigned char *p = plaintext; + size_t n = length; + +/* { */ +/* # warning debug code is enabled */ +/* FILE *fp = fopen ("tmp-minip12-plain-key.der", "wb"); */ +/* if (!fp || fwrite (p, n, 1, fp) != 1) */ +/* exit (2); */ +/* fclose (fp); */ +/* } */ + + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + return 0; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER + || ti.length != 1 || *p) + return 0; + + return 1; +} + + +static gpg_error_t +parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, + const unsigned char *buffer, size_t length, + int startoffset, + size_t *r_consumed) +{ + gpg_error_t err = 0; + struct tag_info ti; + const unsigned char *p = buffer; + const unsigned char *p_start = buffer; + size_t n = length; + const char *where; + char salt[20]; + size_t saltlen; + char iv[16]; + unsigned int iter; + int len; + unsigned char *plain = NULL; + unsigned char *cram_buffer = NULL; + size_t consumed = 0; /* Number of bytes consumed from the original buffer. */ + int is_pbes2 = 0; + int keyelem_count = 0; + + where = "shrouded_key_bag"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CLASS_CONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class == 0 && ti.tag == TAG_OBJECT_ID + && ti.length == DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC) + && !memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC, + DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC))) + { + p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); + n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); + } + else if (ti.class == 0 && ti.tag == TAG_OBJECT_ID + && ti.length == DIM(oid_pkcs5PBES2) + && !memcmp (p, oid_pkcs5PBES2, DIM(oid_pkcs5PBES2))) + { + p += DIM(oid_pkcs5PBES2); + n -= DIM(oid_pkcs5PBES2); + is_pbes2 = 1; + } + else + goto bailout; + + if (is_pbes2) + { + where = "shrouded_key_bag.pkcs5PBES2-params"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!(!ti.class && ti.tag == TAG_OBJECT_ID + && ti.length == DIM(oid_pkcs5PBKDF2) + && !memcmp (p, oid_pkcs5PBKDF2, ti.length))) + goto bailout; /* Not PBKDF2. */ + p += ti.length; + n -= ti.length; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!(!ti.class && ti.tag == TAG_OCTET_STRING + && ti.length >= 8 && ti.length < sizeof salt)) + goto bailout; /* No salt or unsupported length. */ + saltlen = ti.length; + memcpy (salt, p, saltlen); + p += saltlen; + n -= saltlen; + + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!(!ti.class && ti.tag == TAG_INTEGER && ti.length)) + goto bailout; /* No valid iteration count. */ + for (iter=0; ti.length; ti.length--) + { + iter <<= 8; + iter |= (*p++) & 0xff; + n--; + } + /* Note: We don't support the optional parameters but assume + that the algorithmIdentifier follows. */ + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!(!ti.class && ti.tag == TAG_OBJECT_ID + && ti.length == DIM(oid_aes128_CBC) + && !memcmp (p, oid_aes128_CBC, ti.length))) + goto bailout; /* Not AES-128. */ + p += ti.length; + n -= ti.length; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!(!ti.class && ti.tag == TAG_OCTET_STRING && ti.length == sizeof iv)) + goto bailout; /* Bad IV. */ + memcpy (iv, p, sizeof iv); + p += sizeof iv; + n -= sizeof iv; + } + else + { + where = "shrouded_key_bag.3des-params"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING + || ti.length < 8 || ti.length > 20) + goto bailout; + saltlen = ti.length; + memcpy (salt, p, saltlen); + p += saltlen; + n -= saltlen; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_INTEGER || !ti.length ) + goto bailout; + for (iter=0; ti.length; ti.length--) + { + iter <<= 8; + iter |= (*p++) & 0xff; + n--; + } + } + + where = "shrouded_key_bag.3desoraes-ciphertext"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING || !ti.length ) + goto bailout; + + if (opt_verbose) + log_info ("%lu bytes of %s encrypted text\n", + ti.length, is_pbes2? "AES128":"3DES"); + + plain = gcry_malloc_secure (ti.length); + if (!plain) + { + log_error ("error allocating decryption buffer\n"); + goto bailout; + } + consumed += p - p_start + ti.length; + decrypt_block (p, plain, ti.length, salt, saltlen, iter, + iv, is_pbes2? 16:0, ctx->password, + is_pbes2? GCRY_CIPHER_AES128 : GCRY_CIPHER_3DES, + bag_data_p); + n = ti.length; + startoffset = 0; + p_start = p = plain; + + where = "shrouded_key_bag.decrypted-text"; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER + || ti.length != 1 || *p) + goto bailout; + p++; n--; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + len = ti.length; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (len < ti.nhdr) + goto bailout; + len -= ti.nhdr; + if (ti.class || ti.tag != TAG_OBJECT_ID) + goto bailout; + /* gpgrt_log_printhex (p, ti.length, "OID:"); */ + if (ti.length == DIM(oid_rsaEncryption) + && !memcmp (p, oid_rsaEncryption, DIM(oid_rsaEncryption))) + { + p += DIM (oid_rsaEncryption); + n -= DIM (oid_rsaEncryption); + } + else if (ti.length == DIM(oid_pcPublicKey) + && !memcmp (p, oid_pcPublicKey, DIM(oid_pcPublicKey))) + { + /* See RFC-5915 for the format. */ + p += DIM (oid_pcPublicKey); + n -= DIM (oid_pcPublicKey); + if (len < ti.length) + goto bailout; + len -= ti.length; + if (n < len) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + /* gpgrt_log_debug ("ti=%d/%lu len=%lu\n",ti.class,ti.tag,ti.length); */ + if (len < ti.nhdr) + goto bailout; + len -= ti.nhdr; + if (ti.class || ti.tag != TAG_OBJECT_ID) + goto bailout; + ksba_free (ctx->curve); + ctx->curve = ksba_oid_to_str (p, ti.length); + if (!ctx->curve) + goto bailout; + /* log_debug ("OID of curve is: %s\n", curve); */ + p += ti.length; + n -= ti.length; + } + else + goto bailout; + if (len < ti.length) + goto bailout; + len -= ti.length; + if (n < len) + goto bailout; + p += len; + n -= len; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_OCTET_STRING) + goto bailout; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + len = ti.length; + + if (ctx->privatekey) + { + log_error ("a key has already been received\n"); + goto bailout; + } + ctx->privatekey = gcry_calloc (10, sizeof *ctx->privatekey); + if (!ctx->privatekey) + { + + log_error ("error allocating privatekey element array\n"); + goto bailout; + } + keyelem_count = 0; + + where = "shrouded_key_bag.reading.key-parameters"; + if (ctx->curve) /* ECC case. */ + { + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER) + goto bailout; + if (len < ti.nhdr) + goto bailout; + len -= ti.nhdr; + if (len < ti.length) + goto bailout; + len -= ti.length; + if (ti.length != 1 && *p != 1) + { + log_error ("error parsing private ecPublicKey parameter: %s\n", + "bad version"); + goto bailout; + } + p += ti.length; + n -= ti.length; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_OCTET_STRING) + goto bailout; + if (len < ti.nhdr) + goto bailout; + len -= ti.nhdr; + if (len < ti.length) + goto bailout; + len -= ti.length; + /* log_printhex (p, ti.length, "ecc q="); */ + err = gcry_mpi_scan (ctx->privatekey, GCRYMPI_FMT_USG, + p, ti.length, NULL); + if (err) + { + log_error ("error parsing key parameter: %s\n", gpg_strerror (err)); + goto bailout; + } + p += ti.length; + n -= ti.length; + + len = 0; /* Skip the rest. */ + } + else /* RSA case */ + { + for (keyelem_count=0; len && keyelem_count < 9;) + { + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER) + goto bailout; + if (len < ti.nhdr) + goto bailout; + len -= ti.nhdr; + if (len < ti.length) + goto bailout; + len -= ti.length; + if (!keyelem_count && ti.length == 1 && !*p) + ; /* ignore the very first one if it is a 0 */ + else + { + err = gcry_mpi_scan (ctx->privatekey+keyelem_count, + GCRYMPI_FMT_USG, p, ti.length, NULL); + if (err) + { + log_error ("error parsing key parameter: %s\n", + gpg_strerror (err)); + goto bailout; + } + keyelem_count++; + } + p += ti.length; + n -= ti.length; + } + } + if (len) + goto bailout; + + goto leave; + + bailout: + gcry_free (plain); + log_error ("data error at \"%s\", offset %zu\n", + where, (size_t)((p - p_start) + startoffset)); + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + + leave: + gcry_free (cram_buffer); + if (r_consumed) + *r_consumed = consumed; + return err; +} + + +static gpg_error_t +parse_cert_bag (struct p12_parse_ctx_s *ctx, + const unsigned char *buffer, size_t length, + int startoffset, + size_t *r_consumed) +{ + gpg_error_t err = 0; + struct tag_info ti; + const unsigned char *p = buffer; + const unsigned char *p_start = buffer; + size_t n = length; + const char *where; + size_t consumed = 0; /* Number of bytes consumed from the original buffer. */ + + if (opt_verbose) + log_info ("processing certBag\n"); + + /* Expect: + * [0] + * SEQUENCE + * OBJECT IDENTIFIER pkcs-12-certBag + */ + where = "certbag.before.certheader"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CLASS_CONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OBJECT_ID + || ti.length != DIM(oid_x509Certificate_for_pkcs_12) + || memcmp (p, oid_x509Certificate_for_pkcs_12, + DIM(oid_x509Certificate_for_pkcs_12))) + goto bailout; + p += DIM(oid_x509Certificate_for_pkcs_12); + n -= DIM(oid_x509Certificate_for_pkcs_12); + + /* Expect: + * [0] + * OCTET STRING encapsulates -- the certificates + */ + where = "certbag.before.octetstring"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CLASS_CONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING || ti.ndef) + goto bailout; + + /* Return the certificate from the octet string. */ + if (ctx->certcb) + ctx->certcb (ctx->certcbarg, p, ti.length); + + p += ti.length; + n -= ti.length; + + if (!n) + goto leave; /* ready. */ + + /* Expect: + * SET + * SEQUENCE -- we actually ignore this. + */ + where = "certbag.attribute_set"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!ti.class && ti.tag == TAG_SET && !ti.ndef) + { /* Comsume the optional SET. */ + p += ti.length; + n -= ti.length; + if (parse_tag (&p, &n, &ti)) + goto bailout; + } + + goto leave; + + bailout: + log_error ( "data error at \"%s\", offset %u\n", + where, (unsigned int)((p - p_start) + startoffset)); + err = gpg_error (GPG_ERR_GENERAL); + + leave: + if (r_consumed) + *r_consumed = consumed; + return err; +} + + +static gpg_error_t +parse_bag_data (struct p12_parse_ctx_s *ctx, + const unsigned char *buffer, size_t length, int startoffset, + size_t *r_consumed) +{ + gpg_error_t err = 0; + struct tag_info ti; + const unsigned char *p = buffer; + const unsigned char *p_start = buffer; + size_t n = length; + const char *where; + unsigned char *cram_buffer = NULL; + size_t consumed = 0; /* Number of bytes consumed from the original buffer. */ + + /* Expect: + * [0] + * OCTET STRING, encapsulates + */ + where = "data"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CLASS_CONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING) + goto bailout; + + + consumed = p - p_start; + if (ti.is_constructed && ti.ndef) + { + /* Mozilla exported certs now come with single byte chunks of + octet strings. (Mozilla Firefox 1.0.4). Arghh. */ + where = "data.cram_os"; + cram_buffer = cram_octet_string ( p, &n, &consumed); + if (!cram_buffer) + goto bailout; + p = p_start = cram_buffer; + if (r_consumed) + *r_consumed = consumed; + r_consumed = NULL; /* Ugly hack to not update that value on return. */ + } + + /* Expect: + * SEQUENCE + * SEQUENCE + */ + where = "data.2seqs"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + + /* Expect: + * OBJECT IDENTIFIER + */ + where = "data.oid"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OBJECT_ID) + goto bailout; + + /* Now divert to the actual parser. */ + if (ti.length == DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag) + && !memcmp (p, oid_pkcs_12_pkcs_8ShroudedKeyBag, + DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag))) + { + p += DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag); + n -= DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag); + + if (parse_shrouded_key_bag (ctx, p, n, + startoffset + (p - p_start), r_consumed)) + goto bailout; + } + else if ( ti.length == DIM(oid_pkcs_12_CertBag) + && !memcmp (p, oid_pkcs_12_CertBag, DIM(oid_pkcs_12_CertBag))) + { + p += DIM(oid_pkcs_12_CertBag); + n -= DIM(oid_pkcs_12_CertBag); + + if (parse_cert_bag (ctx, p, n, + startoffset + (p - p_start), r_consumed)) + goto bailout; + } + else + goto bailout; + + goto leave; + + bailout: + log_error ( "data error at \"%s\", offset %u\n", + where, (unsigned int)((p - p_start) + startoffset)); + err = gpg_error (GPG_ERR_GENERAL); + + leave: + gcry_free (cram_buffer); + if (r_consumed) /* Store the number of consumed bytes unless already done. */ + *r_consumed = consumed; + return err; +} + + +/* Parse a PKCS12 object and return an array of MPI representing the + secret key parameters. This is a very limited implementation in + that it is only able to look for 3DES encoded encryptedData and + tries to extract the first private key object it finds. In case of + an error NULL is returned. CERTCB and CERRTCBARG are used to pass + X.509 certificates back to the caller. If R_CURVE is not NULL and + an ECC key was found the OID of the curve is stored there. */ +gcry_mpi_t * +p12_parse (const unsigned char *buffer, size_t length, const char *pw, + void (*certcb)(void*, const unsigned char*, size_t), + void *certcbarg, int *r_badpass, char **r_curve) +{ + struct tag_info ti; + const unsigned char *p = buffer; + const unsigned char *p_start = buffer; + size_t n = length; + const char *where; + int bagseqlength, len; + int bagseqndef, lenndef; + unsigned char *cram_buffer = NULL; + size_t consumed; + struct p12_parse_ctx_s ctx = { NULL }; + + *r_badpass = 0; + + ctx.certcb = certcb; + ctx.certcbarg = certcbarg; + ctx.password = pw; + + + where = "pfx"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_SEQUENCE) + goto bailout; + + where = "pfxVersion"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 3) + goto bailout; + p++; n--; + + where = "authSave"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data) + || memcmp (p, oid_data, DIM(oid_data))) + goto bailout; + p += DIM(oid_data); + n -= DIM(oid_data); + + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CLASS_CONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CLASS_UNIVERSAL || ti.tag != TAG_OCTET_STRING) + goto bailout; + + if (ti.is_constructed && ti.ndef) + { + /* Mozilla exported certs now come with single byte chunks of + octet strings. (Mozilla Firefox 1.0.4). Arghh. */ + where = "cram-bags"; + cram_buffer = cram_octet_string ( p, &n, NULL); + if (!cram_buffer) + goto bailout; + p = p_start = cram_buffer; + } + + where = "bags"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CLASS_UNIVERSAL || ti.tag != TAG_SEQUENCE) + goto bailout; + bagseqndef = ti.ndef; + bagseqlength = ti.length; + while (bagseqlength || bagseqndef) + { + /* log_debug ("p12_parse: at offset %ld\n", (p - p_start)); */ + where = "bag-sequence"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (bagseqndef && ti.class == CLASS_UNIVERSAL + && !ti.tag && !ti.is_constructed) + break; /* Ready */ + if (ti.class != CLASS_UNIVERSAL || ti.tag != TAG_SEQUENCE) + goto bailout; + + if (!bagseqndef) + { + if (bagseqlength < ti.nhdr) + goto bailout; + bagseqlength -= ti.nhdr; + if (bagseqlength < ti.length) + goto bailout; + bagseqlength -= ti.length; + } + lenndef = ti.ndef; + len = ti.length; + + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (lenndef) + len = ti.nhdr; + else + len -= ti.nhdr; + + if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_encryptedData) + && !memcmp (p, oid_encryptedData, DIM(oid_encryptedData))) + { + + p += DIM(oid_encryptedData); + n -= DIM(oid_encryptedData); + if (!lenndef) + len -= DIM(oid_encryptedData); + where = "bag.encryptedData"; + consumed = 0; + if (parse_bag_encrypted_data (&ctx, p, n, (p - p_start), &consumed)) + { + *r_badpass = ctx.badpass; + goto bailout; + } + if (lenndef) + len += consumed; + } + else if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_data) + && !memcmp (p, oid_data, DIM(oid_data))) + { + p += DIM(oid_data); + n -= DIM(oid_data); + if (!lenndef) + len -= DIM(oid_data); + + where = "bag.data"; + consumed = 0; + if (parse_bag_data (&ctx, p, n, (p - p_start), &consumed)) + goto bailout; + if (lenndef) + len += consumed; + } + else + { + log_info ("unknown outer bag type - skipped\n"); + p += ti.length; + n -= ti.length; + } + + if (len < 0 || len > n) + goto bailout; + p += len; + n -= len; + if (lenndef) + { + /* Need to skip the Null Tag. */ + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!(ti.class == CLASS_UNIVERSAL && !ti.tag && !ti.is_constructed)) + goto bailout; + } + } + + gcry_free (cram_buffer); + if (r_curve) + *r_curve = ctx.curve; + else + gcry_free (ctx.curve); + + return ctx.privatekey; + + bailout: + log_error ("error at \"%s\", offset %u\n", + where, (unsigned int)(p - p_start)); + if (ctx.privatekey) + { + int i; + + for (i=0; ctx.privatekey[i]; i++) + gcry_mpi_release (ctx.privatekey[i]); + gcry_free (ctx.privatekey); + ctx.privatekey = NULL; + } + gcry_free (cram_buffer); + gcry_free (ctx.curve); + if (r_curve) + *r_curve = NULL; + return NULL; +} + + + +static size_t +compute_tag_length (size_t n) +{ + int needed = 0; + + if (n < 128) + needed += 2; /* tag and one length byte */ + else if (n < 256) + needed += 3; /* tag, number of length bytes, 1 length byte */ + else if (n < 65536) + needed += 4; /* tag, number of length bytes, 2 length bytes */ + else + { + log_error ("object too larger to encode\n"); + return 0; + } + return needed; +} + +static unsigned char * +store_tag_length (unsigned char *p, int tag, size_t n) +{ + if (tag == TAG_SEQUENCE) + tag |= 0x20; /* constructed */ + + *p++ = tag; + if (n < 128) + *p++ = n; + else if (n < 256) + { + *p++ = 0x81; + *p++ = n; + } + else if (n < 65536) + { + *p++ = 0x82; + *p++ = n >> 8; + *p++ = n; + } + + return p; +} + + +/* Create the final PKCS-12 object from the sequences contained in + SEQLIST. PW is the password. That array is terminated with an NULL + object. */ +static unsigned char * +create_final (struct buffer_s *sequences, const char *pw, size_t *r_length) +{ + int i; + size_t needed = 0; + size_t len[8], n; + unsigned char *macstart; + size_t maclen; + unsigned char *result, *p; + size_t resultlen; + char salt[8]; + unsigned char keybuf[20]; + gcry_md_hd_t md; + int rc; + int with_mac = 1; + + + /* 9 steps to create the pkcs#12 Krampf. */ + + /* 8. The MAC. */ + /* We add this at step 0. */ + + /* 7. All the buffers. */ + for (i=0; sequences[i].buffer; i++) + needed += sequences[i].length; + + /* 6. This goes into a sequences. */ + len[6] = needed; + n = compute_tag_length (needed); + needed += n; + + /* 5. Encapsulate all in an octet string. */ + len[5] = needed; + n = compute_tag_length (needed); + needed += n; + + /* 4. And tag it with [0]. */ + len[4] = needed; + n = compute_tag_length (needed); + needed += n; + + /* 3. Prepend an data OID. */ + needed += 2 + DIM (oid_data); + + /* 2. Put all into a sequences. */ + len[2] = needed; + n = compute_tag_length (needed); + needed += n; + + /* 1. Prepend the version integer 3. */ + needed += 3; + + /* 0. And the final outer sequence. */ + if (with_mac) + needed += DIM (data_mactemplate); + len[0] = needed; + n = compute_tag_length (needed); + needed += n; + + /* Allocate a buffer. */ + result = gcry_malloc (needed); + if (!result) + { + log_error ("error allocating buffer\n"); + return NULL; + } + p = result; + + /* 0. Store the very outer sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[0]); + + /* 1. Store the version integer 3. */ + *p++ = TAG_INTEGER; + *p++ = 1; + *p++ = 3; + + /* 2. Store another sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[2]); + + /* 3. Store the data OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); + memcpy (p, oid_data, DIM (oid_data)); + p += DIM (oid_data); + + /* 4. Next comes a context tag. */ + p = store_tag_length (p, 0xa0, len[4]); + + /* 5. And an octet string. */ + p = store_tag_length (p, TAG_OCTET_STRING, len[5]); + + /* 6. And the inner sequence. */ + macstart = p; + p = store_tag_length (p, TAG_SEQUENCE, len[6]); + + /* 7. Append all the buffers. */ + for (i=0; sequences[i].buffer; i++) + { + memcpy (p, sequences[i].buffer, sequences[i].length); + p += sequences[i].length; + } + + if (with_mac) + { + /* Intermezzo to compute the MAC. */ + maclen = p - macstart; + gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); + if (string_to_key (3, salt, 8, 2048, pw, 20, keybuf)) + { + gcry_free (result); + return NULL; + } + rc = gcry_md_open (&md, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC); + if (rc) + { + log_error ("gcry_md_open failed: %s\n", gpg_strerror (rc)); + gcry_free (result); + return NULL; + } + rc = gcry_md_setkey (md, keybuf, 20); + if (rc) + { + log_error ("gcry_md_setkey failed: %s\n", gpg_strerror (rc)); + gcry_md_close (md); + gcry_free (result); + return NULL; + } + gcry_md_write (md, macstart, maclen); + + /* 8. Append the MAC template and fix it up. */ + memcpy (p, data_mactemplate, DIM (data_mactemplate)); + memcpy (p + DATA_MACTEMPLATE_SALT_OFF, salt, 8); + memcpy (p + DATA_MACTEMPLATE_MAC_OFF, gcry_md_read (md, 0), 20); + p += DIM (data_mactemplate); + gcry_md_close (md); + } + + /* Ready. */ + resultlen = p - result; + if (needed != resultlen) + log_debug ("p12_parse: warning: length mismatch: %lu, %lu\n", + (unsigned long)needed, (unsigned long)resultlen); + + *r_length = resultlen; + return result; +} + + +/* Build a DER encoded SEQUENCE with the key: + * + * SEQUENCE { -- OneAsymmetricKey (RFC-5958) + * INTEGER 0 + * SEQUENCE { + * OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1) + * NULL + * } + * OCTET STRING, encapsulates { + * SEQUENCE { -- RSAPrivateKey (RFC-3447) + * INTEGER 0 -- Version + * INTEGER -- n + * INTEGER -- e + * INTEGER -- d + * INTEGER -- p + * INTEGER -- q + * INTEGER -- d mod (p-1) + * INTEGER -- d mod (q-1) + * INTEGER -- q^-1 mod p + * } + * } + * } + * + * MODE controls what is being generated: + * 0 - As described above + * 1 - Ditto but without the padding + * 2 - Only the inner part (pkcs#1) + */ + +static unsigned char * +build_rsa_key_sequence (gcry_mpi_t *kparms, int mode, size_t *r_length) +{ + int rc, i; + size_t needed, n; + unsigned char *plain, *p; + size_t plainlen; + size_t outseqlen, oidseqlen, octstrlen, inseqlen; + + needed = 3; /* The version integer with value 0. */ + for (i=0; kparms[i]; i++) + { + n = 0; + rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]); + if (rc) + { + log_error ("error formatting parameter: %s\n", gpg_strerror (rc)); + return NULL; + } + needed += n; + n = compute_tag_length (n); + if (!n) + return NULL; + needed += n; + } + if (i != 8) + { + log_error ("invalid parameters for p12_build\n"); + return NULL; + } + /* Now this all goes into a sequence. */ + inseqlen = needed; + n = compute_tag_length (needed); + if (!n) + return NULL; + needed += n; + + if (mode != 2) + { + /* Encapsulate all into an octet string. */ + octstrlen = needed; + n = compute_tag_length (needed); + if (!n) + return NULL; + needed += n; + /* Prepend the object identifier sequence. */ + oidseqlen = 2 + DIM (oid_rsaEncryption) + 2; + needed += 2 + oidseqlen; + /* The version number. */ + needed += 3; + /* And finally put the whole thing into a sequence. */ + outseqlen = needed; + n = compute_tag_length (needed); + if (!n) + return NULL; + needed += n; + } + + /* allocate 8 extra bytes for padding */ + plain = gcry_malloc_secure (needed+8); + if (!plain) + { + log_error ("error allocating encryption buffer\n"); + return NULL; + } + + /* And now fill the plaintext buffer. */ + p = plain; + if (mode != 2) + { + p = store_tag_length (p, TAG_SEQUENCE, outseqlen); + /* Store version. */ + *p++ = TAG_INTEGER; + *p++ = 1; + *p++ = 0; + /* Store object identifier sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, oidseqlen); + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_rsaEncryption)); + memcpy (p, oid_rsaEncryption, DIM (oid_rsaEncryption)); + p += DIM (oid_rsaEncryption); + *p++ = TAG_NULL; + *p++ = 0; + /* Start with the octet string. */ + p = store_tag_length (p, TAG_OCTET_STRING, octstrlen); + } + + p = store_tag_length (p, TAG_SEQUENCE, inseqlen); + /* Store the key parameters. */ + *p++ = TAG_INTEGER; + *p++ = 1; + *p++ = 0; + for (i=0; kparms[i]; i++) + { + n = 0; + rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]); + if (rc) + { + log_error ("oops: error formatting parameter: %s\n", + gpg_strerror (rc)); + gcry_free (plain); + return NULL; + } + p = store_tag_length (p, TAG_INTEGER, n); + + n = plain + needed - p; + rc = gcry_mpi_print (GCRYMPI_FMT_STD, p, n, &n, kparms[i]); + if (rc) + { + log_error ("oops: error storing parameter: %s\n", + gpg_strerror (rc)); + gcry_free (plain); + return NULL; + } + p += n; + } + + plainlen = p - plain; + log_assert (needed == plainlen); + + if (!mode) + { + /* Append some pad characters; we already allocated extra space. */ + n = 8 - plainlen % 8; + for (i=0; i < n; i++, plainlen++) + *p++ = n; + } + + *r_length = plainlen; + return plain; +} + + +/* Build a DER encoded SEQUENCE for an ECC key: + * + * SEQUENCE { -- OneAsymmetricKey (RFC-5958) + * INTEGER 0 + * SEQUENCE { + * OBJECT IDENTIFIER ecPublicKey (1 2 840 10045 2 1) + * OBJECT IDENTIFIER -- curvename + * } + * OCTET STRING, encapsulates { + * SEQUENCE { -- ECPrivateKey + * INTEGER 1 -- version + * OCTET STRING -- privateKey + * [1] { + * BIT STRING - publicKey + * } + * } + * } + * } + * + * For details see RFC-5480 and RFC-5915 (ECparameters are not created). + * + * KPARMS[0] := Opaque MPI with the curve name as dotted-decimal string. + * KPARMS[1] := Opaque MPI with the public key (q) + * KPARMS[2] := Opaque MPI with the private key (d) + * MODE controls what is being generated: + * 0 - As described above + * 1 - Ditto but without the extra padding needed for pcsk#12 + * 2 - Only the octet string (ECPrivateKey) + */ + +static unsigned char * +build_ecc_key_sequence (gcry_mpi_t *kparms, int mode, size_t *r_length) +{ + gpg_error_t err; + unsigned int nbits, n; + const unsigned char *s; + char *p; + tlv_builder_t tb; + void *result; + size_t resultlen; + const char *curve; + unsigned int curvebits; + int e; + int i; + int strip_one; + + for (i=0; kparms[i]; i++) + ; + if (i != 3) + { + log_error ("%s: invalid number of parameters\n", __func__); + return NULL; + } + + s = gcry_mpi_get_opaque (kparms[0], &nbits); + n = (nbits+7)/8; + p = xtrymalloc (n + 1); + if (!p) + { + err = gpg_error_from_syserror (); + log_error ("%s:%d: error getting parameter: %s\n", + __func__, __LINE__, gpg_strerror (err)); + return NULL; + } + memcpy (p, s, n); + p[n] = 0; + /* We need to use our OpenPGP mapping to turn a curve name into its + * canonical numerical OID. We should have a Libgcrypt function to + * do this; see bug report #4926. */ + curve = openpgp_curve_to_oid (p, &curvebits, NULL); + xfree (p); + if (!curve) + { + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + log_error ("%s:%d: error getting parameter: %s\n", + __func__, __LINE__, gpg_strerror (err)); + return NULL; + } + + /* Unfortunately the private key D may come with a single leading + * zero byte. This is becuase at some point it was treated as + * signed MPI and the code made sure that it is always interpreted + * as unsigned. Fortunately we got the size of the curve and can + * detect such a case reliable. */ + s = gcry_mpi_get_opaque (kparms[2], &nbits); + n = (nbits+7)/8; + strip_one = (n == (curvebits+7)/8 + 1 && !*s); + + + tb = tlv_builder_new (1); + if (!tb) + { + err = gpg_error_from_syserror (); + log_error ("%s:%d: error creating new TLV builder: %s\n", + __func__, __LINE__, gpg_strerror (err)); + return NULL; + } + e = 0; + tlv_builder_add_tag (tb, 0, TAG_SEQUENCE); + tlv_builder_add_ptr (tb, 0, TAG_INTEGER, "\0", 1); + tlv_builder_add_tag (tb, 0, TAG_SEQUENCE); + e|= builder_add_oid (tb, 0, "1.2.840.10045.2.1"); + e|= builder_add_oid (tb, 0, curve); + tlv_builder_add_end (tb); + tlv_builder_add_tag (tb, 0, TAG_OCTET_STRING); + tlv_builder_add_tag (tb, 0, TAG_SEQUENCE); + tlv_builder_add_ptr (tb, 0, TAG_INTEGER, "\x01", 1); + e|= builder_add_mpi (tb, 0, TAG_OCTET_STRING, kparms[2], strip_one); + tlv_builder_add_tag (tb, CLASS_CONTEXT, 1); + e|= builder_add_mpi (tb, 0, TAG_BIT_STRING, kparms[1], 0); + tlv_builder_add_end (tb); + tlv_builder_add_end (tb); + tlv_builder_add_end (tb); + tlv_builder_add_end (tb); + + err = tlv_builder_finalize (tb, &result, &resultlen); + if (err || e) + { + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + log_error ("%s:%d: tlv building failed: %s\n", + __func__, __LINE__, gpg_strerror (err)); + return NULL; + } + + /* Append some pad characters if needed. */ + if (!mode && (n = 8 - resultlen % 8)) + { + p = xtrymalloc_secure (resultlen + n); + if (!p) + { + err = gpg_error_from_syserror (); + log_error ("%s:%d: error allocating buffer: %s\n", + __func__, __LINE__, gpg_strerror (err)); + xfree (result); + return NULL; + } + memcpy (p, result, resultlen); + xfree (result); + result = p; + p = (unsigned char*)result + resultlen; + for (i=0; i < n; i++, resultlen++) + *p++ = n; + } + + *r_length = resultlen; + + return result; +} + + +static unsigned char * +build_key_bag (unsigned char *buffer, size_t buflen, char *salt, + const unsigned char *sha1hash, const char *keyidstr, + size_t *r_length) +{ + size_t len[11], needed; + unsigned char *p, *keybag; + size_t keybaglen; + + /* Walk 11 steps down to collect the info: */ + + /* 10. The data goes into an octet string. */ + needed = compute_tag_length (buflen); + needed += buflen; + + /* 9. Prepend the algorithm identifier. */ + needed += DIM (data_3desiter2048); + + /* 8. Put a sequence around. */ + len[8] = needed; + needed += compute_tag_length (needed); + + /* 7. Prepend a [0] tag. */ + len[7] = needed; + needed += compute_tag_length (needed); + + /* 6b. The attributes which are appended at the end. */ + if (sha1hash) + needed += DIM (data_attrtemplate) + 20; + + /* 6. Prepend the shroudedKeyBag OID. */ + needed += 2 + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag); + + /* 5+4. Put all into two sequences. */ + len[5] = needed; + needed += compute_tag_length ( needed); + len[4] = needed; + needed += compute_tag_length (needed); + + /* 3. This all goes into an octet string. */ + len[3] = needed; + needed += compute_tag_length (needed); + + /* 2. Prepend another [0] tag. */ + len[2] = needed; + needed += compute_tag_length (needed); + + /* 1. Prepend the data OID. */ + needed += 2 + DIM (oid_data); + + /* 0. Prepend another sequence. */ + len[0] = needed; + needed += compute_tag_length (needed); + + /* Now that we have all length information, allocate a buffer. */ + p = keybag = gcry_malloc (needed); + if (!keybag) + { + log_error ("error allocating buffer\n"); + return NULL; + } + + /* Walk 11 steps up to store the data. */ + + /* 0. Store the first sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[0]); + + /* 1. Store the data OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); + memcpy (p, oid_data, DIM (oid_data)); + p += DIM (oid_data); + + /* 2. Store a [0] tag. */ + p = store_tag_length (p, 0xa0, len[2]); + + /* 3. And an octet string. */ + p = store_tag_length (p, TAG_OCTET_STRING, len[3]); + + /* 4+5. Two sequences. */ + p = store_tag_length (p, TAG_SEQUENCE, len[4]); + p = store_tag_length (p, TAG_SEQUENCE, len[5]); + + /* 6. Store the shroudedKeyBag OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag)); + memcpy (p, oid_pkcs_12_pkcs_8ShroudedKeyBag, + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag)); + p += DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag); + + /* 7. Store a [0] tag. */ + p = store_tag_length (p, 0xa0, len[7]); + + /* 8. Store a sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[8]); + + /* 9. Now for the pre-encoded algorithm identifier and the salt. */ + memcpy (p, data_3desiter2048, DIM (data_3desiter2048)); + memcpy (p + DATA_3DESITER2048_SALT_OFF, salt, 8); + p += DIM (data_3desiter2048); + + /* 10. And the octet string with the encrypted data. */ + p = store_tag_length (p, TAG_OCTET_STRING, buflen); + memcpy (p, buffer, buflen); + p += buflen; + + /* Append the attributes whose length we calculated at step 2b. */ + if (sha1hash) + { + int i; + + memcpy (p, data_attrtemplate, DIM (data_attrtemplate)); + for (i=0; i < 8; i++) + p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i]; + p += DIM (data_attrtemplate); + memcpy (p, sha1hash, 20); + p += 20; + } + + + keybaglen = p - keybag; + if (needed != keybaglen) + log_debug ("p12_parse: warning: length mismatch: %lu, %lu\n", + (unsigned long)needed, (unsigned long)keybaglen); + + *r_length = keybaglen; + return keybag; +} + + +static unsigned char * +build_cert_bag (unsigned char *buffer, size_t buflen, char *salt, + size_t *r_length) +{ + size_t len[9], needed; + unsigned char *p, *certbag; + size_t certbaglen; + + /* Walk 9 steps down to collect the info: */ + + /* 8. The data goes into an octet string. */ + needed = compute_tag_length (buflen); + needed += buflen; + + /* 7. The algorithm identifier. */ + needed += DIM (data_rc2iter2048); + + /* 6. The data OID. */ + needed += 2 + DIM (oid_data); + + /* 5. A sequence. */ + len[5] = needed; + needed += compute_tag_length ( needed); + + /* 4. An integer. */ + needed += 3; + + /* 3. A sequence. */ + len[3] = needed; + needed += compute_tag_length (needed); + + /* 2. A [0] tag. */ + len[2] = needed; + needed += compute_tag_length (needed); + + /* 1. The encryptedData OID. */ + needed += 2 + DIM (oid_encryptedData); + + /* 0. The first sequence. */ + len[0] = needed; + needed += compute_tag_length (needed); + + /* Now that we have all length information, allocate a buffer. */ + p = certbag = gcry_malloc (needed); + if (!certbag) + { + log_error ("error allocating buffer\n"); + return NULL; + } + + /* Walk 9 steps up to store the data. */ + + /* 0. Store the first sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[0]); + + /* 1. Store the encryptedData OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_encryptedData)); + memcpy (p, oid_encryptedData, DIM (oid_encryptedData)); + p += DIM (oid_encryptedData); + + /* 2. Store a [0] tag. */ + p = store_tag_length (p, 0xa0, len[2]); + + /* 3. Store a sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[3]); + + /* 4. Store the integer 0. */ + *p++ = TAG_INTEGER; + *p++ = 1; + *p++ = 0; + + /* 5. Store a sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[5]); + + /* 6. Store the data OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); + memcpy (p, oid_data, DIM (oid_data)); + p += DIM (oid_data); + + /* 7. Now for the pre-encoded algorithm identifier and the salt. */ + memcpy (p, data_rc2iter2048, DIM (data_rc2iter2048)); + memcpy (p + DATA_RC2ITER2048_SALT_OFF, salt, 8); + p += DIM (data_rc2iter2048); + + /* 8. And finally the [0] tag with the encrypted data. */ + p = store_tag_length (p, 0x80, buflen); + memcpy (p, buffer, buflen); + p += buflen; + certbaglen = p - certbag; + + if (needed != certbaglen) + log_debug ("p12_parse: warning: length mismatch: %lu, %lu\n", + (unsigned long)needed, (unsigned long)certbaglen); + + *r_length = certbaglen; + return certbag; +} + + +static unsigned char * +build_cert_sequence (const unsigned char *buffer, size_t buflen, + const unsigned char *sha1hash, const char *keyidstr, + size_t *r_length) +{ + size_t len[8], needed, n; + unsigned char *p, *certseq; + size_t certseqlen; + int i; + + log_assert (strlen (keyidstr) == 8); + + /* Walk 8 steps down to collect the info: */ + + /* 7. The data goes into an octet string. */ + needed = compute_tag_length (buflen); + needed += buflen; + + /* 6. A [0] tag. */ + len[6] = needed; + needed += compute_tag_length (needed); + + /* 5. An OID. */ + needed += 2 + DIM (oid_x509Certificate_for_pkcs_12); + + /* 4. A sequence. */ + len[4] = needed; + needed += compute_tag_length (needed); + + /* 3. A [0] tag. */ + len[3] = needed; + needed += compute_tag_length (needed); + + /* 2b. The attributes which are appended at the end. */ + if (sha1hash) + needed += DIM (data_attrtemplate) + 20; + + /* 2. An OID. */ + needed += 2 + DIM (oid_pkcs_12_CertBag); + + /* 1. A sequence. */ + len[1] = needed; + needed += compute_tag_length (needed); + + /* 0. The first sequence. */ + len[0] = needed; + needed += compute_tag_length (needed); + + /* Now that we have all length information, allocate a buffer. */ + p = certseq = gcry_malloc (needed + 8 /*(for padding)*/); + if (!certseq) + { + log_error ("error allocating buffer\n"); + return NULL; + } + + /* Walk 8 steps up to store the data. */ + + /* 0. Store the first sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[0]); + + /* 1. Store the second sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[1]); + + /* 2. Store the pkcs12-cert-bag OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_pkcs_12_CertBag)); + memcpy (p, oid_pkcs_12_CertBag, DIM (oid_pkcs_12_CertBag)); + p += DIM (oid_pkcs_12_CertBag); + + /* 3. Store a [0] tag. */ + p = store_tag_length (p, 0xa0, len[3]); + + /* 4. Store a sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[4]); + + /* 5. Store the x509Certificate OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, + DIM (oid_x509Certificate_for_pkcs_12)); + memcpy (p, oid_x509Certificate_for_pkcs_12, + DIM (oid_x509Certificate_for_pkcs_12)); + p += DIM (oid_x509Certificate_for_pkcs_12); + + /* 6. Store a [0] tag. */ + p = store_tag_length (p, 0xa0, len[6]); + + /* 7. And the octet string with the actual certificate. */ + p = store_tag_length (p, TAG_OCTET_STRING, buflen); + memcpy (p, buffer, buflen); + p += buflen; + + /* Append the attributes whose length we calculated at step 2b. */ + if (sha1hash) + { + memcpy (p, data_attrtemplate, DIM (data_attrtemplate)); + for (i=0; i < 8; i++) + p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i]; + p += DIM (data_attrtemplate); + memcpy (p, sha1hash, 20); + p += 20; + } + + certseqlen = p - certseq; + if (needed != certseqlen) + log_debug ("p12_parse: warning: length mismatch: %lu, %lu\n", + (unsigned long)needed, (unsigned long)certseqlen); + + /* Append some pad characters; we already allocated extra space. */ + n = 8 - certseqlen % 8; + for (i=0; i < n; i++, certseqlen++) + *p++ = n; + + *r_length = certseqlen; + return certseq; +} + + +/* Expect the RSA key parameters in KPARMS and a password in PW. + Create a PKCS structure from it and return it as well as the length + in R_LENGTH; return NULL in case of an error. If CHARSET is not + NULL, re-encode PW to that character set. */ +unsigned char * +p12_build (gcry_mpi_t *kparms, const void *cert, size_t certlen, + const char *pw, const char *charset, size_t *r_length) +{ + unsigned char *buffer = NULL; + size_t n, buflen; + char salt[8]; + struct buffer_s seqlist[3]; + int seqlistidx = 0; + unsigned char sha1hash[20]; + char keyidstr[8+1]; + char *pwbuf = NULL; + size_t pwbufsize = 0; + + n = buflen = 0; /* (avoid compiler warning). */ + memset (sha1hash, 0, 20); + *keyidstr = 0; + + if (charset && pw && *pw) + { + jnlib_iconv_t cd; + const char *inptr; + char *outptr; + size_t inbytes, outbytes; + + /* We assume that the converted passphrase is at max 2 times + longer than its utf-8 encoding. */ + pwbufsize = strlen (pw)*2 + 1; + pwbuf = gcry_malloc_secure (pwbufsize); + if (!pwbuf) + { + log_error ("out of secure memory while converting passphrase\n"); + goto failure; + } + + cd = jnlib_iconv_open (charset, "utf-8"); + if (cd == (jnlib_iconv_t)(-1)) + { + log_error ("can't convert passphrase to" + " requested charset '%s': %s\n", + charset, strerror (errno)); + goto failure; + } + + inptr = pw; + inbytes = strlen (pw); + outptr = pwbuf; + outbytes = pwbufsize - 1; + if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes, + &outptr, &outbytes) == (size_t)-1) + { + log_error ("error converting passphrase to" + " requested charset '%s': %s\n", + charset, strerror (errno)); + jnlib_iconv_close (cd); + goto failure; + } + *outptr = 0; + jnlib_iconv_close (cd); + pw = pwbuf; + } + + + if (cert && certlen) + { + /* Calculate the hash value we need for the bag attributes. */ + gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash, cert, certlen); + sprintf (keyidstr, "%02x%02x%02x%02x", + sha1hash[16], sha1hash[17], sha1hash[18], sha1hash[19]); + + /* Encode the certificate. */ + buffer = build_cert_sequence (cert, certlen, sha1hash, keyidstr, + &buflen); + if (!buffer) + goto failure; + + /* Encrypt it. */ + gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); + crypt_block (buffer, buflen, salt, 8, 2048, NULL, 0, pw, + GCRY_CIPHER_RFC2268_40, 1); + + /* Encode the encrypted stuff into a bag. */ + seqlist[seqlistidx].buffer = build_cert_bag (buffer, buflen, salt, &n); + seqlist[seqlistidx].length = n; + gcry_free (buffer); + buffer = NULL; + if (!seqlist[seqlistidx].buffer) + goto failure; + seqlistidx++; + } + + + if (kparms) + { + /* Encode the key. */ + int i; + + /* Right, that is a stupid way to distinguish ECC from RSA. */ + for (i=0; kparms[i]; i++) + ; + + if (i == 3 && gcry_mpi_get_flag (kparms[0], GCRYMPI_FLAG_OPAQUE)) + buffer = build_ecc_key_sequence (kparms, 0, &buflen); + else + buffer = build_rsa_key_sequence (kparms, 0, &buflen); + if (!buffer) + goto failure; + + /* Encrypt it. */ + gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); + crypt_block (buffer, buflen, salt, 8, 2048, NULL, 0, + pw, GCRY_CIPHER_3DES, 1); + + /* Encode the encrypted stuff into a bag. */ + if (cert && certlen) + seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt, + sha1hash, keyidstr, &n); + else + seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt, + NULL, NULL, &n); + seqlist[seqlistidx].length = n; + gcry_free (buffer); + buffer = NULL; + if (!seqlist[seqlistidx].buffer) + goto failure; + seqlistidx++; + } + + seqlist[seqlistidx].buffer = NULL; + seqlist[seqlistidx].length = 0; + + buffer = create_final (seqlist, pw, &buflen); + + failure: + if (pwbuf) + { + /* Note that wipememory is not really needed due to the use of + gcry_malloc_secure. */ + wipememory (pwbuf, pwbufsize); + gcry_free (pwbuf); + } + for ( ; seqlistidx; seqlistidx--) + gcry_free (seqlist[seqlistidx].buffer); + + *r_length = buffer? buflen : 0; + return buffer; +} + + +/* This is actually not a PKCS#12 function but one which creates an + * unencrypted PKCS#1 private key. */ +unsigned char * +p12_raw_build (gcry_mpi_t *kparms, int rawmode, size_t *r_length) +{ + unsigned char *buffer; + size_t buflen; + int i; + + log_assert (rawmode == 1 || rawmode == 2); + + /* Right, that is a stupid way to distinguish ECC from RSA. */ + for (i=0; kparms[i]; i++) + ; + + if (gcry_mpi_get_flag (kparms[0], GCRYMPI_FLAG_OPAQUE)) + buffer = build_ecc_key_sequence (kparms, rawmode, &buflen); + else + buffer = build_rsa_key_sequence (kparms, rawmode, &buflen); + if (!buffer) + return NULL; + + *r_length = buflen; + return buffer; +} diff --git a/sm/minip12.h b/sm/minip12.h new file mode 100644 index 0000000..84c5f5f --- /dev/null +++ b/sm/minip12.h @@ -0,0 +1,42 @@ +/* minip12.h - Global definitions for the minimal pkcs-12 implementation. + * Copyright (C) 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 <https://www.gnu.org/licenses/>. + */ + +#ifndef MINIP12_H +#define MINIP12_H + +#include <gcrypt.h> + + +void p12_set_verbosity (int verbose); + +gcry_mpi_t *p12_parse (const unsigned char *buffer, size_t length, + const char *pw, + void (*certcb)(void*, const unsigned char*, size_t), + void *certcbarg, int *r_badpass, char **r_curve); + +unsigned char *p12_build (gcry_mpi_t *kparms, + const void *cert, size_t certlen, + const char *pw, const char *charset, + size_t *r_length); +unsigned char *p12_raw_build (gcry_mpi_t *kparms, + int rawmode, + size_t *r_length); + + +#endif /*MINIP12_H*/ diff --git a/sm/misc.c b/sm/misc.c new file mode 100644 index 0000000..66d928c --- /dev/null +++ b/sm/misc.c @@ -0,0 +1,308 @@ +/* misc.c - Miscellaneous functions + * Copyright (C) 2004, 2009, 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif + +#include "gpgsm.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/tlv.h" +#include "../common/sexp-parse.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) + gnupg_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 = gnupg_ttyname (0); + if (!lc) + lc = "/dev/tty"; + gnupg_setenv ("GPG_TTY", lc, 1); + } + + if (opt.lc_ctype) + gnupg_setenv ("LC_CTYPE", opt.lc_ctype, 1); +#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) + else if ( (lc = setlocale (LC_CTYPE, "")) ) + gnupg_setenv ("LC_CTYPE", lc, 1); +#endif + + if (opt.lc_messages) + gnupg_setenv ("LC_MESSAGES", opt.lc_messages, 1); +#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) + else if ( (lc = setlocale (LC_MESSAGES, "")) ) + gnupg_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) + gnupg_setenv (name, value, 1); + } + +#endif /*!HAVE_W32_SYSTEM*/ +} + + + +/* Transform a sig-val style s-expression as returned by Libgcrypt to + one which includes an algorithm identifier encoding the public key + and the hash algorithm. The public key algorithm is taken directly + from SIGVAL and the hash algorithm is given by MDALGO. This is + required because X.509 merges the public key algorithm and the hash + algorithm into one OID but Libgcrypt is not aware of that. The + function ignores missing parameters so that it can also be used to + create an siginfo value as expected by ksba_certreq_set_siginfo. + To create a siginfo s-expression a public-key s-expression may be + used instead of a sig-val. We only support RSA for now. */ +gpg_error_t +transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, + unsigned char **r_newsigval, size_t *r_newsigvallen) +{ + gpg_error_t err; + const unsigned char *buf, *tok; + size_t buflen, toklen; + int depth, last_depth1, last_depth2; + int is_pubkey = 0; + const unsigned char *rsa_s = NULL; + size_t rsa_s_len = 0; + const char *oid; + gcry_sexp_t sexp; + + *r_newsigval = NULL; + if (r_newsigvallen) + *r_newsigvallen = 0; + + buf = sigval; + buflen = sigvallen; + depth = 0; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + return err; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + return err; + if (tok && toklen == 7 && !memcmp ("sig-val", tok, toklen)) + ; + else if (tok && toklen == 10 && !memcmp ("public-key", tok, toklen)) + is_pubkey = 1; + else + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + return err; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + return err; + if (!tok || toklen != 3 || memcmp ("rsa", tok, toklen)) + return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + + last_depth1 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth1) + { + if (tok) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + return err; + if (tok && toklen == 1) + { + const unsigned char **mpi; + size_t *mpi_len; + + switch (*tok) + { + case 's': mpi = &rsa_s; mpi_len = &rsa_s_len; break; + default: mpi = NULL; mpi_len = NULL; break; + } + if (mpi && *mpi) + return gpg_error (GPG_ERR_DUP_VALUE); + + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + return err; + if (tok && mpi) + { + *mpi = tok; + *mpi_len = toklen; + } + } + + /* Skip to the end of the list. */ + last_depth2 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth2) + ; + if (err) + return err; + } + if (err) + return err; + + /* Map the hash algorithm to an OID. */ + switch (mdalgo) + { + case GCRY_MD_SHA1: + oid = "1.2.840.113549.1.1.5"; /* sha1WithRSAEncryption */ + break; + + case GCRY_MD_SHA256: + oid = "1.2.840.113549.1.1.11"; /* sha256WithRSAEncryption */ + break; + + case GCRY_MD_SHA384: + oid = "1.2.840.113549.1.1.12"; /* sha384WithRSAEncryption */ + break; + + case GCRY_MD_SHA512: + oid = "1.2.840.113549.1.1.13"; /* sha512WithRSAEncryption */ + break; + + default: + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + + if (rsa_s && !is_pubkey) + err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s(s%b)))", + oid, (int)rsa_s_len, rsa_s); + else + err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s))", oid); + if (err) + return err; + err = make_canon_sexp (sexp, r_newsigval, r_newsigvallen); + gcry_sexp_release (sexp); + + return err; +} + + +/* Wrapper around ksba_cms_get_sig_val to return a gcrypt object + * instaed of ksba's canonical s-expression. On errror NULL is return + * and in some cases an error message is printed. */ +gcry_sexp_t +gpgsm_ksba_cms_get_sig_val (ksba_cms_t cms, int idx) +{ + gpg_error_t err; + ksba_sexp_t sigval; + gcry_sexp_t s_sigval; + size_t n; + + sigval = ksba_cms_get_sig_val (cms, idx); + if (!sigval) + return NULL; + n = gcry_sexp_canon_len (sigval, 0, NULL, NULL); + if (!n) + { + log_error ("%s: libksba did not return a proper S-Exp\n", __func__); + ksba_free (sigval); + return NULL; + } + err = gcry_sexp_sscan (&s_sigval, NULL, (char*)sigval, n); + ksba_free (sigval); + if (err) + { + log_error ("%s: gcry_sexp_scan failed: %s\n", + __func__, gpg_strerror (err)); + s_sigval = NULL; + } + + return s_sigval; +} + + +/* Return the hash algorithm from the S-expression SIGVAL. Returns 0 + * if the hash algorithm is not encoded in SIGVAL or it is not + * supported by libgcrypt. It further stores flag values for the + * public key algorithm at R_PKALGO_FLAGS; the only flag we currently + * support is PK_ALGO_FLAG_RSAPSS. */ +int +gpgsm_get_hash_algo_from_sigval (gcry_sexp_t sigval_arg, + unsigned int *r_pkalgo_flags) +{ + gcry_sexp_t sigval, l1; + size_t n; + const char *s; + char *string; + int hashalgo; + int i; + + *r_pkalgo_flags = 0; + + sigval = gcry_sexp_find_token (sigval_arg, "sig-val", 0); + if (!sigval) + return 0; /* Not a sig-val. */ + + /* First check whether this is a rsaPSS signature and return that as + * additional info. */ + l1 = gcry_sexp_find_token (sigval, "flags", 0); + if (l1) + { + /* Note that the flag parser assumes that the list of flags + * contains only strings and in particular not a sub-list. This + * is always the case for the current libksba. */ + for (i=1; (s = gcry_sexp_nth_data (l1, i, &n)); i++) + if (n == 3 && !memcmp (s, "pss", 3)) + { + *r_pkalgo_flags |= PK_ALGO_FLAG_RSAPSS; + break; + } + gcry_sexp_release (l1); + } + + l1 = gcry_sexp_find_token (sigval, "hash", 0); + if (!l1) + { + gcry_sexp_release (sigval); + return 0; /* hash algorithm not given in sigval. */ + } + string = gcry_sexp_nth_string (l1, 1); + gcry_sexp_release (sigval); + if (!string) + return 0; /* hash algorithm has no value. */ + hashalgo = gcry_md_map_name (string); + gcry_free (string); + + return hashalgo; +} diff --git a/sm/passphrase.c b/sm/passphrase.c new file mode 100644 index 0000000..09eac07 --- /dev/null +++ b/sm/passphrase.c @@ -0,0 +1,90 @@ +/* passphrase.c - Get a passphrase + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, + * 2005, 2006, 2007, 2009, 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <unistd.h> + +#include "passphrase.h" +#include "gpgsm.h" +#include "../common/shareddefs.h" +#include "../common/ttyio.h" + +static char *fd_passwd = NULL; + +int +have_static_passphrase () +{ + return (!!fd_passwd + && (opt.batch || opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)); +} + +/* Return a static passphrase. The returned value is only valid as + long as no other passphrase related function is called. NULL may + be returned if no passphrase has been set; better use + have_static_passphrase first. */ +const char * +get_static_passphrase (void) +{ + return fd_passwd; +} + +void +read_passphrase_from_fd (int fd) +{ + int i, len; + char *pw; + + if (!opt.batch && opt.pinentry_mode != PINENTRY_MODE_LOOPBACK) + { /* Not used but we have to do a dummy read, so that it won't end + up at the begin of the message if the quite usual trick to + prepend the passphtrase to the message is used. */ + char buf[1]; + + while (!(read (fd, buf, 1) != 1 || *buf == '\n')) + ; + *buf = 0; + return; + } + + for (pw = NULL, i = len = 100; ; i++) + { + if (i >= len-1) + { + char *pw2 = pw; + len += 100; + pw = xmalloc_secure (len); + if (pw2) + { + memcpy (pw, pw2, i); + xfree (pw2); + } + else + i = 0; + } + if (read (fd, pw+i, 1) != 1 || pw[i] == '\n') + break; + } + pw[i] = 0; + if (!opt.batch && opt.pinentry_mode != PINENTRY_MODE_LOOPBACK) + tty_printf("\b\b\b \n" ); + + xfree (fd_passwd); + fd_passwd = pw; +} diff --git a/sm/passphrase.h b/sm/passphrase.h new file mode 100644 index 0000000..c69f4d9 --- /dev/null +++ b/sm/passphrase.h @@ -0,0 +1,27 @@ +/* passphrase.h - Get a passphrase + * Copyright (C) 2016 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef GPGSM_PASSPHRASE_H +#define GPGSM_PASSPHRASE_H + +int have_static_passphrase (void); +const char *get_static_passphrase (void); +void read_passphrase_from_fd (int fd); + +#endif /* GPGSM_PASSPHRASE_H */ diff --git a/sm/qualified.c b/sm/qualified.c new file mode 100644 index 0000000..b433717 --- /dev/null +++ b/sm/qualified.c @@ -0,0 +1,325 @@ +/* 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 <https://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 "../common/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 initialized and if the + LISTFP is also NULL, no list of qualified signatures exists. */ +static char *listname; +static estream_t 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 = es_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 (!es_fgets (line, DIM(line)-1, listfp) ) + { + if (es_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 = es_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) + { + /* W32ce has no rewind, thus we use the equivalent code. */ + es_fseek (listfp, 0, SEEK_SET); + es_clearerr (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 other 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..5341d31 --- /dev/null +++ b/sm/server.c @@ -0,0 +1,1508 @@ +/* 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 <https://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 "../common/sysutils.h" +#include "../common/server-help.h" +#include "../common/asshelp.h" +#include "../common/shareddefs.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 gpgrt_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; +} + + +/* A write handler used by es_fopencookie to write assuan data + lines. */ +static gpgrt_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)) + { + gpg_err_set_errno (EIO); + return -1; + } + + return (gpgrt_ssize_t)size; +} + +static int +data_line_cookie_close (void *cookie) +{ + assuan_context_t ctx = cookie; + + if (assuan_send_data (ctx, NULL, 0)) + { + gpg_err_set_errno (EIO); + return -1; + } + + return 0; +} + + +static void +close_message_fd (ctrl_t ctrl) +{ + if (ctrl->server_local->message_fd != -1) + { +#ifdef HAVE_W32CE_SYSTEM +#warning Is this correct for W32/W32CE? +#endif + 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, "with-secret")) + { + int i = *value? atoi (value) : 0; + ctrl->with_secret = i; + } + else if (!strcmp (key, "validation-model")) + { + int i = gpgsm_parse_validation_model (value); + if ( i >= 0 && i <= 2 ) + 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 if (!strcmp (key, "offline")) + { + /* We ignore this option if gpgsm has been started with + --disable-dirmngr (which also sets offline). */ + if (!opt.disable_dirmngr) + { + int i = *value? !!atoi (value) : 1; + ctrl->offline = i; + } + } + else if (!strcmp (key, "request-origin")) + { + if (!opt.request_origin) + { + int i = parse_request_origin (value); + if (i == -1) + err = gpg_error (GPG_ERR_INV_VALUE); + else + opt.request_origin = i; + } + } + 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 because 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 compatibility 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; + estream_t 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 = es_fdopen_nc (out_fd, "w"); + if (!out_fp) + return set_error (gpg_err_code_from_syserror (), "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); + es_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; + estream_t 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 = es_fdopen_nc (out_fd, "w"); + if (!out_fp) + return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); + + rc = start_audit_session (ctrl); + if (!rc) + rc = gpgsm_decrypt (ctrl, inp_fd, out_fp); + es_fclose (out_fp); + + /* Close and reset the fds. */ + 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); + estream_t out_fp = NULL; + + (void)line; + + if (fd == -1) + return set_error (GPG_ERR_ASS_NO_INPUT, NULL); + + if (out_fd != -1) + { + out_fp = es_fdopen_nc (out_fd, "w"); + if (!out_fp) + return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); + } + + rc = start_audit_session (ctrl); + if (!rc) + rc = gpgsm_verify (assuan_get_pointer (ctx), fd, + ctrl->server_local->message_fd, out_fp); + es_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; + estream_t 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 = es_fdopen_nc (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); + es_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]] [--secret [--(raw|pkcs12)] [--] <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.\n" + "With --secret the secret key is exported using the PKCS#8 format,\n" + "with --raw using PKCS#1, and with --pkcs12 as full PKCS#12 container."; +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; + int opt_secret; + int opt_raw = 0; + int opt_pkcs12 = 0; + + 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"); + } + opt_secret = has_option (line, "--secret"); + if (opt_secret) + { + opt_raw = has_option (line, "--raw"); + opt_pkcs12 = has_option (line, "--pkcs12"); + } + + 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 (opt_secret) + { + if (!list) + return set_error (GPG_ERR_NO_DATA, "No key given"); + if (!*list->d) + { + free_strlist (list); + return set_error (GPG_ERR_NO_DATA, "No key given"); + } + if (list->next) + return set_error (GPG_ERR_TOO_MANY, "Only one key allowed"); + } + + 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"); + } + if (opt_secret) + gpgsm_p12_export (ctrl, list->d, stream, + opt_raw? 2 : opt_pkcs12 ? 0 : 1); + else + gpgsm_export (ctrl, list, stream); + es_fclose (stream); + } + else + { + int fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); + estream_t out_fp; + + if (fd == -1) + { + free_strlist (list); + return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); + } + out_fp = es_fdopen_nc (fd, "w"); + if (!out_fp) + { + free_strlist (list); + return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); + } + + if (opt_secret) + gpgsm_p12_export (ctrl, list->d, out_fp, + opt_raw? 2 : opt_pkcs12 ? 0 : 1); + else + gpgsm_export (ctrl, list, out_fp); + es_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; + +#ifdef HAVE_W32CE_SYSTEM + sysfd = _assuan_w32ce_finish_pipe ((int)sysfd, 0); + if (sysfd == INVALID_HANDLE_VALUE) + return set_error (gpg_err_code_from_syserror (), + "rvid conversion failed"); +#endif + + 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 ) + { + free_strlist (list); + return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); + } + fp = es_fdopen_nc (outfd, "w"); + if (!fp) + { + free_strlist (list); + return set_error (gpg_err_code_from_syserror (), + "es_fdopen() failed"); + } + } + else + { + fp = es_fopencookie (ctx, "w", data_line_cookie_functions); + if (!fp) + { + free_strlist (list); + 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; + estream_t in_stream, out_stream; + 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); + + in_stream = es_fdopen_nc (inp_fd, "r"); + if (!in_stream) + return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen failed"); + + out_stream = es_fdopen_nc (out_fd, "w"); + if (!out_stream) + { + es_fclose (in_stream); + return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); + } + rc = gpgsm_genkey (ctrl, in_stream, out_stream); + es_fclose (out_stream); + es_fclose (in_stream); + + /* 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 formatted 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"); + /* Not needed: 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 (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.\n" + " offline - Returns OK if the connection is in offline mode."; +static gpg_error_t +cmd_getinfo (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc = 0; + + if (!strcmp (line, "version")) + { + const char *s = VERSION; + rc = assuan_send_data (ctx, s, strlen (s)); + } + else if (!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")) + { + 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_FALSE); + } + } + } + } + else if (!strcmp (line, "offline")) + { + rc = ctrl->offline? 0 : gpg_error (GPG_ERR_FALSE); + } + 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 (ctrl, line, NULL, &cert, 0); + 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 FILEDES in this case. */ +#ifdef HAVE_W32CE_SYSTEM + #define SERVER_STDIN es_fileno(es_stdin) + #define SERVER_STDOUT es_fileno(es_stdout) +#else +#define SERVER_STDIN 0 +#define SERVER_STDOUT 1 +#endif + filedes[0] = assuan_fdopen (SERVER_STDIN); + filedes[1] = assuan_fdopen (SERVER_STDOUT); + 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; + + /* Fixme: Use the really used socket name. */ + if (asprintf (&tmp, + "Home: %s\n" + "Config: %s\n" + "DirmngrInfo: %s\n" + "%s", + gnupg_homedir (), + opt.config_filename, + dirmngr_socket_name (), + 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; + + 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 + { + err = vprint_assuan_status_strings (ctrl->server_local->assuan_ctx, + get_status_string (no), arg_ptr); + } + + 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); +} + +gpg_error_t +gpgsm_status_with_error (ctrl_t ctrl, int no, const char *text, + gpg_error_t err) +{ + char buf[30]; + + snprintf (buf, sizeof buf, "%u", err); + 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..dd7612f --- /dev/null +++ b/sm/sign.c @@ -0,0 +1,828 @@ +/* sign.c - Sign a message + * Copyright (C) 2001, 2002, 2003, 2008, + * 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <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 "../common/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) +{ + estream_t fp; + char buffer[4096]; + int nread; + int rc = 0; + + fp = es_fdopen_nc (fd, "rb"); + if (!fp) + { + log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); + return -1; + } + + do + { + nread = es_fread (buffer, 1, DIM(buffer), fp); + gcry_md_write (md, buffer, nread); + } + while (nread); + if (es_ferror (fp)) + { + log_error ("read error on fd %d: %s\n", fd, strerror (errno)); + rc = -1; + } + es_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; + estream_t fp; + char buffer[4096]; + int nread; + int rc = 0; + int any = 0; + + fp = es_fdopen_nc (fd, "rb"); + if (!fp) + { + gpg_error_t tmperr = gpg_error_from_syserror (); + log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); + return tmperr; + } + + do + { + nread = es_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 (es_ferror (fp)) + { + rc = gpg_error_from_syserror (); + log_error ("read error on fd %d: %s\n", fd, strerror (errno)); + } + es_fclose (fp); + if (!any) + { + /* We can't allow signing 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 and an octet string of size 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 (); + if (!hd) + return gpg_error (GPG_ERR_GENERAL); + rc = keydb_search_first (ctrl, 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, 1)) + { + 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 (ctrl, 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 = classify_user_id (opt.local_user, &desc, 0); + if (rc) + { + log_error ("failed to find default signer: %s\n", gpg_strerror (rc)); + return NULL; + } + + kh = keydb_new (); + if (!kh) + return NULL; + + rc = keydb_search (ctrl, 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, estream_t out_fp) +{ + int i, rc; + gpg_error_t err; + gnupg_ksba_io_t 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 (); + if (!kh) + { + log_error (_("failed to allocate keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + if (!gnupg_rng_is_compliant (opt.compliance)) + { + rc = gpg_error (GPG_ERR_FORBIDDEN); + log_error (_("%s is not compliant with %s mode\n"), + "RNG", + gnupg_compliance_option_string (opt.compliance)); + gpgsm_status_with_error (ctrl, STATUS_ERROR, + "random-compliance", rc); + goto leave; + } + + ctrl->pem_name = "SIGNED MESSAGE"; + rc = gnupg_ksba_create_writer + (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) + | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), + ctrl->pem_name, out_fp, &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 ambiguous specification we will + check that the signer's certificate is usable and valid. */ + rc = gpgsm_cert_use_sign_p (cert, 0); + 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; + + /* Check compliance. */ + if (! gnupg_digest_is_allowed (opt.compliance, 1, cl->hash_algo)) + { + log_error (_("digest algorithm '%s' may not be used in %s mode\n"), + gcry_md_algo_name (cl->hash_algo), + gnupg_compliance_option_string (opt.compliance)); + err = gpg_error (GPG_ERR_DIGEST_ALGO); + goto leave; + } + + { + unsigned int nbits; + int pk_algo = gpgsm_get_key_algo_info (cl->cert, &nbits); + + if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_SIGNING, pk_algo, 0, + NULL, nbits, NULL)) + { + char kidstr[10+1]; + + snprintf (kidstr, sizeof kidstr, "0x%08lX", + gpgsm_get_short_fingerprint (cl->cert, NULL)); + log_error (_("key %s may not be used for signing in %s mode\n"), + kidstr, + gnupg_compliance_option_string (opt.compliance)); + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + goto leave; + } + } + } + + 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, 0); + 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. */ + if (!opt.no_chain_validation) + { + 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_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_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 = gnupg_ksba_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); + gnupg_ksba_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..5510f42 --- /dev/null +++ b/sm/verify.c @@ -0,0 +1,731 @@ +/* verify.c - Verify a messages signature + * Copyright (C) 2001, 2002, 2003, 2007, + * 2010 Free Software Foundation, Inc. + * Copyright (C) 2001-2019 Werner Koch + * Copyright (C) 2015-2020 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <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 "../common/i18n.h" +#include "../common/compliance.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; + estream_t fp; + char buffer[4096]; + int nread; + + fp = es_fdopen_nc (fd, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("fdopen(%d) failed: %s\n", fd, gpg_strerror (err)); + return err; + } + + do + { + nread = es_fread (buffer, 1, DIM(buffer), fp); + gcry_md_write (md, buffer, nread); + } + while (nread); + if (es_ferror (fp)) + { + err = gpg_error_from_syserror (); + log_error ("read error on fd %d: %s\n", fd, gpg_strerror (err)); + } + es_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, estream_t out_fp) +{ + int i, rc; + gnupg_ksba_io_t b64reader = NULL; + gnupg_ksba_io_t 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; + estream_t in_fp = NULL; + char *p; + + audit_set_type (ctrl->audit, AUDIT_TYPE_VERIFY); + + kh = keydb_new (); + if (!kh) + { + log_error (_("failed to allocate keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + + in_fp = es_fdopen_nc (in_fd, "rb"); + if (!in_fp) + { + rc = gpg_error_from_syserror (); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + rc = gnupg_ksba_create_reader + (&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0) + | (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0) + | (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0)), + in_fp, &reader); + if (rc) + { + log_error ("can't create reader: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (out_fp) + { + rc = gnupg_ksba_create_writer + (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) + | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), + ctrl->pem_name, out_fp, &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_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 = gnupg_ksba_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 (ctrl, cert, 0, NULL)); + ksba_cert_release (cert); + } + + cert = NULL; + for (signer=0; ; signer++) + { + char *issuer = NULL; + gcry_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 nbits; + int pkalgo; + char *pkalgostr = NULL; + char *pkfpr = NULL; + unsigned int pkalgoflags, 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) + { + 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); + if (! gcry_md_is_enabled (data_md, algo)) + { + 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 = gpgsm_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 = gpgsm_get_hash_algo_from_sigval (sigval, &pkalgoflags); + if (DBG_X509) + { + log_debug ("signer %d - signature available (sigval hash=%d pkaf=%u)", + signer, sigval_hash_algo, pkalgoflags); + } + 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 (ctrl, 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; + } + + pkfpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + pkalgostr = gpgsm_pubkey_algo_string (cert, NULL); + pkalgo = gpgsm_get_key_algo_info (cert, &nbits); + + log_info (_("Signature made ")); + if (*sigtime) + { + /* We take the freedom as noted in RFC3339 to use a space + * instead of the "T" delimiter between date and time. We + * also append a separate UTC instead of a "Z" or "+00:00" + * suffix because that makes it clear to everyone what kind + * of time this is. */ + dump_isotime (sigtime); + log_printf (" UTC"); + } + else + log_printf (_("[date not given]")); + log_info (_(" using %s key %s\n"), pkalgostr, pkfpr); + if (opt.verbose) + { + log_info (_("algorithm:")); + log_printf (" %s + %s", + pubkey_algo_to_string (pkalgo), + gcry_md_algo_name (sigval_hash_algo)); + if (algo != sigval_hash_algo) + log_printf (" (%s)", gcry_md_algo_name (algo)); + log_printf ("\n"); + } + + audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo); + + /* Check compliance. */ + if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_VERIFICATION, + pkalgo, pkalgoflags, NULL, nbits, NULL)) + { + char kidstr[10+1]; + + snprintf (kidstr, sizeof kidstr, "0x%08lX", + gpgsm_get_short_fingerprint (cert, NULL)); + log_error (_("key %s may not be used for signing in %s mode\n"), + kidstr, + gnupg_compliance_option_string (opt.compliance)); + goto next_signer; + } + + if (!gnupg_digest_is_allowed (opt.compliance, 0, sigval_hash_algo)) + { + log_error (_("digest algorithm '%s' may not be used in %s mode\n"), + gcry_md_algo_name (sigval_hash_algo), + gnupg_compliance_option_string (opt.compliance)); + goto next_signer; + } + + /* Check compliance with CO_DE_VS. */ + if (gnupg_pk_is_compliant (CO_DE_VS, pkalgo, pkalgoflags, + NULL, nbits, NULL) + && gnupg_gcrypt_is_compliant (CO_DE_VS) + && gnupg_digest_is_compliant (CO_DE_VS, sigval_hash_algo)) + gpgsm_status (ctrl, STATUS_VERIFICATION_COMPLIANCE_MODE, + gnupg_status_compliance_flag (CO_DE_VS)); + else if (opt.require_compliance + && opt.compliance == CO_DE_VS) + { + log_error (_("operation forced to fail due to" + " unfulfilled compliance rules\n")); + gpgsm_errors_seen = 1; + } + + + /* Now we can check the signature. */ + 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 + || 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 (msgdigest, msgdigestlen, "message: "); + if (s) + log_printhex (s, gcry_md_get_algo_dlen (algo), + "computed: "); + } + 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_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, + pkalgoflags, &info_pkalgo); + gcry_md_close (md); + } + else + { + rc = gpgsm_check_cms_signature (cert, sigval, data_md, + algo, pkalgoflags, &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_es_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_STEED)? + "0 steed": + (verifyflags & VALIDATE_FLAG_CHAIN_MODEL)? + "0 chain": "0 shell"); + + next_signer: + rc = 0; + xfree (issuer); + xfree (serial); + gcry_sexp_release (sigval); + xfree (msgdigest); + xfree (pkalgostr); + xfree (pkfpr); + ksba_cert_release (cert); + cert = NULL; + } + rc = 0; + + leave: + ksba_cms_release (cms); + gnupg_ksba_destroy_reader (b64reader); + gnupg_ksba_destroy_writer (b64writer); + keydb_release (kh); + gcry_md_close (data_md); + es_fclose (in_fp); + + if (rc) + { + char numbuf[50]; + sprintf (numbuf, "%d", rc ); + gpgsm_status2 (ctrl, STATUS_ERROR, "verify.leave", + numbuf, NULL); + } + + return rc; +} |