summaryrefslogtreecommitdiffstats
path: root/sm
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
commiteee068778cb28ecf3c14e1bf843a95547d72c42d (patch)
tree0e07b30ddc5ea579d682d5dbe57998200d1c9ab7 /sm
parentInitial commit. (diff)
downloadgnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.tar.xz
gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.zip
Adding upstream version 2.2.40.upstream/2.2.40upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sm')
-rw-r--r--sm/ChangeLog-20112968
-rw-r--r--sm/Makefile.am73
-rw-r--r--sm/Makefile.in907
-rw-r--r--sm/call-agent.c1455
-rw-r--r--sm/call-dirmngr.c1099
-rw-r--r--sm/certchain.c2380
-rw-r--r--sm/certcheck.c616
-rw-r--r--sm/certdump.c940
-rw-r--r--sm/certlist.c618
-rw-r--r--sm/certreqgen-ui.c473
-rw-r--r--sm/certreqgen.c1393
-rw-r--r--sm/decrypt.c1166
-rw-r--r--sm/delete.c182
-rw-r--r--sm/encrypt.c589
-rw-r--r--sm/export.c756
-rw-r--r--sm/fingerprint.c380
-rw-r--r--sm/gpgsm-w32info.rc52
-rw-r--r--sm/gpgsm.c2262
-rw-r--r--sm/gpgsm.h467
-rw-r--r--sm/gpgsm.w32-manifest.in18
-rw-r--r--sm/import.c941
-rw-r--r--sm/keydb.c1322
-rw-r--r--sm/keydb.h79
-rw-r--r--sm/keylist.c1686
-rw-r--r--sm/minip12.c3048
-rw-r--r--sm/minip12.h42
-rw-r--r--sm/misc.c308
-rw-r--r--sm/passphrase.c90
-rw-r--r--sm/passphrase.h27
-rw-r--r--sm/qualified.c325
-rw-r--r--sm/server.c1508
-rw-r--r--sm/sign.c828
-rw-r--r--sm/verify.c731
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;
+}