summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRepo Admin <nobody@gnupg.org>2003-08-05 19:11:04 +0200
committerRepo Admin <nobody@gnupg.org>2003-08-05 19:11:04 +0200
commit9ca4830a5b8392bc7bb01211407c876fd40b49d4 (patch)
tree5b5eb60b48cea2f86d01b8d9a458a2fa4fc0a2b2
parentminor changes to make make distcheck happy (diff)
downloadgnupg2-9ca4830a5b8392bc7bb01211407c876fd40b49d4.tar.xz
gnupg2-9ca4830a5b8392bc7bb01211407c876fd40b49d4.zip
This commit was manufactured by cvs2svn to create branch
'GNUPG-1-9-BRANCH'.
-rw-r--r--agent/ChangeLog565
-rw-r--r--agent/Makefile.am62
-rw-r--r--agent/agent.h226
-rw-r--r--agent/call-scd.c661
-rw-r--r--agent/command.c782
-rw-r--r--agent/divert-scd.c319
-rw-r--r--agent/findkey.c359
-rw-r--r--agent/genkey.c240
-rw-r--r--agent/gpg-agent.c1063
-rw-r--r--agent/learncard.c448
-rw-r--r--agent/minip12.c1140
-rw-r--r--agent/minip12.h33
-rw-r--r--agent/pkdecrypt.c138
-rw-r--r--agent/pksign.c185
-rw-r--r--agent/protect-tool.c977
-rw-r--r--agent/protect.c971
-rw-r--r--agent/simple-pwquery.c486
-rw-r--r--common/ChangeLog219
-rw-r--r--common/Makefile.am58
-rw-r--r--common/README11
-rw-r--r--common/errors.h110
-rw-r--r--common/gettime.c250
-rw-r--r--common/iobuf.c2415
-rw-r--r--common/iobuf.h170
-rw-r--r--common/maperror.c157
-rw-r--r--common/membuf.c89
-rw-r--r--common/membuf.h41
-rw-r--r--common/miscellaneous.c126
-rw-r--r--common/simple-pwquery.c486
-rw-r--r--common/simple-pwquery.h69
-rw-r--r--common/ttyio.c508
-rw-r--r--common/ttyio.h40
-rw-r--r--common/util.h120
-rw-r--r--common/yesno.c96
-rw-r--r--kbx/Makefile.am52
-rw-r--r--kbx/kbxutil.c339
-rw-r--r--kbx/keybox-blob.c1008
-rw-r--r--scd/ChangeLog242
-rw-r--r--scd/Makefile.am72
-rw-r--r--scd/apdu.c558
-rw-r--r--scd/apdu.h73
-rw-r--r--scd/app-common.h128
-rw-r--r--scd/app-openpgp.c1482
-rw-r--r--scd/app.c278
-rw-r--r--scd/card-common.h73
-rw-r--r--scd/card-p15.c502
-rw-r--r--scd/card.c564
-rw-r--r--scd/command.c1034
-rw-r--r--scd/iso7816.c371
-rw-r--r--scd/iso7816.h56
-rw-r--r--scd/sc-copykeys.c731
-rw-r--r--scd/sc-investigate.c209
-rw-r--r--scd/scdaemon.c638
-rw-r--r--scd/scdaemon.h127
-rw-r--r--sm/ChangeLog816
-rw-r--r--sm/Makefile.am55
-rw-r--r--sm/call-agent.c713
-rw-r--r--sm/call-dirmngr.c632
-rw-r--r--sm/certchain.c793
-rw-r--r--sm/certcheck.c300
-rw-r--r--sm/certdump.c457
-rw-r--r--sm/certlist.c315
-rw-r--r--sm/certreqgen.c699
-rw-r--r--sm/decrypt.c506
-rw-r--r--sm/delete.c165
-rw-r--r--sm/encrypt.c550
-rw-r--r--sm/export.c249
-rw-r--r--sm/fingerprint.c271
-rw-r--r--sm/gpgsm.c1458
-rw-r--r--sm/gpgsm.h274
-rw-r--r--sm/import.c349
-rw-r--r--sm/keydb.c1282
-rw-r--r--sm/keylist.c617
-rw-r--r--sm/server.c1070
-rw-r--r--sm/sign.c621
-rw-r--r--sm/verify.c550
76 files changed, 34899 insertions, 0 deletions
diff --git a/agent/ChangeLog b/agent/ChangeLog
new file mode 100644
index 000000000..10f4d45fa
--- /dev/null
+++ b/agent/ChangeLog
@@ -0,0 +1,565 @@
+2003-07-31 Werner Koch <wk@gnupg.org>
+
+ * Makefile.am (gpg_agent_LDADD): Added INTLLIBS.
+ (gpg_protect_tool_SOURCES): Added simple-pwquery.[ch]
+
+2003-07-27 Werner Koch <wk@gnupg.org>
+
+ Adjusted for gcry_mpi_print and gcry_mpi_scan API change.
+
+2003-07-15 Werner Koch <wk@gnupg.org>
+
+ * simple-pwquery.c, simple-pwquery.h: Moved to ../common.
+ * Makefile.am (gpg_protect_tool_LDADD): Add simple-pwquery.o.
+ Removed it from xx_SOURCES.
+
+2003-07-04 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (handle_connections): Kludge to allow use of Pth 1
+ and 2.
+
+2003-06-30 Werner Koch <wk@gnupg.org>
+
+ * call-scd.c (learn_status_cb): Store the serialno in PARM.
+
+2003-06-26 Werner Koch <wk@gnupg.org>
+
+ * call-scd.c (agent_card_serialno): Don't do a RESET anymore.
+
+2003-06-25 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_scd): New.
+ * call-scd.c (agent_card_scd): New.
+ * divert-scd.c (divert_generic_cmd): New
+
+ * call-scd.c (agent_card_learn): New callback args SINFO.
+ (learn_status_cb): Pass all other status lines to the sinfo
+ callback.
+ * learncard.c (release_sinfo, sinfo_cb): New.
+ (agent_handle_learn): Pass the new cb to the learn function and
+ pass the collected information back to the client's assuan
+ connection.
+
+ * gpg-agent.c (main): Moved pth_init before gcry_check_version.
+
+2003-06-24 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (handle_connections): Adjusted for Pth 2.0
+
+ Adjusted for changes in the libgcrypt API. Some more fixes for the
+ libgpg-error stuff.
+
+2003-06-04 Werner Koch <wk@gnupg.org>
+
+ Renamed error codes from INVALID to INV and removed _ERROR suffixes.
+
+2003-06-03 Werner Koch <wk@gnupg.org>
+
+ Changed all error codes in all files to the new libgpg-error scheme.
+
+ * agent.h: Include gpg-error.h and errno.h
+ * Makefile.am: Link with libgpg-error
+
+ * query.c: assuan.h is now a system header.
+ * genkey.c (agent_genkey): Fixed silly use of xmalloc by
+ xtrymalloc.
+
+2003-04-29 Werner Koch <wk@gnupg.org>
+
+ * command.c (register_commands): Adjusted for new Assuan semantics.
+
+ * Makefile.am: Don't override LDFLAGS.
+
+2002-12-04 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c: New variable config_filename.
+ (parse_rereadable_options): New.
+ (main): Use it here. Add setting of default values, set
+ config_filename.
+ (reread_configuration): Filled with actual code.
+
+2002-12-03 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c (read_key): Don't run make_canonical on a NULL
+ buffer.
+
+ * command.c (parse_hexstring): New.
+ (cmd_sethash): Use it.
+ (parse_keygrip): New.
+ (cmd_havekey, cmd_sigkey): Use it.
+ (cmd_passwd): New.
+ * genkey.c (agent_protect_and_store): New.
+ (store_key): Add arg FORCE.
+ (agent_genkey): Pass false to this force of store_key.
+
+2002-11-13 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): Switch all messages to utf-8.
+
+ * simple-pwquery.c (agent_send_all_options): Use $GPG_TTY and
+ stdin with ttyname.
+
+ * cache.c (new_data): Uiih - /sizeof d/sizeof *d/.
+
+2002-11-10 Werner Koch <wk@gnupg.org>
+
+ * command.c (option_handler): Fix keep_tty check.
+
+2002-11-06 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): Make sure we have a default ttyname.
+ * command.c (option_handler): Check opt.keep_tty here
+ * query.c (start_pinentry): but not anymore here.
+
+2002-11-05 Werner Koch <wk@gnupg.org>
+
+ * agent.h (opt,server_control_s): Move display and lc_ variables
+ to the control struct so that they are per connection.
+ * gpg-agent.c (agent_init_default_ctrl): New.
+ (main): Assign those command line options to new default_* variables.
+ Reset DISPLAY in server mode so that tehre is no implicit default.
+ * command.c (start_command_handler): Initialize and deinitialize
+ the control values.
+ (option_handler): Work on the ctrl values and not on the opt.
+ * query.c (start_pinentry): New argument CTRL to set the display
+ connection specific. Changed all callers to pass this value.
+ (agent_askpin,agent_get_passphrase,agent_get_confirmation): Add
+ CTRL arg and pass it ot start_pinentry.
+ * command.c (cmd_get_passphrase): Pass CTRL argument.
+ * trustlist.c (agent_marktrusted): Add CTRL argument
+ * command.c (cmd_marktrusted): Pass CTRL argument
+ * divert-scd.c (ask_for_card): Add CTRL arg.
+ (divert_pksign,divert_pkdecrypt): Ditto. Changed caller.
+ (getpin_cb): Use OPAQUE to pass the CTRL variable. Changed both
+ users.
+ * findkey.c (unprotect): Add CTRL arg.
+ (agent_key_from_file): Ditto.
+
+ * query.c (unlock_pinentry): Disconnect the pinentry so that we
+ start a new one for each request. This is required to support
+ clients with different environments (e.g. X magic cookies).
+
+2002-09-05 Neal H. Walfield <neal@cs.uml.edu>
+
+ * gpg-agent.c (main) [USE_GNU_PTH]: No need to call
+ assuan_set_io_func as assuan is smart.
+
+2002-09-25 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (handle_signal): Flush cache on SIGHUP.
+ * cache.c (agent_flush_cache): New.
+
+ * gpg-agent.c, agent.h: Add --keep-display and --keep-tty.
+ * query.c (start_pinentry): Implement them. The option passing
+ needs more thoughts.
+
+2002-09-09 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (create_private_keys_directory)
+ (create_directories): New.
+ (main): Try to create a home directory.
+
+2002-09-04 Neal H. Walfield <neal@g10code.de>
+
+ * gpg-agent.c (main): Use sigaction, not signal.
+
+2002-09-03 Neal H. Walfield <neal@g10code.de>
+
+ * findkey.c: Include <fcntl.h>.
+ (agent_write_private_key): Prefer POSIX compatibity, open and
+ fdopen, over the simplicity of GNU extensions, fopen(file, "x").
+
+2002-08-22 Werner Koch <wk@gnupg.org>
+
+ * query.c (agent_askpin): Provide the default desc text depending
+ on the pininfo. Do the basic PIN verification only when
+ min_digits is set.
+
+2002-08-21 Werner Koch <wk@gnupg.org>
+
+ * query.c (agent_askpin): Hack to show the right default prompt.
+ (agent_get_passphrase): Ditto.
+
+ * trans.c: Removed and replaced all usages with standard _()
+
+ * divert-scd.c (getpin_cb): Pass a more descritive text to the
+ pinentry.
+
+ * Makefile.am: Renamed the binary protect-tool to gpg-protect-tool.
+ * protect-tool.c: Removed the note about internal use only.
+
+ * gpg-agent.c (main): New option --daemon so that the program is
+ not accidently started in the background.
+
+2002-08-16 Werner Koch <wk@gnupg.org>
+
+ * call-scd.c (learn_status_cb): Handle CERTINFO status.
+ (agent_card_learn): Add args for certinfo cb.
+ * learncard.c (release_certinfo,certinfo_cb): New.
+ (send_cert_back): New. With factored out code from ..
+ (agent_handle_learn): here. Return certinfo stuff.
+
+2002-07-26 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): New option --ignore-cache-for-signing.
+ * command.c (option_handler): New server option
+ use-cache-for-signing defaulting to true.
+ (cmd_pksign): handle global and per session option.
+ * findkey.c (agent_key_from_file, unprotect): New arg
+ ignore_cache. Changed all callers.
+ * pksign.c (agent_pksign): Likewise.
+
+2002-06-29 Werner Koch <wk@gnupg.org>
+
+ * query.c (start_pinentry): Use GNUPG_DERAULT_PINENTRY.
+ * call-scd.c (start_scd): Use GNUPG_DEFAULT_SCDAEMON.
+
+2002-06-28 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c (export_p12_file): New.
+ (main): New command --p12-export.
+ * minip12.c (create_final,p12_build,compute_tag_length): New.
+ (store_tag_length): New.
+
+2002-06-27 Werner Koch <wk@gnupg.org>
+
+ * minip12.c (crypt_block): Renamed from decrypt_block, add arg to
+ allow encryption.
+
+ * Makefile.am (pkglib_PROGRAMS): Put protect-tool there.
+
+ * findkey.c (agent_write_private_key,agent_key_from_file)
+ (agent_key_available): Use GNUPG_PRIVATE_KEYS_DIR constant.
+ * gpg-agent.c (main): Use GNUPG_DEFAULT_HOMEDIR constant.
+
+ * protect-tool.c (store_private_key): New.
+ (import_p12_file): Store the new file if requested.
+ (main): New options --force and --store.
+
+ * gpg-agent.c (main): Set a global flag when running detached.
+ * query.c (start_pinentry): Pass the list of FD to keep in the
+ child when not running detached.
+ * call-scd.c (start_scd): Ditto.
+
+2002-06-26 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_istrusted, cmd_listtrusted, cmd_marktrusted)
+ (cmd_pksign, cmd_pkdecrypt, cmd_genkey, cmd_get_passphrase)
+ (cmd_learn): Print an error message for a failed operation.
+
+ * simple-pwquery.c, simple-pwquery.h: New.
+ * protect-tool. (get_passphrase): New, used to get a passphrase
+ from the agent if none was given on the command line.
+
+2002-06-25 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c (rsa_key_check): New.
+ (import_p12_file): New.
+ (main): New command --p12-import.
+ * minip12.c, minip12.h: New.
+
+2002-06-24 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c (read_file): New.
+ (read_key): Factored most code out to read_file.
+
+2002-06-17 Werner Koch <wk@gnupg.org>
+
+ * agent.h: Add a callback function to the pin_entry_info structure.
+ * query.c (agent_askpin): Use the callback to check for a correct
+ PIN. Removed the start_err_text argument because it is not
+ anymore needed; changed callers.
+ * findkey.c (unprotect): Replace our own check loop by a callback.
+ (try_unprotect_cb): New.
+ * genkey.c (reenter_compare_cb): New.
+ (agent_genkey): Use this callback here. Fixed setting of the pi2
+ variable and a segv in case of an empty PIN.
+
+ * divert-scd.c (getpin_cb): Removed some unused stuff and
+ explained what we still have to change.
+
+2002-06-12 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): New option --disable-pth.
+
+2002-06-11 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c: Add command --show-keygrip
+ (show_keygrip): New.
+
+2002-05-23 Werner Koch <wk@gnupg.org>
+
+ * call-scd.c: Seirialized all scdaeom access when using Pth.
+
+ * cache.c: Made the cache Pth-thread-safe.
+ (agent_unlock_cache_entry): New.
+ * findkey.c (unprotect): Unlock the returned cache value.
+ * command.c (cmd_get_passphrase): Ditto.
+
+ * gpg-agent.c (main): Register pth_read/write with Assuan.
+
+2002-05-22 Werner Koch <wk@gnupg.org>
+
+ * query.c: Serialized all pinentry access when using Pth.
+
+ * gpg-agent.c (handle_signal,start_connection_thread)
+ (handle_connections): New
+ (main): Use the new Pth stuff to allow concurrent connections.
+ * command.c (start_command_handler): Add new arg FD so that the
+ fucntion can also be used for an already connected socket.
+ * Makefile.am: Link with Pth.
+
+2002-05-14 Werner Koch <wk@gnupg.org>
+
+ * cache.c (housekeeping, agent_put_cache): Use our time() wrapper.
+
+2002-04-26 Werner Koch <wk@gnupg.org>
+
+ * cache.c (agent_put_cache): Reinitialize the creation time and
+ the ttl when reusing a slot.
+
+ * call-scd.c (start_scd): Print debug messages only with debug
+ flags set.
+ * query.c (start_pinentry): Ditto.
+
+2002-04-25 Marcus Brinkmann <marcus@g10code.de>
+
+ * agent.h (agent_get_confirmation): Replace paramter prompt with
+ two parameters ok and cancel.
+ * query.c (agent_get_confirmation): Likewise. Implement this.
+ * trustlist.c (agent_marktrusted): Fix invocation of
+ agent_get_confirmation.
+ * divert-scd.c (ask_for_card): Likewise.
+
+2002-04-24 Marcus Brinkmann <marcus@g10code.de>
+
+ * agent.h (struct opt): Add members display, ttyname, ttytype,
+ lc_ctype, and lc_messages.
+ * gpg-agent.c (enum cmd_and_opt_values): Add oDisplay, oTTYname,
+ oTTYtype, oLCctype, and LCmessages.
+ (main): Handle these options.
+ * command.c (option_handler): New function.
+ (register_commands): Register option handler.
+ * query.c (start_pinentry): Pass the various display and tty
+ options to the pinentry.
+
+2002-04-05 Werner Koch <wk@gnupg.org>
+
+ * protect-tool.c (show_file): New. Used as default action.
+
+2002-03-28 Werner Koch <wk@gnupg.org>
+
+ * divert-scd.c (encode_md_for_card): Don't do the pkcs-1 padding,
+ the scdaemon should take care of it.
+ (ask_for_card): Hack to not display the trailing zero.
+
+2002-03-11 Werner Koch <wk@gnupg.org>
+
+ * learncard.c (kpinfo_cb): Remove the content restrictions from
+ the keyID.
+
+2002-03-06 Werner Koch <wk@gnupg.org>
+
+ * learncard.c: New.
+ * divert-scd.c (ask_for_card): The serial number is binary so
+ convert it to hex here.
+ * findkey.c (agent_write_private_key): New.
+ * genkey.c (store_key): And use it here.
+
+ * pkdecrypt.c (agent_pkdecrypt): Changed the way the diversion is done.
+ * divert-scd.c (divert_pkdecrypt): Changed interface and
+ implemented it.
+
+2002-03-05 Werner Koch <wk@gnupg.org>
+
+ * call-scd.c (inq_needpin): New.
+ (agent_card_pksign): Add getpin_cb args.
+ (agent_card_pkdecrypt): New.
+
+2002-03-04 Werner Koch <wk@gnupg.org>
+
+ * pksign.c (agent_pksign): Changed how the diversion is done.
+ * divert-scd.c (divert_pksign): Changed interface and implemented it.
+ (encode_md_for_card): New.
+ * call-scd.c (agent_card_pksign): New.
+
+2002-02-28 Werner Koch <wk@gnupg.org>
+
+ * pksign.c (agent_pksign): Detect whether a Smartcard is to be
+ used and divert the operation in this case.
+ * pkdecrypt.c (agent_pkdecrypt): Likewise
+ * findkey.c (agent_key_from_file): Add optional arg shadow_info
+ and have it return information about a shadowed key.
+ * protect.c (agent_get_shadow_info): New.
+
+ * protect.c (snext,sskip,smatch): Moved to
+ * sexp-parse.h: new file.
+ * divert-scd.c: New.
+
+2002-02-27 Werner Koch <wk@gnupg.org>
+
+ * protect.c (agent_shadow_key): New.
+
+ * command.c (cmd_learn): New command LEARN.
+ * gpg-agent.c: New option --scdaemon-program.
+ * call-scd.c (start_scd): New. Based on query.c
+ * query.c: Add 2 more arguments to all uses of assuan_transact.
+
+2002-02-18 Werner Koch <wk@gnupg.org>
+
+ * findkey.c (unprotect): Show an error message for a bad passphrase.
+
+ * command.c (cmd_marktrusted): Implemented.
+ * trustlist.c (agent_marktrusted): New.
+ (open_list): Add APPEND arg.
+
+ * query.c (agent_get_confirmation): New.
+
+2002-02-06 Werner Koch <wk@gnupg.org>
+
+ * cache.c (housekeeping): Fixed linking in the remove case.
+
+2002-02-01 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c: New option --default-cache-ttl.
+ * cache.c (agent_put_cache): Use it.
+
+ * cache.c: Add a few debug outputs.
+
+ * protect.c (agent_private_key_type): New.
+ * agent.h: Add PRIVATE_KEY_ enums.
+ * findkey.c (agent_key_from_file): Use it to decide whether we
+ have to unprotect a key.
+ (unprotect): Cache the passphrase.
+
+ * findkey.c (agent_key_from_file,agent_key_available): The key
+ files do now require a ".key" suffix to make a script's life
+ easier.
+ * genkey.c (store_key): Ditto.
+
+2002-01-31 Werner Koch <wk@gnupg.org>
+
+ * genkey.c (store_key): Protect the key.
+ (agent_genkey): Ask for the passphrase.
+ * findkey.c (unprotect): Actually unprotect the key.
+ * query.c (agent_askpin): Add an optional start_err_text.
+
+2002-01-30 Werner Koch <wk@gnupg.org>
+
+ * protect.c: New.
+ (hash_passphrase): Based on the GnuPG 1.0.6 version.
+ * protect-tool.c: New
+
+2002-01-29 Werner Koch <wk@gnupg.org>
+
+ * findkey.c (agent_key_available): New.
+ * command.c (cmd_havekey): New.
+ (register_commands): And register new command.
+
+2002-01-20 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_get_passphrase): Remove the plus signs.
+
+ * query.c (start_pinentry): Send no-grab option to pinentry
+ * gpg-agent.c (main): Move variable grab as no_grab to agent.h.
+
+2002-01-19 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): Disable core dumps.
+
+ * cache.c: New.
+ * command.c (cmd_get_passphrase): Use the cache.
+ (cmd_clear_passphrase): Ditto.
+
+ * gpg-agent.c: Removed unused cruft and implement the socket
+ based server.
+ (my_strusage): Take bug report address from configure.ac.
+ * command.c (start_command_handler): Add an argument to start as
+ regular server.
+ (start_command_handler): Enable Assuan logging.
+
+2002-01-15 Werner Koch <wk@gnupg.org>
+
+ * trustlist.c: New.
+ * command.c (cmd_istrusted, cmd_listtrusted, cmd_marktrusted): New.
+
+2002-01-07 Werner Koch <wk@gnupg.org>
+
+ * genkey.c: Store the secret part and return the public part.
+
+2002-01-03 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_get_passphrase): New.
+ (cmd_clear_passphrase): New.
+ * query.c (agent_get_passphrase): New.
+
+2002-01-02 Werner Koch <wk@gnupg.org>
+
+ * genkey.c: New.
+ * command.c (cmd_genkey): New.
+
+ * command.c (rc_to_assuan_status): Removed and changed all callers
+ to use map_to_assuan_status.
+
+2001-12-19 Werner Koch <wk@gnupg.org>
+
+ * keyformat.txt: New.
+
+2001-12-19 Marcus Brinkmann <marcus@g10code.de>
+
+ * query.c (start_pinentry): Add new argument to assuan_pipe_connect.
+
+2001-12-18 Werner Koch <wk@gnupg.org>
+
+ * Makefile.am: Use LIBGCRYPT macros
+
+2001-12-14 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): New option --batch. New option --debug-wait
+ n, so that it is possible to attach gdb when used in server mode.
+ * query.c (agent_askpin): Don't ask in batch mode.
+
+ * command.c: Removed the conversion macros as they are now in
+ ../common/util.h.
+
+2001-12-14 Marcus Brinkmann <marcus@g10code.de>
+
+ * query.c (LINELENGTH): Removed.
+ (agent_askpin): Use ASSUAN_LINELENGTH, not LINELENGTH.
+
+2001-11-19 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c: Removed all GUI code, removed code for old
+ protocol. New code to use the Assuan protocol as a server and
+ also to communicate with a new ask-passphrase utility.
+
+2000-11-22 Werner Koch <wk@gnupg.org>
+
+ * gpg-agent.c (main): csh support by Dan Winship, new options --sh
+ and --csh and set default by consulting $SHELL.
+
+Mon Aug 21 17:59:17 CEST 2000 Werner Koch <wk@openit.de>
+
+ * gpg-agent.c (passphrase_dialog): Cleanup the window and added the
+ user supplied text to the window.
+ (main): Fixed segv in gtk_init when used without a command to start.
+
+ * gpg-agent.c: --flush option.
+ (req_flush): New.
+ (req_clear_passphrase): Implemented.
+
+Fri Aug 18 14:27:14 CEST 2000 Werner Koch <wk@openit.de>
+
+ * gpg-agent.c: New.
+ * Makefile.am: New.
+
+
+ Copyright 2001, 2002 Free Software Foundation, Inc.
+
+ This file is free software; as a special exception the author gives
+ unlimited permission to copy and/or distribute it, with or without
+ modifications, as long as this notice is preserved.
+
+ This file is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/agent/Makefile.am b/agent/Makefile.am
new file mode 100644
index 000000000..400aa2fd2
--- /dev/null
+++ b/agent/Makefile.am
@@ -0,0 +1,62 @@
+# Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+#
+# This file is part of GnuPG.
+#
+# GnuPG is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+## Process this file with automake to produce Makefile.in
+
+localedir = $(datadir)/locale
+INCLUDES = -I../intl -DLOCALEDIR=\"$(localedir)\"
+
+bin_PROGRAMS = gpg-agent
+pkglib_PROGRAMS = gpg-protect-tool
+
+AM_CPPFLAGS = -I$(top_srcdir)/common $(LIBGCRYPT_CFLAGS) \
+ $(LIBASSUAN_CFLAGS) $(PTH_CFLAGS)
+
+gpg_agent_SOURCES = \
+ gpg-agent.c agent.h \
+ command.c \
+ query.c \
+ cache.c \
+ trans.c \
+ findkey.c \
+ pksign.c \
+ pkdecrypt.c \
+ genkey.c \
+ protect.c \
+ trustlist.c \
+ divert-scd.c \
+ call-scd.c \
+ learncard.c \
+ sexp-parse.h
+
+
+gpg_agent_LDADD = ../jnlib/libjnlib.a ../common/libcommon.a \
+ $(LIBGCRYPT_LIBS) $(PTH_LIBS) $(LIBASSUAN_LIBS) \
+ -lgpg-error @INTLLIBS@
+
+gpg_protect_tool_SOURCES = \
+ protect-tool.c \
+ protect.c \
+ simple-pwquery.c simple-pwquery.h \
+ minip12.c minip12.h
+
+gpg_protect_tool_LDADD = ../jnlib/libjnlib.a \
+ ../common/libcommon.a ../common/libsimple-pwquery.a \
+ $(LIBGCRYPT_LIBS) -lgpg-error @INTLLIBS@
+
+
diff --git a/agent/agent.h b/agent/agent.h
new file mode 100644
index 000000000..eb4f4e32d
--- /dev/null
+++ b/agent/agent.h
@@ -0,0 +1,226 @@
+/* agent.h - Global definitions for the agent
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef AGENT_H
+#define AGENT_H
+
+#ifdef GPG_ERR_SOURCE_DEFAULT
+#error GPG_ERR_SOURCE_DEFAULT already defined
+#endif
+#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGAGENT
+#include <gpg-error.h>
+#include <errno.h>
+
+#include <gcrypt.h>
+#include "../common/util.h"
+#include "../common/errors.h"
+
+/* Convenience function to be used instead of returning the old
+ GNUPG_Out_Of_Core. */
+static __inline__ gpg_error_t
+out_of_core (void)
+{
+ return gpg_error (gpg_err_code_from_errno (errno));
+}
+
+#define MAX_DIGEST_LEN 24
+
+/* A large struct name "opt" to keep global flags */
+struct {
+ unsigned int debug; /* debug flags (DBG_foo_VALUE) */
+ int verbose; /* verbosity level */
+ int quiet; /* be as quiet as possible */
+ int dry_run; /* don't change any persistent data */
+ int batch; /* batch mode */
+ const char *homedir; /* configuration directory name */
+ const char *pinentry_program;
+ const char *scdaemon_program;
+ int no_grab; /* don't let the pinentry grab the keyboard */
+ unsigned long def_cache_ttl;
+
+ int running_detached; /* we are running detached from the tty. */
+
+ int ignore_cache_for_signing;
+ int keep_tty; /* don't switch the TTY (for pinentry) on request */
+ int keep_display; /* don't switch the DISPLAY (for pinentry) on request */
+} opt;
+
+
+#define DBG_COMMAND_VALUE 1 /* debug commands i/o */
+#define DBG_MPI_VALUE 2 /* debug mpi details */
+#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
+#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
+#define DBG_CACHE_VALUE 64 /* debug the caching */
+#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
+#define DBG_HASHING_VALUE 512 /* debug hashing operations */
+#define DBG_ASSUAN_VALUE 1024
+
+#define DBG_COMMAND (opt.debug & DBG_COMMAND_VALUE)
+#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
+#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE)
+#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
+#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
+#define DBG_ASSUAN (opt.debug & DBG_ASSUAN_VALUE)
+
+struct server_local_s;
+
+struct server_control_s {
+ struct server_local_s *server_local;
+ char *display;
+ char *ttyname;
+ char *ttytype;
+ char *lc_ctype;
+ char *lc_messages;
+ struct {
+ int algo;
+ unsigned char value[MAX_DIGEST_LEN];
+ int valuelen;
+ } digest;
+ char keygrip[20];
+ int have_keygrip;
+
+};
+typedef struct server_control_s *CTRL;
+
+
+struct pin_entry_info_s {
+ int min_digits; /* min. number of digits required or 0 for freeform entry */
+ int max_digits; /* max. number of allowed digits allowed*/
+ int max_tries;
+ int failed_tries;
+ int (*check_cb)(struct pin_entry_info_s *); /* CB used to check the PIN */
+ void *check_cb_arg; /* optional argument which might be of use in the CB */
+ const char *cb_errtext; /* used by the cb to displaye a specific error */
+ size_t max_length; /* allocated length of the buffer */
+ char pin[1];
+};
+
+
+enum {
+ PRIVATE_KEY_UNKNOWN = 0,
+ PRIVATE_KEY_CLEAR = 1,
+ PRIVATE_KEY_PROTECTED = 2,
+ PRIVATE_KEY_SHADOWED = 3
+};
+
+/*-- gpg-agent.c --*/
+void agent_exit (int rc); /* also implemented in other tools */
+void agent_init_default_ctrl (struct server_control_s *ctrl);
+
+/*-- command.c --*/
+void start_command_handler (int, int);
+
+/*-- findkey.c --*/
+int agent_write_private_key (const unsigned char *grip,
+ const void *buffer, size_t length, int force);
+gcry_sexp_t agent_key_from_file (CTRL ctrl, const unsigned char *grip,
+ unsigned char **shadow_info,
+ int ignore_cache);
+int agent_key_available (const unsigned char *grip);
+
+/*-- query.c --*/
+int agent_askpin (CTRL ctrl,
+ const char *desc_text, struct pin_entry_info_s *pininfo);
+int agent_get_passphrase (CTRL ctrl, char **retpass,
+ const char *desc, const char *prompt,
+ const char *errtext);
+int agent_get_confirmation (CTRL ctrl, const char *desc, const char *ok,
+ const char *cancel);
+
+/*-- cache.c --*/
+void agent_flush_cache (void);
+int agent_put_cache (const char *key, const char *data, int ttl);
+const char *agent_get_cache (const char *key, void **cache_id);
+void agent_unlock_cache_entry (void **cache_id);
+
+
+/*-- pksign.c --*/
+int agent_pksign (CTRL ctrl, FILE *outfp, int ignore_cache);
+
+/*-- pkdecrypt.c --*/
+int agent_pkdecrypt (CTRL ctrl, const char *ciphertext, size_t ciphertextlen,
+ FILE *outfp);
+
+/*-- genkey.c --*/
+int agent_genkey (CTRL ctrl,
+ const char *keyparam, size_t keyparmlen, FILE *outfp);
+int agent_protect_and_store (CTRL ctrl, gcry_sexp_t s_skey);
+
+/*-- protect.c --*/
+int agent_protect (const unsigned char *plainkey, const char *passphrase,
+ unsigned char **result, size_t *resultlen);
+int agent_unprotect (const unsigned char *protectedkey, const char *passphrase,
+ unsigned char **result, size_t *resultlen);
+int agent_private_key_type (const unsigned char *privatekey);
+int agent_shadow_key (const unsigned char *pubkey,
+ const unsigned char *shadow_info,
+ unsigned char **result);
+int agent_get_shadow_info (const unsigned char *shadowkey,
+ unsigned char const **shadow_info);
+
+
+/*-- trustlist.c --*/
+int agent_istrusted (const char *fpr);
+int agent_listtrusted (void *assuan_context);
+int agent_marktrusted (CTRL ctrl, const char *name, const char *fpr, int flag);
+
+
+/*-- divert-scd.c --*/
+int divert_pksign (CTRL ctrl,
+ const unsigned char *digest, size_t digestlen, int algo,
+ const unsigned char *shadow_info, unsigned char **r_sig);
+int divert_pkdecrypt (CTRL ctrl,
+ const unsigned char *cipher,
+ const unsigned char *shadow_info,
+ char **r_buf, size_t *r_len);
+int divert_generic_cmd (CTRL ctrl, const char *cmdline, void *assuan_context);
+
+
+/*-- call-scd.c --*/
+int agent_card_learn (void (*kpinfo_cb)(void*, const char *),
+ void *kpinfo_cb_arg,
+ void (*certinfo_cb)(void*, const char *),
+ void *certinfo_cb_arg,
+ void (*sinfo_cb)(void*, const char *,
+ size_t, const char *),
+ void *sinfo_cb_arg);
+int agent_card_serialno (char **r_serialno);
+int agent_card_pksign (const char *keyid,
+ int (*getpin_cb)(void *, const char *, char*, size_t),
+ void *getpin_cb_arg,
+ const unsigned char *indata, size_t indatalen,
+ char **r_buf, size_t *r_buflen);
+int agent_card_pkdecrypt (const char *keyid,
+ int (*getpin_cb)(void *, const char *, char*,size_t),
+ void *getpin_cb_arg,
+ const unsigned char *indata, size_t indatalen,
+ char **r_buf, size_t *r_buflen);
+int agent_card_readcert (const char *id, char **r_buf, size_t *r_buflen);
+int agent_card_readkey (const char *id, unsigned char **r_buf);
+int agent_card_scd (const char *cmdline,
+ int (*getpin_cb)(void *, const char *, char*, size_t),
+ void *getpin_cb_arg, void *assuan_context);
+
+
+/*-- learncard.c --*/
+int agent_handle_learn (void *assuan_context);
+
+
+#endif /*AGENT_H*/
diff --git a/agent/call-scd.c b/agent/call-scd.c
new file mode 100644
index 000000000..14487f1e3
--- /dev/null
+++ b/agent/call-scd.c
@@ -0,0 +1,661 @@
+/* call-scd.c - fork of the scdaemon to do SC operations
+ * Copyright (C) 2001, 2002 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+/* Fixme: For now we have serialized all access to the scdaemon which
+ make sense becuase the scdaemon can't handle concurrent connections
+ right now. We should however keep a list of connections and lock
+ just that connection - it migth make sense to implemtn parts of
+ this in Assuan.*/
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#ifdef USE_GNU_PTH
+# include <pth.h>
+#endif
+
+#include "agent.h"
+#include <assuan.h>
+
+#ifdef _POSIX_OPEN_MAX
+#define MAX_OPEN_FDS _POSIX_OPEN_MAX
+#else
+#define MAX_OPEN_FDS 20
+#endif
+
+static ASSUAN_CONTEXT scd_ctx = NULL;
+#ifdef USE_GNU_PTH
+static pth_mutex_t scd_lock = PTH_MUTEX_INIT;
+#endif
+
+/* callback parameter for learn card */
+struct learn_parm_s {
+ void (*kpinfo_cb)(void*, const char *);
+ void *kpinfo_cb_arg;
+ void (*certinfo_cb)(void*, const char *);
+ void *certinfo_cb_arg;
+ void (*sinfo_cb)(void*, const char *, size_t, const char *);
+ void *sinfo_cb_arg;
+};
+
+struct inq_needpin_s {
+ ASSUAN_CONTEXT ctx;
+ int (*getpin_cb)(void *, const char *, char*, size_t);
+ void *getpin_cb_arg;
+};
+
+struct membuf {
+ size_t len;
+ size_t size;
+ char *buf;
+ int out_of_core;
+};
+
+
+
+/* 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;
+}
+
+
+
+
+static int
+unlock_scd (int rc)
+{
+#ifdef USE_GNU_PTH
+ if (!pth_mutex_release (&scd_lock))
+ {
+ log_error ("failed to release the SCD lock\n");
+ if (!rc)
+ rc = gpg_error (GPG_ERR_INTERNAL);
+ }
+#endif
+ return rc;
+}
+
+/* Fork off the SCdaemon if this has not already been done */
+static int
+start_scd (void)
+{
+ int rc;
+ const char *pgmname;
+ ASSUAN_CONTEXT ctx;
+ const char *argv[3];
+ int no_close_list[3];
+ int i;
+
+#ifdef USE_GNU_PTH
+ if (!pth_mutex_acquire (&scd_lock, 0, NULL))
+ {
+ log_error ("failed to acquire the SCD lock\n");
+ return gpg_error (GPG_ERR_INTERNAL);
+ }
+#endif
+
+ if (scd_ctx)
+ return 0; /* No need to serialize things because the agent is
+ expected to tun as a single-thread (or may be in
+ future using libpth) */
+
+ if (opt.verbose)
+ log_info ("no running SCdaemon - starting it\n");
+
+ if (fflush (NULL))
+ {
+ gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("error flushing pending output: %s\n", strerror (errno));
+ return unlock_scd (tmperr);
+ }
+
+ if (!opt.scdaemon_program || !*opt.scdaemon_program)
+ opt.scdaemon_program = GNUPG_DEFAULT_SCDAEMON;
+ if ( !(pgmname = strrchr (opt.scdaemon_program, '/')))
+ pgmname = opt.scdaemon_program;
+ else
+ pgmname++;
+
+ argv[0] = pgmname;
+ argv[1] = "--server";
+ argv[2] = NULL;
+
+ i=0;
+ if (!opt.running_detached)
+ {
+ if (log_get_fd () != -1)
+ no_close_list[i++] = log_get_fd ();
+ no_close_list[i++] = fileno (stderr);
+ }
+ no_close_list[i] = -1;
+
+ /* connect to the pinentry and perform initial handshaking */
+ rc = assuan_pipe_connect (&ctx, opt.scdaemon_program, (char**)argv,
+ no_close_list);
+ if (rc)
+ {
+ log_error ("can't connect to the SCdaemon: %s\n",
+ assuan_strerror (rc));
+ return unlock_scd (gpg_error (GPG_ERR_NO_SCDAEMON));
+ }
+ scd_ctx = ctx;
+
+ if (DBG_ASSUAN)
+ log_debug ("connection to SCdaemon established\n");
+ return 0;
+}
+
+
+
+static AssuanError
+learn_status_cb (void *opaque, const char *line)
+{
+ struct learn_parm_s *parm = opaque;
+ const char *keyword = line;
+ int keywordlen;
+
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ while (spacep (line))
+ line++;
+ if (keywordlen == 8 && !memcmp (keyword, "CERTINFO", keywordlen))
+ {
+ parm->certinfo_cb (parm->certinfo_cb_arg, line);
+ }
+ else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
+ {
+ parm->kpinfo_cb (parm->kpinfo_cb_arg, line);
+ }
+ else if (keywordlen && *line)
+ {
+ parm->sinfo_cb (parm->sinfo_cb_arg, keyword, keywordlen, line);
+ }
+
+ return 0;
+}
+
+/* Perform the learn command and return a list of all private keys
+ stored on the card. */
+int
+agent_card_learn (void (*kpinfo_cb)(void*, const char *),
+ void *kpinfo_cb_arg,
+ void (*certinfo_cb)(void*, const char *),
+ void *certinfo_cb_arg,
+ void (*sinfo_cb)(void*, const char *, size_t, const char *),
+ void *sinfo_cb_arg)
+{
+ int rc;
+ struct learn_parm_s parm;
+
+ rc = start_scd ();
+ if (rc)
+ return rc;
+
+ memset (&parm, 0, sizeof parm);
+ parm.kpinfo_cb = kpinfo_cb;
+ parm.kpinfo_cb_arg = kpinfo_cb_arg;
+ parm.certinfo_cb = certinfo_cb;
+ parm.certinfo_cb_arg = certinfo_cb_arg;
+ parm.sinfo_cb = sinfo_cb;
+ parm.sinfo_cb_arg = sinfo_cb_arg;
+ rc = assuan_transact (scd_ctx, "LEARN --force",
+ NULL, NULL, NULL, NULL,
+ learn_status_cb, &parm);
+ if (rc)
+ return unlock_scd (map_assuan_err (rc));
+
+ return unlock_scd (0);
+}
+
+
+
+static AssuanError
+get_serialno_cb (void *opaque, const char *line)
+{
+ char **serialno = opaque;
+ const char *keyword = line;
+ const char *s;
+ int keywordlen, n;
+
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ while (spacep (line))
+ line++;
+
+ if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
+ {
+ if (*serialno)
+ return ASSUAN_Unexpected_Status;
+ for (n=0,s=line; hexdigitp (s); s++, n++)
+ ;
+ if (!n || (n&1)|| !(spacep (s) || !*s) )
+ return ASSUAN_Invalid_Status;
+ *serialno = xtrymalloc (n+1);
+ if (!*serialno)
+ return ASSUAN_Out_Of_Core;
+ memcpy (*serialno, line, n);
+ (*serialno)[n] = 0;
+ }
+
+ return 0;
+}
+
+/* Return the serial number of the card or an appropriate error. The
+ serial number is returned as a hexstring. */
+int
+agent_card_serialno (char **r_serialno)
+{
+ int rc;
+ char *serialno = NULL;
+
+ rc = start_scd ();
+ if (rc)
+ return rc;
+
+ /* Hmm, do we really need this reset - scddaemon should do this or
+ we can do this if we for some reason figure out that the
+ operation might have failed due to a missing RESET. Hmmm, I feel
+ this is really SCdaemon's duty */
+/* rc = assuan_transact (scd_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); */
+/* if (rc) */
+/* return unlock_scd (map_assuan_err (rc)); */
+
+ rc = assuan_transact (scd_ctx, "SERIALNO",
+ NULL, NULL, NULL, NULL,
+ get_serialno_cb, &serialno);
+ if (rc)
+ {
+ xfree (serialno);
+ return unlock_scd (map_assuan_err (rc));
+ }
+ *r_serialno = serialno;
+ return unlock_scd (0);
+}
+
+
+static AssuanError
+membuf_data_cb (void *opaque, const void *buffer, size_t length)
+{
+ struct membuf *data = opaque;
+
+ if (buffer)
+ put_membuf (data, buffer, length);
+ return 0;
+}
+
+/* Handle the NEEDPIN inquiry. */
+static AssuanError
+inq_needpin (void *opaque, const char *line)
+{
+ struct inq_needpin_s *parm = opaque;
+ char *pin;
+ size_t pinlen;
+ int rc;
+
+ if (!(!strncmp (line, "NEEDPIN", 7) && (line[7] == ' ' || !line[7])))
+ {
+ log_error ("unsupported inquiry `%s'\n", line);
+ return ASSUAN_Inquire_Unknown;
+ }
+ line += 7;
+
+ pinlen = 90;
+ pin = gcry_malloc_secure (pinlen);
+ if (!pin)
+ return ASSUAN_Out_Of_Core;
+
+ rc = parm->getpin_cb (parm->getpin_cb_arg, line, pin, pinlen);
+ if (rc)
+ rc = ASSUAN_Canceled;
+ if (!rc)
+ rc = assuan_send_data (parm->ctx, pin, pinlen);
+ xfree (pin);
+
+ return rc;
+}
+
+
+
+/* Create a signature using the current card */
+int
+agent_card_pksign (const char *keyid,
+ int (*getpin_cb)(void *, const char *, char*, size_t),
+ void *getpin_cb_arg,
+ const unsigned char *indata, size_t indatalen,
+ char **r_buf, size_t *r_buflen)
+{
+ int rc, i;
+ char *p, line[ASSUAN_LINELENGTH];
+ struct membuf data;
+ struct inq_needpin_s inqparm;
+ size_t len;
+ unsigned char *sigbuf;
+ size_t sigbuflen;
+
+ *r_buf = NULL;
+ rc = start_scd ();
+ if (rc)
+ return rc;
+
+ if (indatalen*2 + 50 > DIM(line))
+ return unlock_scd (gpg_error (GPG_ERR_GENERAL));
+
+ sprintf (line, "SETDATA ");
+ p = line + strlen (line);
+ for (i=0; i < indatalen ; i++, p += 2 )
+ sprintf (p, "%02X", indata[i]);
+ rc = assuan_transact (scd_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_scd (map_assuan_err (rc));
+
+ init_membuf (&data, 1024);
+ inqparm.ctx = scd_ctx;
+ inqparm.getpin_cb = getpin_cb;
+ inqparm.getpin_cb_arg = getpin_cb_arg;
+ snprintf (line, DIM(line)-1, "PKSIGN %s", keyid);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (scd_ctx, line,
+ membuf_data_cb, &data,
+ inq_needpin, &inqparm,
+ NULL, NULL);
+ if (rc)
+ {
+ xfree (get_membuf (&data, &len));
+ return unlock_scd (map_assuan_err (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)))" */
+ *r_buflen = 21 + 11 + sigbuflen + 4;
+ *r_buf = xtrymalloc (*r_buflen);
+ if (!*r_buf)
+ {
+ gpg_error_t tmperr = out_of_core ();
+ xfree (*r_buf);
+ return unlock_scd (tmperr);
+ }
+ p = stpcpy (*r_buf, "(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 unlock_scd (0);
+}
+
+/* Decipher INDATA using the current card. Note that the returned value is */
+int
+agent_card_pkdecrypt (const char *keyid,
+ int (*getpin_cb)(void *, const char *, char*, size_t),
+ void *getpin_cb_arg,
+ const unsigned char *indata, size_t indatalen,
+ char **r_buf, size_t *r_buflen)
+{
+ int rc, i;
+ char *p, line[ASSUAN_LINELENGTH];
+ struct membuf data;
+ struct inq_needpin_s inqparm;
+ size_t len;
+
+ *r_buf = NULL;
+ rc = start_scd ();
+ if (rc)
+ return rc;
+
+ /* FIXME: use secure memory where appropriate */
+ if (indatalen*2 + 50 > DIM(line))
+ return unlock_scd (gpg_error (GPG_ERR_GENERAL));
+
+ sprintf (line, "SETDATA ");
+ p = line + strlen (line);
+ for (i=0; i < indatalen ; i++, p += 2 )
+ sprintf (p, "%02X", indata[i]);
+ rc = assuan_transact (scd_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return unlock_scd (map_assuan_err (rc));
+
+ init_membuf (&data, 1024);
+ inqparm.ctx = scd_ctx;
+ inqparm.getpin_cb = getpin_cb;
+ inqparm.getpin_cb_arg = getpin_cb_arg;
+ snprintf (line, DIM(line)-1, "PKDECRYPT %s", keyid);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (scd_ctx, line,
+ membuf_data_cb, &data,
+ inq_needpin, &inqparm,
+ NULL, NULL);
+ if (rc)
+ {
+ xfree (get_membuf (&data, &len));
+ return unlock_scd (map_assuan_err (rc));
+ }
+ *r_buf = get_membuf (&data, r_buflen);
+ if (!*r_buf)
+ return unlock_scd (gpg_error (GPG_ERR_ENOMEM));
+
+ return unlock_scd (0);
+}
+
+
+
+/* Read a certificate with ID into R_BUF and R_BUFLEN. */
+int
+agent_card_readcert (const char *id, char **r_buf, size_t *r_buflen)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+ struct membuf data;
+ size_t len;
+
+ *r_buf = NULL;
+ rc = start_scd ();
+ if (rc)
+ return rc;
+
+ init_membuf (&data, 1024);
+ snprintf (line, DIM(line)-1, "READCERT %s", id);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (scd_ctx, line,
+ membuf_data_cb, &data,
+ NULL, NULL,
+ NULL, NULL);
+ if (rc)
+ {
+ xfree (get_membuf (&data, &len));
+ return unlock_scd (map_assuan_err (rc));
+ }
+ *r_buf = get_membuf (&data, r_buflen);
+ if (!*r_buf)
+ return unlock_scd (gpg_error (GPG_ERR_ENOMEM));
+
+ return unlock_scd (0);
+}
+
+
+
+/* Read a key with ID and return it in an allocate buffer pointed to
+ by r_BUF as a valid S-expression. */
+int
+agent_card_readkey (const char *id, unsigned char **r_buf)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+ struct membuf data;
+ size_t len, buflen;
+
+ *r_buf = NULL;
+ rc = start_scd ();
+ if (rc)
+ return rc;
+
+ init_membuf (&data, 1024);
+ snprintf (line, DIM(line)-1, "READKEY %s", id);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (scd_ctx, line,
+ membuf_data_cb, &data,
+ NULL, NULL,
+ NULL, NULL);
+ if (rc)
+ {
+ xfree (get_membuf (&data, &len));
+ return unlock_scd (map_assuan_err (rc));
+ }
+ *r_buf = get_membuf (&data, &buflen);
+ if (!*r_buf)
+ return unlock_scd (gpg_error (GPG_ERR_ENOMEM));
+
+ if (!gcry_sexp_canon_len (*r_buf, buflen, NULL, NULL))
+ {
+ xfree (*r_buf); *r_buf = NULL;
+ return unlock_scd (gpg_error (GPG_ERR_INV_VALUE));
+ }
+
+ return unlock_scd (0);
+}
+
+
+
+
+static AssuanError
+pass_status_thru (void *opaque, const char *line)
+{
+ ASSUAN_CONTEXT ctx = opaque;
+ char keyword[200];
+ int i;
+
+ for (i=0; *line && !spacep (line) && i < DIM(keyword)-1; line++, i++)
+ keyword[i] = *line;
+ keyword[i] = 0;
+ /* truncate any remaining keyword stuff. */
+ for (; *line && !spacep (line); line++)
+ ;
+ while (spacep (line))
+ line++;
+
+ assuan_write_status (ctx, keyword, line);
+ return 0;
+}
+
+static AssuanError
+pass_data_thru (void *opaque, const void *buffer, size_t length)
+{
+ ASSUAN_CONTEXT ctx = opaque;
+
+ assuan_send_data (ctx, buffer, length);
+ return 0;
+}
+
+
+/* Send the line CMDLINE with command for the SCDdaemon to it and send
+ all status messages back. This command is used as a general quoting
+ mechanism to pass everything verbatim to SCDAEMOPN. The PIN
+ inquirey is handled inside gpg-agent. */
+int
+agent_card_scd (const char *cmdline,
+ int (*getpin_cb)(void *, const char *, char*, size_t),
+ void *getpin_cb_arg, void *assuan_context)
+{
+ int rc;
+ struct inq_needpin_s inqparm;
+
+ rc = start_scd ();
+ if (rc)
+ return rc;
+
+ inqparm.ctx = scd_ctx;
+ inqparm.getpin_cb = getpin_cb;
+ inqparm.getpin_cb_arg = getpin_cb_arg;
+ rc = assuan_transact (scd_ctx, cmdline,
+ pass_data_thru, assuan_context,
+ inq_needpin, &inqparm,
+ pass_status_thru, assuan_context);
+ if (rc)
+ {
+ return unlock_scd (map_assuan_err (rc));
+ }
+
+ return unlock_scd (0);
+}
+
+
diff --git a/agent/command.c b/agent/command.c
new file mode 100644
index 000000000..ed4ea6b02
--- /dev/null
+++ b/agent/command.c
@@ -0,0 +1,782 @@
+/* command.c - gpg-agent command handler
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+/* FIXME: we should not use the default assuan buffering but setup
+ some buffering in secure mempory to protect session keys etc. */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#include <assuan.h>
+
+#include "agent.h"
+
+/* maximum allowed size of the inquired ciphertext */
+#define MAXLEN_CIPHERTEXT 4096
+/* maximum allowed size of the key parameters */
+#define MAXLEN_KEYPARAM 1024
+
+#define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t))
+
+
+#if MAX_DIGEST_LEN < 20
+#error MAX_DIGEST_LEN shorter than keygrip
+#endif
+
+/* Data used to associate an Assuan context with local server data */
+struct server_local_s {
+ ASSUAN_CONTEXT assuan_ctx;
+ int message_fd;
+ int use_cache_for_signing;
+};
+
+
+
+
+
+static void
+reset_notify (ASSUAN_CONTEXT ctx)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+
+ memset (ctrl->keygrip, 0, 20);
+ ctrl->have_keygrip = 0;
+ ctrl->digest.valuelen = 0;
+}
+
+
+/* Check whether the option NAME appears in LINE */
+static int
+has_option (const char *line, const char *name)
+{
+ const char *s;
+ int n = strlen (name);
+
+ s = strstr (line, name);
+ return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n)));
+}
+
+/* Parse a hex string. Return an Assuan error code or 0 on success and the
+ length of the parsed string in LEN. */
+static int
+parse_hexstring (ASSUAN_CONTEXT ctx, const char *string, size_t *len)
+{
+ const char *p;
+ size_t n;
+
+ /* parse the hash value */
+ for (p=string, n=0; hexdigitp (p); p++, n++)
+ ;
+ if (*p)
+ return set_error (Parameter_Error, "invalid hexstring");
+ if ((n&1))
+ return set_error (Parameter_Error, "odd number of digits");
+ *len = n;
+ return 0;
+}
+
+/* Parse the keygrip in STRING into the provided buffer BUF. BUF must
+ provide space for 20 bytes. BUF is not changed if the fucntions
+ returns an error. */
+static int
+parse_keygrip (ASSUAN_CONTEXT ctx, const char *string, unsigned char *buf)
+{
+ int rc;
+ size_t n;
+ const unsigned char *p;
+
+ rc = parse_hexstring (ctx, string, &n);
+ if (rc)
+ return rc;
+ n /= 2;
+ if (n != 20)
+ return set_error (Parameter_Error, "invalid length of keygrip");
+
+ for (p=string, n=0; n < 20; p += 2, n++)
+ buf[n] = xtoi_2 (p);
+
+ return 0;
+}
+
+
+
+
+/* ISTRUSTED <hexstring_with_fingerprint>
+
+ Return OK when we have an entry with this fingerprint in our
+ trustlist */
+static int
+cmd_istrusted (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc, n, i;
+ char *p;
+ char fpr[41];
+
+ /* parse the fingerprint value */
+ for (p=line,n=0; hexdigitp (p); p++, n++)
+ ;
+ if (*p || !(n == 40 || n == 32))
+ return set_error (Parameter_Error, "invalid fingerprint");
+ i = 0;
+ if (n==32)
+ {
+ strcpy (fpr, "00000000");
+ i += 8;
+ }
+ for (p=line; i < 40; p++, i++)
+ fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
+ fpr[i] = 0;
+ rc = agent_istrusted (fpr);
+ if (!rc)
+ return 0;
+ else if (rc == -1)
+ return ASSUAN_Not_Trusted;
+ else
+ {
+ log_error ("command is_trusted failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+ }
+}
+
+/* LISTTRUSTED
+
+ List all entries from the trustlist */
+static int
+cmd_listtrusted (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc = agent_listtrusted (ctx);
+ if (rc)
+ log_error ("command listtrusted failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+/* MARKTRUSTED <hexstring_with_fingerprint> <flag> <display_name>
+
+ Store a new key in into the trustlist*/
+static int
+cmd_marktrusted (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc, n, i;
+ char *p;
+ char fpr[41];
+ int flag;
+
+ /* parse the fingerprint value */
+ for (p=line,n=0; hexdigitp (p); p++, n++)
+ ;
+ if (!spacep (p) || !(n == 40 || n == 32))
+ return set_error (Parameter_Error, "invalid fingerprint");
+ i = 0;
+ if (n==32)
+ {
+ strcpy (fpr, "00000000");
+ i += 8;
+ }
+ for (p=line; i < 40; p++, i++)
+ fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
+ fpr[i] = 0;
+
+ while (spacep (p))
+ p++;
+ flag = *p++;
+ if ( (flag != 'S' && flag != 'P') || !spacep (p) )
+ return set_error (Parameter_Error, "invalid flag - must be P or S");
+ while (spacep (p))
+ p++;
+
+ rc = agent_marktrusted (ctrl, p, fpr, flag);
+ if (rc)
+ log_error ("command marktrusted failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+
+
+/* HAVEKEY <hexstring_with_keygrip>
+
+ Return success when the secret key is available */
+static int
+cmd_havekey (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc;
+ unsigned char buf[20];
+
+ rc = parse_keygrip (ctx, line, buf);
+ if (rc)
+ return rc;
+
+ if (agent_key_available (buf))
+ return ASSUAN_No_Secret_Key;
+
+ return 0;
+}
+
+
+/* SIGKEY <hexstring_with_keygrip>
+ SETKEY <hexstring_with_keygrip>
+
+ Set the key used for a sign or decrypt operation */
+static int
+cmd_sigkey (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc;
+ CTRL ctrl = assuan_get_pointer (ctx);
+
+ rc = parse_keygrip (ctx, line, ctrl->keygrip);
+ if (rc)
+ return rc;
+ ctrl->have_keygrip = 1;
+ return 0;
+}
+
+
+/* SETHASH <algonumber> <hexstring>
+
+ The client can use this command to tell the server about the data
+ (which usually is a hash) to be signed. */
+static int
+cmd_sethash (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc;
+ size_t n;
+ char *p;
+ CTRL ctrl = assuan_get_pointer (ctx);
+ unsigned char *buf;
+ char *endp;
+ int algo;
+
+ /* parse the algo number and check it */
+ algo = (int)strtoul (line, &endp, 10);
+ for (line = endp; *line == ' ' || *line == '\t'; line++)
+ ;
+ if (!algo || gcry_md_test_algo (algo))
+ return set_error (Unsupported_Algorithm, NULL);
+ ctrl->digest.algo = algo;
+
+ /* parse the hash value */
+ rc = parse_hexstring (ctx, line, &n);
+ if (rc)
+ return rc;
+ n /= 2;
+ if (n != 16 && n != 20 && n != 24 && n != 32)
+ return set_error (Parameter_Error, "unsupported length of hash");
+ if (n > MAX_DIGEST_LEN)
+ return set_error (Parameter_Error, "hash value to long");
+
+ buf = ctrl->digest.value;
+ ctrl->digest.valuelen = n;
+ for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++)
+ buf[n] = xtoi_2 (p);
+ for (; n < ctrl->digest.valuelen; n++)
+ buf[n] = 0;
+ return 0;
+}
+
+
+/* PKSIGN <options>
+
+ Perform the actual sign operation. Neither input nor output are
+ sensitive to eavesdropping */
+static int
+cmd_pksign (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc;
+ int ignore_cache = 0;
+ CTRL ctrl = assuan_get_pointer (ctx);
+
+ if (opt.ignore_cache_for_signing)
+ ignore_cache = 1;
+ else if (!ctrl->server_local->use_cache_for_signing)
+ ignore_cache = 1;
+
+ rc = agent_pksign (ctrl, assuan_get_data_fp (ctx), ignore_cache);
+ if (rc)
+ log_error ("command pksign failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+/* PKDECRYPT <options>
+
+ Perform the actual decrypt operation. Input is not
+ sensitive to eavesdropping */
+static int
+cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc;
+ CTRL ctrl = assuan_get_pointer (ctx);
+ char *value;
+ size_t valuelen;
+
+ /* First inquire the data to decrypt */
+ rc = assuan_inquire (ctx, "CIPHERTEXT",
+ &value, &valuelen, MAXLEN_CIPHERTEXT);
+ if (rc)
+ return rc;
+
+ rc = agent_pkdecrypt (ctrl, value, valuelen, assuan_get_data_fp (ctx));
+ xfree (value);
+ if (rc)
+ log_error ("command pkdecrypt failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+/* GENKEY
+
+ Generate a new key, store the secret part and return the public
+ part. Here is an example transaction:
+
+ C: GENKEY
+ S: INQUIRE KEYPARM
+ C: D (genkey (rsa (nbits 1024)))
+ C: END
+ S: D (public-key
+ S: D (rsa (n 326487324683264) (e 10001)))
+ S OK key created
+*/
+
+static int
+cmd_genkey (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ char *value;
+ size_t valuelen;
+
+ /* First inquire the parameters */
+ rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM);
+ if (rc)
+ return rc;
+
+ rc = agent_genkey (ctrl, value, valuelen, assuan_get_data_fp (ctx));
+ xfree (value);
+ if (rc)
+ log_error ("command genkey failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+static void
+plus_to_blank (char *s)
+{
+ for (; *s; s++)
+ {
+ if (*s == '+')
+ *s = ' ';
+ }
+}
+
+/* GET_PASSPHRASE <cache_id> [<error_message> <prompt> <description>]
+
+ This function is usually used to ask for a passphrase to be used
+ for conventional encryption, but may also be used by programs which
+ need specal handling of passphrases. This command uses a syntax
+ which helps clients to use the agent with minimum effort. The
+ agent either returns with an error or with a OK followed by the hex
+ encoded passphrase. Note that the length of the strings is
+ implicitly limited by the maximum length of a command.
+*/
+
+static int
+cmd_get_passphrase (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ const char *pw;
+ char *response;
+ char *cacheid = NULL, *desc = NULL, *prompt = NULL, *errtext = NULL;
+ char *p;
+ void *cache_marker;
+
+ /* parse the stuff */
+ for (p=line; *p == ' '; p++)
+ ;
+ cacheid = p;
+ p = strchr (cacheid, ' ');
+ if (p)
+ {
+ *p++ = 0;
+ while (*p == ' ')
+ p++;
+ errtext = p;
+ p = strchr (errtext, ' ');
+ if (p)
+ {
+ *p++ = 0;
+ while (*p == ' ')
+ p++;
+ prompt = p;
+ p = strchr (prompt, ' ');
+ if (p)
+ {
+ *p++ = 0;
+ while (*p == ' ')
+ p++;
+ desc = p;
+ p = strchr (desc, ' ');
+ if (p)
+ *p = 0; /* ignore garbage */
+ }
+ }
+ }
+ if (!cacheid || !*cacheid || strlen (cacheid) > 50)
+ return set_error (Parameter_Error, "invalid length of cacheID");
+ if (!desc)
+ return set_error (Parameter_Error, "no description given");
+
+ if (!strcmp (cacheid, "X"))
+ cacheid = NULL;
+ if (!strcmp (errtext, "X"))
+ errtext = NULL;
+ if (!strcmp (prompt, "X"))
+ prompt = NULL;
+ if (!strcmp (desc, "X"))
+ desc = NULL;
+
+ /* Note: we store the hexified versions in the cache. */
+ pw = cacheid ? agent_get_cache (cacheid, &cache_marker) : NULL;
+ if (pw)
+ {
+ assuan_begin_confidential (ctx);
+ rc = assuan_set_okay_line (ctx, pw);
+ agent_unlock_cache_entry (&cache_marker);
+ }
+ else
+ {
+ /* Note, that we only need to replace the + characters and
+ should leave the other escaping in place because the escaped
+ string is send verbatim to the pinentry which does the
+ unescaping (but not the + replacing) */
+ if (errtext)
+ plus_to_blank (errtext);
+ if (prompt)
+ plus_to_blank (prompt);
+ if (desc)
+ plus_to_blank (desc);
+
+ rc = agent_get_passphrase (ctrl, &response, desc, prompt, errtext);
+ if (!rc)
+ {
+ if (cacheid)
+ agent_put_cache (cacheid, response, 0);
+ assuan_begin_confidential (ctx);
+ rc = assuan_set_okay_line (ctx, response);
+ xfree (response);
+ }
+ }
+
+ if (rc)
+ log_error ("command get_passphrase failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+/* CLEAR_PASSPHRASE <cache_id>
+
+ may be used to invalidate the cache entry for a passphrase. The
+ function returns with OK even when there is no cached passphrase.
+*/
+
+static int
+cmd_clear_passphrase (ASSUAN_CONTEXT ctx, char *line)
+{
+ char *cacheid = NULL;
+ char *p;
+
+ /* parse the stuff */
+ for (p=line; *p == ' '; p++)
+ ;
+ cacheid = p;
+ p = strchr (cacheid, ' ');
+ if (p)
+ *p = 0; /* ignore garbage */
+ if (!cacheid || !*cacheid || strlen (cacheid) > 50)
+ return set_error (Parameter_Error, "invalid length of cacheID");
+
+ agent_put_cache (cacheid, NULL, 0);
+ return 0;
+}
+
+
+/* LEARN [--send]
+
+ Learn something about the currently inserted smartcard. With
+ --send the new certificates are send back. */
+static int
+cmd_learn (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc;
+
+ rc = agent_handle_learn (has_option (line, "--send")? ctx : NULL);
+ if (rc)
+ log_error ("command learn failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+
+/* PASSWD <hexstring_with_keygrip>
+
+ Change the passphrase/PID for the key identified by keygrip in LINE. */
+static int
+cmd_passwd (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ unsigned char grip[20];
+ gcry_sexp_t s_skey = NULL;
+ unsigned char *shadow_info = NULL;
+
+ rc = parse_keygrip (ctx, line, grip);
+ if (rc)
+ return rc; /* we can't jump to leave because this is already an
+ Assuan error code. */
+
+ s_skey = agent_key_from_file (ctrl, grip, &shadow_info, 1);
+ if (!s_skey && !shadow_info)
+ rc = gpg_error (GPG_ERR_NO_SECKEY);
+ else if (!s_skey)
+ {
+ log_error ("changing a smartcard PIN is not yet supported\n");
+ rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+ }
+ else
+ rc = agent_protect_and_store (ctrl, s_skey);
+
+ gcry_sexp_release (s_skey);
+ xfree (shadow_info);
+ if (rc)
+ log_error ("command passwd failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+/* SCD <commands to pass to the scdaemon>
+
+ This is a general quote command to redirect everything to the
+ SCDAEMON. */
+static int
+cmd_scd (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+
+ rc = divert_generic_cmd (ctrl, line, ctx);
+
+ return map_to_assuan_status (rc);
+}
+
+
+
+static int
+option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+
+ if (!strcmp (key, "display"))
+ {
+ if (ctrl->display)
+ free (ctrl->display);
+ ctrl->display = strdup (value);
+ if (!ctrl->display)
+ return ASSUAN_Out_Of_Core;
+ }
+ else if (!strcmp (key, "ttyname"))
+ {
+ if (!opt.keep_tty)
+ {
+ if (ctrl->ttyname)
+ free (ctrl->ttyname);
+ ctrl->ttyname = strdup (value);
+ if (!ctrl->ttyname)
+ return ASSUAN_Out_Of_Core;
+ }
+ }
+ else if (!strcmp (key, "ttytype"))
+ {
+ if (!opt.keep_tty)
+ {
+ if (ctrl->ttytype)
+ free (ctrl->ttytype);
+ ctrl->ttytype = strdup (value);
+ if (!ctrl->ttytype)
+ return ASSUAN_Out_Of_Core;
+ }
+ }
+ else if (!strcmp (key, "lc-ctype"))
+ {
+ if (ctrl->lc_ctype)
+ free (ctrl->lc_ctype);
+ ctrl->lc_ctype = strdup (value);
+ if (!ctrl->lc_ctype)
+ return ASSUAN_Out_Of_Core;
+ }
+ else if (!strcmp (key, "lc-messages"))
+ {
+ if (ctrl->lc_messages)
+ free (ctrl->lc_messages);
+ ctrl->lc_messages = strdup (value);
+ if (!ctrl->lc_messages)
+ return ASSUAN_Out_Of_Core;
+ }
+ else if (!strcmp (key, "use-cache-for-signing"))
+ ctrl->server_local->use_cache_for_signing = *value? atoi (value) : 0;
+ else
+ return ASSUAN_Invalid_Option;
+
+ return 0;
+}
+
+
+/* Tell the assuan library about our commands */
+static int
+register_commands (ASSUAN_CONTEXT ctx)
+{
+ static struct {
+ const char *name;
+ int (*handler)(ASSUAN_CONTEXT, char *line);
+ } table[] = {
+ { "ISTRUSTED", cmd_istrusted },
+ { "HAVEKEY", cmd_havekey },
+ { "SIGKEY", cmd_sigkey },
+ { "SETKEY", cmd_sigkey },
+ { "SETHASH", cmd_sethash },
+ { "PKSIGN", cmd_pksign },
+ { "PKDECRYPT", cmd_pkdecrypt },
+ { "GENKEY", cmd_genkey },
+ { "GET_PASSPHRASE", cmd_get_passphrase },
+ { "CLEAR_PASSPHRASE", cmd_clear_passphrase },
+ { "LISTTRUSTED", cmd_listtrusted },
+ { "MARKTRUSTED", cmd_marktrusted },
+ { "LEARN", cmd_learn },
+ { "PASSWD", cmd_passwd },
+ { "INPUT", NULL },
+ { "OUTPUT", NULL },
+ { "SCD", cmd_scd },
+ { NULL }
+ };
+ int i, rc;
+
+ for (i=0; table[i].name; i++)
+ {
+ rc = assuan_register_command (ctx, table[i].name, table[i].handler);
+ if (rc)
+ return rc;
+ }
+ assuan_register_reset_notify (ctx, reset_notify);
+ assuan_register_option_handler (ctx, option_handler);
+ return 0;
+}
+
+
+/* Startup the server. If LISTEN_FD and FD is given as -1, this is a simple
+ piper server, otherwise it is a regular server */
+void
+start_command_handler (int listen_fd, int fd)
+{
+ int rc;
+ ASSUAN_CONTEXT ctx;
+ struct server_control_s ctrl;
+
+ memset (&ctrl, 0, sizeof ctrl);
+ agent_init_default_ctrl (&ctrl);
+
+ if (listen_fd == -1 && fd == -1)
+ {
+ int filedes[2];
+
+ filedes[0] = 0;
+ filedes[1] = 1;
+ rc = assuan_init_pipe_server (&ctx, filedes);
+ }
+ else if (listen_fd != -1)
+ {
+ rc = assuan_init_socket_server (&ctx, listen_fd);
+ }
+ else
+ {
+ rc = assuan_init_connected_socket_server (&ctx, fd);
+ }
+ if (rc)
+ {
+ log_error ("failed to initialize the server: %s\n",
+ assuan_strerror(rc));
+ agent_exit (2);
+ }
+ rc = register_commands (ctx);
+ if (rc)
+ {
+ log_error ("failed to register commands with Assuan: %s\n",
+ assuan_strerror(rc));
+ agent_exit (2);
+ }
+
+ assuan_set_pointer (ctx, &ctrl);
+ ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local);
+ ctrl.server_local->assuan_ctx = ctx;
+ ctrl.server_local->message_fd = -1;
+ ctrl.server_local->use_cache_for_signing = 1;
+
+ if (DBG_ASSUAN)
+ assuan_set_log_stream (ctx, log_get_stream ());
+
+ for (;;)
+ {
+ rc = assuan_accept (ctx);
+ if (rc == -1)
+ {
+ break;
+ }
+ else if (rc)
+ {
+ log_info ("Assuan accept problem: %s\n", assuan_strerror (rc));
+ break;
+ }
+
+ rc = assuan_process (ctx);
+ if (rc)
+ {
+ log_info ("Assuan processing failed: %s\n", assuan_strerror (rc));
+ continue;
+ }
+ }
+
+
+ assuan_deinit_server (ctx);
+ if (ctrl.display)
+ free (ctrl.display);
+ if (ctrl.ttyname)
+ free (ctrl.ttyname);
+ if (ctrl.ttytype)
+ free (ctrl.ttytype);
+ if (ctrl.lc_ctype)
+ free (ctrl.lc_ctype);
+ if (ctrl.lc_messages)
+ free (ctrl.lc_messages);
+}
+
diff --git a/agent/divert-scd.c b/agent/divert-scd.c
new file mode 100644
index 000000000..69f184474
--- /dev/null
+++ b/agent/divert-scd.c
@@ -0,0 +1,319 @@
+/* divert-scd.c - divert operations to the scdaemon
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "agent.h"
+#include "sexp-parse.h"
+#include "i18n.h"
+
+
+static int
+ask_for_card (CTRL ctrl, const unsigned char *shadow_info, char **r_kid)
+{
+ int rc, i;
+ const unsigned char *s;
+ size_t n;
+ char *serialno;
+ int no_card = 0;
+ char *desc;
+ char *want_sn, *want_kid;
+ int want_sn_displen;
+
+ *r_kid = NULL;
+ s = shadow_info;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ want_sn = xtrymalloc (n*2+1);
+ if (!want_sn)
+ return out_of_core ();
+ for (i=0; i < n; i++)
+ sprintf (want_sn+2*i, "%02X", s[i]);
+ s += n;
+ /* We assume that a 20 byte serial number is a standard one which
+ seems to have the property to have a zero in the last nibble. We
+ don't display this '0' because it may confuse the user */
+ want_sn_displen = strlen (want_sn);
+ if (want_sn_displen == 20 && want_sn[19] == '0')
+ want_sn_displen--;
+
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ want_kid = xtrymalloc (n+1);
+ if (!want_kid)
+ {
+ gpg_error_t tmperr = out_of_core ();
+ xfree (want_sn);
+ return tmperr;
+ }
+ memcpy (want_kid, s, n);
+ want_kid[n] = 0;
+
+ for (;;)
+ {
+ rc = agent_card_serialno (&serialno);
+ if (!rc)
+ {
+ log_debug ("detected card with S/N %s\n", serialno);
+ i = strcmp (serialno, want_sn);
+ xfree (serialno);
+ serialno = NULL;
+ if (!i)
+ {
+ xfree (want_sn);
+ *r_kid = want_kid;
+ return 0; /* yes, we have the correct card */
+ }
+ }
+ else if (gpg_err_code (rc) == GPG_ERR_CARD_NOT_PRESENT)
+ {
+ log_debug ("no card present\n");
+ rc = 0;
+ no_card = 1;
+ }
+ else
+ {
+ log_error ("error accesing card: %s\n", gpg_strerror (rc));
+ }
+
+ if (!rc)
+ {
+ if (asprintf (&desc,
+ "%s:%%0A%%0A"
+ " \"%.*s\"",
+ no_card? "Please insert the card with serial number"
+ : "Please remove the current card and "
+ "insert the one with serial number",
+ want_sn_displen, want_sn) < 0)
+ {
+ rc = out_of_core ();
+ }
+ else
+ {
+ rc = agent_get_confirmation (ctrl, desc, NULL, NULL);
+ free (desc);
+ }
+ }
+ if (rc)
+ {
+ xfree (want_sn);
+ xfree (want_kid);
+ return rc;
+ }
+ }
+}
+
+
+/* Put the DIGEST into an DER encoded comtainer and return it in R_VAL. */
+static int
+encode_md_for_card (const unsigned char *digest, size_t digestlen, int algo,
+ unsigned char **r_val, size_t *r_len)
+{
+ byte *frame;
+ byte asn[100];
+ size_t asnlen;
+
+ asnlen = DIM(asn);
+ if (gcry_md_algo_info (algo, GCRYCTL_GET_ASNOID, asn, &asnlen))
+ {
+ log_error ("no object identifier for algo %d\n", algo);
+ return gpg_error (GPG_ERR_INTERNAL);
+ }
+
+ frame = xtrymalloc (asnlen + digestlen);
+ if (!frame)
+ return out_of_core ();
+ memcpy (frame, asn, asnlen);
+ memcpy (frame+asnlen, digest, digestlen);
+ if (DBG_CRYPTO)
+ log_printhex ("encoded hash:", frame, asnlen+digestlen);
+
+ *r_val = frame;
+ *r_len = asnlen+digestlen;
+ return 0;
+}
+
+
+/* Callback used to ask for the PIN which should be set into BUF. The
+ buf has been allocated by the caller and is of size MAXBUF which
+ includes the terminating null. The function should return an UTF-8
+ string with the passphrase, the buffer may optionally be padded
+ with arbitrary characters */
+static int
+getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf)
+{
+ struct pin_entry_info_s *pi;
+ int rc;
+ char *desc;
+ CTRL ctrl = opaque;
+
+ if (maxbuf < 2)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+
+ /* FIXME: keep PI and TRIES in OPAQUE. Frankly this is a whole
+ mess because we should call the card's verify function from the
+ pinentry check pin CB. */
+ pi = gcry_calloc_secure (1, sizeof (*pi) + 100);
+ pi->max_length = maxbuf-1;
+ pi->min_digits = 0; /* we want a real passphrase */
+ pi->max_digits = 8;
+ pi->max_tries = 3;
+
+ if ( asprintf (&desc, _("Please enter the PIN%s%s%s to unlock the card"),
+ info? " (`":"",
+ info? info:"",
+ info? "')":"") < 0)
+ desc = NULL;
+ rc = agent_askpin (ctrl, desc?desc:info, pi);
+ free (desc);
+ if (!rc)
+ {
+ strncpy (buf, pi->pin, maxbuf-1);
+ buf[maxbuf-1] = 0;
+ }
+ xfree (pi);
+ return rc;
+}
+
+
+
+
+int
+divert_pksign (CTRL ctrl,
+ const unsigned char *digest, size_t digestlen, int algo,
+ const unsigned char *shadow_info, unsigned char **r_sig)
+{
+ int rc;
+ char *kid;
+ size_t siglen;
+ char *sigval;
+ unsigned char *data;
+ size_t ndata;
+
+ rc = ask_for_card (ctrl, shadow_info, &kid);
+ if (rc)
+ return rc;
+
+ rc = encode_md_for_card (digest, digestlen, algo,
+ &data, &ndata);
+ if (rc)
+ return rc;
+
+ rc = agent_card_pksign (kid, getpin_cb, ctrl,
+ data, ndata, &sigval, &siglen);
+ if (!rc)
+ *r_sig = sigval;
+ xfree (data);
+ xfree (kid);
+
+ return rc;
+}
+
+
+/* Decrypt the the value given asn an S-expression in CIPHER using the
+ key identified by SHADOW_INFO and return the plaintext in an
+ allocated buffer in R_BUF. */
+int
+divert_pkdecrypt (CTRL ctrl,
+ const unsigned char *cipher,
+ const unsigned char *shadow_info,
+ char **r_buf, size_t *r_len)
+{
+ int rc;
+ char *kid;
+ const unsigned char *s;
+ size_t n;
+ const unsigned char *ciphertext;
+ size_t ciphertextlen;
+ char *plaintext;
+ size_t plaintextlen;
+
+ s = cipher;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "enc-val"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "rsa"))
+ return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "a"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ ciphertext = s;
+ ciphertextlen = n;
+
+ rc = ask_for_card (ctrl, shadow_info, &kid);
+ if (rc)
+ return rc;
+
+ rc = agent_card_pkdecrypt (kid, getpin_cb, ctrl,
+ ciphertext, ciphertextlen,
+ &plaintext, &plaintextlen);
+ if (!rc)
+ {
+ *r_buf = plaintext;
+ *r_len = plaintextlen;
+ }
+ xfree (kid);
+ return rc;
+}
+
+
+int
+divert_generic_cmd (CTRL ctrl, const char *cmdline, void *assuan_context)
+{
+ return agent_card_scd (cmdline, getpin_cb, ctrl, assuan_context);
+}
+
+
+
+
+
diff --git a/agent/findkey.c b/agent/findkey.c
new file mode 100644
index 000000000..db36cb1b9
--- /dev/null
+++ b/agent/findkey.c
@@ -0,0 +1,359 @@
+/* findkey.c - locate the secret key
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <assert.h>
+
+#include "agent.h"
+
+/* Helper to pass data to the check callback of the unprotect function. */
+struct try_unprotect_arg_s {
+ const unsigned char *protected_key;
+ unsigned char *unprotected_key;
+};
+
+
+
+int
+agent_write_private_key (const unsigned char *grip,
+ const void *buffer, size_t length, int force)
+{
+ int i;
+ char *fname;
+ FILE *fp;
+ char hexgrip[40+4+1];
+
+ for (i=0; i < 20; i++)
+ sprintf (hexgrip+2*i, "%02X", grip[i]);
+ strcpy (hexgrip+40, ".key");
+
+ fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+ if (force)
+ fp = fopen (fname, "wb");
+ else
+ {
+ int fd;
+
+ if (!access (fname, F_OK))
+ {
+ log_error ("secret key file `%s' already exists\n", fname);
+ xfree (fname);
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+
+ /* We would like to create FNAME but only if it does not already
+ exist. We cannot make this guarantee just using POSIX (GNU
+ provides the "x" opentype for fopen, however, this is not
+ portable). Thus, we use the more flexible open function and
+ then use fdopen to obtain a stream.
+
+ The mode parameter to open is what fopen uses. It will be
+ combined with the process' umask automatically. */
+ fd = open (fname, O_CREAT | O_EXCL | O_RDWR,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ if (fd < 0)
+ fp = 0;
+ else
+ {
+ fp = fdopen (fd, "wb");
+ if (!fp)
+ {
+ int save_e = errno;
+ close (fd);
+ errno = save_e;
+ }
+ }
+ }
+
+ if (!fp)
+ {
+ gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("can't create `%s': %s\n", fname, strerror (errno));
+ xfree (fname);
+ return tmperr;
+ }
+
+ if (fwrite (buffer, length, 1, fp) != 1)
+ {
+ gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("error writing `%s': %s\n", fname, strerror (errno));
+ fclose (fp);
+ remove (fname);
+ xfree (fname);
+ return tmperr;
+ }
+ if ( fclose (fp) )
+ {
+ gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("error closing `%s': %s\n", fname, strerror (errno));
+ remove (fname);
+ xfree (fname);
+ return tmperr;
+ }
+
+ xfree (fname);
+ return 0;
+}
+
+
+/* Callback function to try the unprotection from the passpharse query
+ code. */
+static int
+try_unprotect_cb (struct pin_entry_info_s *pi)
+{
+ struct try_unprotect_arg_s *arg = pi->check_cb_arg;
+ size_t dummy;
+
+ assert (!arg->unprotected_key);
+ return agent_unprotect (arg->protected_key, pi->pin,
+ &arg->unprotected_key, &dummy);
+}
+
+
+/* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP
+ should be the hex encoded keygrip of that key to be used with the
+ caching mechanism. */
+static int
+unprotect (CTRL ctrl,
+ unsigned char **keybuf, const unsigned char *grip, int ignore_cache)
+{
+ struct pin_entry_info_s *pi;
+ struct try_unprotect_arg_s arg;
+ int rc, i;
+ unsigned char *result;
+ size_t resultlen;
+ char hexgrip[40+1];
+
+ for (i=0; i < 20; i++)
+ sprintf (hexgrip+2*i, "%02X", grip[i]);
+ hexgrip[40] = 0;
+
+ /* first try to get it from the cache - if there is none or we can't
+ unprotect it, we fall back to ask the user */
+ if (!ignore_cache)
+ {
+ void *cache_marker;
+ const char *pw = agent_get_cache (hexgrip, &cache_marker);
+ if (pw)
+ {
+ rc = agent_unprotect (*keybuf, pw, &result, &resultlen);
+ agent_unlock_cache_entry (&cache_marker);
+ if (!rc)
+ {
+ xfree (*keybuf);
+ *keybuf = result;
+ return 0;
+ }
+ rc = 0;
+ }
+ }
+
+ pi = gcry_calloc_secure (1, sizeof (*pi) + 100);
+ pi->max_length = 100;
+ pi->min_digits = 0; /* we want a real passphrase */
+ pi->max_digits = 8;
+ pi->max_tries = 3;
+ pi->check_cb = try_unprotect_cb;
+ arg.protected_key = *keybuf;
+ arg.unprotected_key = NULL;
+ pi->check_cb_arg = &arg;
+
+ rc = agent_askpin (ctrl, NULL, pi);
+ if (!rc)
+ {
+ assert (arg.unprotected_key);
+ agent_put_cache (hexgrip, pi->pin, 0);
+ xfree (*keybuf);
+ *keybuf = arg.unprotected_key;
+ }
+ xfree (pi);
+ return rc;
+}
+
+
+
+/* Return the secret key as an S-Exp after locating it using the grip.
+ Returns NULL if key is not available or the operation should be
+ diverted to a token. In the latter case shadow_info will point to
+ an allocated S-Expression with the shadow_info part from the file.
+ With IGNORE_CACHE passed as true the passphrase is not taken from
+ the cache.*/
+gcry_sexp_t
+agent_key_from_file (CTRL ctrl,
+ const unsigned char *grip, unsigned char **shadow_info,
+ int ignore_cache)
+{
+ int i, rc;
+ char *fname;
+ FILE *fp;
+ struct stat st;
+ unsigned char *buf;
+ size_t len, buflen, erroff;
+ gcry_sexp_t s_skey;
+ char hexgrip[40+4+1];
+
+ if (shadow_info)
+ *shadow_info = NULL;
+
+ for (i=0; i < 20; i++)
+ sprintf (hexgrip+2*i, "%02X", grip[i]);
+ strcpy (hexgrip+40, ".key");
+
+ fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+ fp = fopen (fname, "rb");
+ if (!fp)
+ {
+ log_error ("can't open `%s': %s\n", fname, strerror (errno));
+ xfree (fname);
+ return NULL;
+ }
+
+ if (fstat (fileno(fp), &st))
+ {
+ log_error ("can't stat `%s': %s\n", fname, strerror (errno));
+ xfree (fname);
+ fclose (fp);
+ return NULL;
+ }
+
+ buflen = st.st_size;
+ buf = xmalloc (buflen+1);
+ if (fread (buf, buflen, 1, fp) != 1)
+ {
+ log_error ("error reading `%s': %s\n", fname, strerror (errno));
+ xfree (fname);
+ fclose (fp);
+ xfree (buf);
+ return NULL;
+ }
+
+ rc = gcry_sexp_sscan (&s_skey, &erroff, buf, buflen);
+ xfree (fname);
+ fclose (fp);
+ xfree (buf);
+ if (rc)
+ {
+ log_error ("failed to build S-Exp (off=%u): %s\n",
+ (unsigned int)erroff, gpg_strerror (rc));
+ return NULL;
+ }
+ len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (len);
+ buf = xtrymalloc (len);
+ if (!buf)
+ {
+ gcry_sexp_release (s_skey);
+ return NULL;
+ }
+ len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, buf, len);
+ assert (len);
+ gcry_sexp_release (s_skey);
+
+ switch (agent_private_key_type (buf))
+ {
+ case PRIVATE_KEY_CLEAR:
+ break; /* no unprotection needed */
+ case PRIVATE_KEY_PROTECTED:
+ rc = unprotect (ctrl, &buf, grip, ignore_cache);
+ if (rc)
+ log_error ("failed to unprotect the secret key: %s\n",
+ gpg_strerror (rc));
+ break;
+ case PRIVATE_KEY_SHADOWED:
+ if (shadow_info)
+ {
+ const unsigned char *s;
+ size_t n;
+
+ rc = agent_get_shadow_info (buf, &s);
+ if (!rc)
+ {
+ n = gcry_sexp_canon_len (s, 0, NULL,NULL);
+ assert (n);
+ *shadow_info = xtrymalloc (n);
+ if (!*shadow_info)
+ rc = out_of_core ();
+ else
+ {
+ memcpy (*shadow_info, s, n);
+ rc = 0;
+ }
+ }
+ if (rc)
+ log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc));
+ }
+ rc = -1; /* ugly interface: we return an error but keep a value
+ in shadow_info. */
+ break;
+ default:
+ log_error ("invalid private key format\n");
+ rc = gpg_error (GPG_ERR_BAD_SECKEY);
+ break;
+ }
+ if (rc)
+ {
+ xfree (buf);
+ return NULL;
+ }
+
+ /* arggg FIXME: does scan support secure memory? */
+ rc = gcry_sexp_sscan (&s_skey, &erroff,
+ buf, gcry_sexp_canon_len (buf, 0, NULL, NULL));
+ xfree (buf);
+ if (rc)
+ {
+ log_error ("failed to build S-Exp (off=%u): %s\n",
+ (unsigned int)erroff, gpg_strerror (rc));
+ return NULL;
+ }
+
+ return s_skey;
+}
+
+/* Return the secret key as an S-Exp after locating it using the grip.
+ Returns NULL if key is not available. 0 = key is available */
+int
+agent_key_available (const unsigned char *grip)
+{
+ int i;
+ char *fname;
+ char hexgrip[40+4+1];
+
+ for (i=0; i < 20; i++)
+ sprintf (hexgrip+2*i, "%02X", grip[i]);
+ strcpy (hexgrip+40, ".key");
+
+ fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+ i = !access (fname, R_OK)? 0 : -1;
+ xfree (fname);
+ return i;
+}
+
+
+
diff --git a/agent/genkey.c b/agent/genkey.c
new file mode 100644
index 000000000..0a0577f17
--- /dev/null
+++ b/agent/genkey.c
@@ -0,0 +1,240 @@
+/* pksign.c - Generate a keypair
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "agent.h"
+#include "i18n.h"
+
+static int
+store_key (gcry_sexp_t private, const char *passphrase, int force)
+{
+ int rc;
+ char *buf;
+ size_t len;
+ unsigned char grip[20];
+
+ if ( !gcry_pk_get_keygrip (private, grip) )
+ {
+ log_error ("can't calculate keygrip\n");
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+
+ len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (len);
+ buf = gcry_malloc_secure (len);
+ if (!buf)
+ return out_of_core ();
+ len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len);
+ assert (len);
+
+ if (passphrase)
+ {
+ unsigned char *p;
+
+ rc = agent_protect (buf, passphrase, &p, &len);
+ if (rc)
+ {
+ xfree (buf);
+ return rc;
+ }
+ xfree (buf);
+ buf = p;
+ }
+
+ rc = agent_write_private_key (grip, buf, len, force);
+ xfree (buf);
+ return rc;
+}
+
+/* Callback function to compare the first entered PIN with the one
+ currently being entered. */
+static int
+reenter_compare_cb (struct pin_entry_info_s *pi)
+{
+ const char *pin1 = pi->check_cb_arg;
+
+ if (!strcmp (pin1, pi->pin))
+ return 0; /* okay */
+ pi->cb_errtext = _("does not match - try again");
+ return -1;
+}
+
+
+
+/* Generate a new keypair according to the parameters given in
+ KEYPARAM */
+int
+agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen,
+ FILE *outfp)
+{
+ gcry_sexp_t s_keyparam, s_key, s_private, s_public;
+ struct pin_entry_info_s *pi, *pi2;
+ int rc;
+ size_t len;
+ char *buf;
+
+ rc = gcry_sexp_sscan (&s_keyparam, NULL, keyparam, keyparamlen);
+ if (rc)
+ {
+ log_error ("failed to convert keyparam: %s\n", gpg_strerror (rc));
+ return gpg_error (GPG_ERR_INV_DATA);
+ }
+
+ /* Get the passphrase now, cause key generation may take a while. */
+ {
+ const char *text1 = _("Please enter the passphrase to%0A"
+ "to protect your new key");
+ const char *text2 = _("Please re-enter this passphrase");
+
+ pi = gcry_calloc_secure (2, sizeof (*pi) + 100);
+ pi2 = pi + (sizeof *pi + 100);
+ pi->max_length = 100;
+ pi->max_tries = 3;
+ pi2->max_length = 100;
+ pi2->max_tries = 3;
+ pi2->check_cb = reenter_compare_cb;
+ pi2->check_cb_arg = pi->pin;
+
+ rc = agent_askpin (ctrl, text1, pi);
+ if (!rc)
+ rc = agent_askpin (ctrl, text2, pi2);
+ if (rc)
+ return rc;
+ if (!*pi->pin)
+ {
+ xfree (pi);
+ pi = NULL; /* User does not want a passphrase. */
+ }
+ }
+
+ rc = gcry_pk_genkey (&s_key, s_keyparam );
+ gcry_sexp_release (s_keyparam);
+ if (rc)
+ {
+ log_error ("key generation failed: %s\n", gpg_strerror (rc));
+ xfree (pi);
+ return map_gcry_err (rc);
+ }
+
+ /* break out the parts */
+ s_private = gcry_sexp_find_token (s_key, "private-key", 0);
+ if (!s_private)
+ {
+ log_error ("key generation failed: invalid return value\n");
+ gcry_sexp_release (s_key);
+ xfree (pi);
+ return gpg_error (GPG_ERR_INV_DATA);
+ }
+ s_public = gcry_sexp_find_token (s_key, "public-key", 0);
+ if (!s_public)
+ {
+ log_error ("key generation failed: invalid return value\n");
+ gcry_sexp_release (s_private);
+ gcry_sexp_release (s_key);
+ xfree (pi);
+ return gpg_error (GPG_ERR_INV_DATA);
+ }
+ gcry_sexp_release (s_key); s_key = NULL;
+
+ /* store the secret key */
+ log_debug ("storing private key\n");
+ rc = store_key (s_private, pi? pi->pin:NULL, 0);
+ xfree (pi); pi = NULL;
+ gcry_sexp_release (s_private);
+ if (rc)
+ {
+ gcry_sexp_release (s_public);
+ return rc;
+ }
+
+ /* return the public key */
+ log_debug ("returning public key\n");
+ len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (len);
+ buf = xtrymalloc (len);
+ if (!buf)
+ {
+ gpg_error_t tmperr = out_of_core ();
+ gcry_sexp_release (s_private);
+ gcry_sexp_release (s_public);
+ return tmperr;
+ }
+ len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, buf, len);
+ assert (len);
+ if (fwrite (buf, len, 1, outfp) != 1)
+ {
+ gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("error writing public key: %s\n", strerror (errno));
+ gcry_sexp_release (s_private);
+ gcry_sexp_release (s_public);
+ xfree (buf);
+ return tmperr;
+ }
+ gcry_sexp_release (s_public);
+ xfree (buf);
+
+ return 0;
+}
+
+
+
+/* Apply a new passpahrse to the key S_SKEY and store it. */
+int
+agent_protect_and_store (CTRL ctrl, gcry_sexp_t s_skey)
+{
+ struct pin_entry_info_s *pi, *pi2;
+ int rc;
+
+ {
+ const char *text1 = _("Please enter the new passphrase");
+ const char *text2 = _("Please re-enter this passphrase");
+
+ pi = gcry_calloc_secure (2, sizeof (*pi) + 100);
+ pi2 = pi + (sizeof *pi + 100);
+ pi->max_length = 100;
+ pi->max_tries = 3;
+ pi2->max_length = 100;
+ pi2->max_tries = 3;
+ pi2->check_cb = reenter_compare_cb;
+ pi2->check_cb_arg = pi->pin;
+
+ rc = agent_askpin (ctrl, text1, pi);
+ if (!rc)
+ rc = agent_askpin (ctrl, text2, pi2);
+ if (rc)
+ return rc;
+ if (!*pi->pin)
+ {
+ xfree (pi);
+ pi = NULL; /* User does not want a passphrase. */
+ }
+ }
+
+ rc = store_key (s_skey, pi? pi->pin:NULL, 1);
+ xfree (pi);
+ return 0;
+}
diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c
new file mode 100644
index 000000000..675f2be3f
--- /dev/null
+++ b/agent/gpg-agent.c
@@ -0,0 +1,1063 @@
+/* gpg-agent.c - The GnuPG Agent
+ * Copyright (C) 2000, 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <time.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <signal.h>
+#ifdef USE_GNU_PTH
+# include <pth.h>
+#endif
+
+#define JNLIB_NEED_LOG_LOGV
+#include "agent.h"
+#include <assuan.h> /* malloc hooks */
+
+#include "i18n.h"
+#include "sysutils.h"
+
+
+enum cmd_and_opt_values
+{ aNull = 0,
+ oCsh = 'c',
+ oQuiet = 'q',
+ oSh = 's',
+ oVerbose = 'v',
+
+ oNoVerbose = 500,
+ oOptions,
+ oDebug,
+ oDebugAll,
+ oDebugWait,
+ oNoGreeting,
+ oNoOptions,
+ oHomedir,
+ oNoDetach,
+ oNoGrab,
+ oLogFile,
+ oServer,
+ oDaemon,
+ oBatch,
+
+ oPinentryProgram,
+ oDisplay,
+ oTTYname,
+ oTTYtype,
+ oLCctype,
+ oLCmessages,
+ oScdaemonProgram,
+ oDefCacheTTL,
+ oDisablePth,
+
+ oIgnoreCacheForSigning,
+ oKeepTTY,
+ oKeepDISPLAY,
+
+aTest };
+
+
+
+static ARGPARSE_OPTS opts[] = {
+
+ { 301, NULL, 0, N_("@Options:\n ") },
+
+ { oServer, "server", 0, N_("run in server mode (foreground)") },
+ { oDaemon, "daemon", 0, N_("run in daemon mode (background)") },
+ { oVerbose, "verbose", 0, N_("verbose") },
+ { oQuiet, "quiet", 0, N_("be somewhat more quiet") },
+ { oSh, "sh", 0, N_("sh-style command output") },
+ { oCsh, "csh", 0, N_("csh-style command output") },
+ { oOptions, "options" , 2, N_("read options from file")},
+ { oDebug, "debug" ,4|16, N_("set debugging flags")},
+ { oDebugAll, "debug-all" ,0, N_("enable full debugging")},
+ { oDebugWait,"debug-wait",1, "@"},
+ { oNoDetach, "no-detach" ,0, N_("do not detach from the console")},
+ { oNoGrab, "no-grab" ,0, N_("do not grab keyboard and mouse")},
+ { oLogFile, "log-file" ,2, N_("use a log file for the server")},
+ { oDisablePth, "disable-pth", 0, N_("do not allow multiple connections")},
+
+ { oPinentryProgram, "pinentry-program", 2 , "path to PIN Entry program" },
+ { oDisplay, "display", 2, "set the display" },
+ { oTTYname, "ttyname", 2, "set the tty terminal node name" },
+ { oTTYtype, "ttytype", 2, "set the tty terminal type" },
+ { oLCctype, "lc-ctype", 2, "set the tty LC_CTYPE value" },
+ { oLCmessages, "lc-messages", 2, "set the tty LC_MESSAGES value" },
+
+ { oScdaemonProgram, "scdaemon-program", 2 , "path to SCdaemon program" },
+ { oDefCacheTTL, "default-cache-ttl", 4,
+ "|N|expire cached PINs after N seconds"},
+ { oIgnoreCacheForSigning, "ignore-cache-for-signing", 0,
+ "do not use the PIN cache when signing"},
+ { oKeepTTY, "keep-tty", 0, N_("ignore requests to change the TTY")},
+ { oKeepDISPLAY, "keep-display",
+ 0, N_("ignore requests to change the X display")},
+ {0}
+};
+
+
+static volatile int caught_fatal_sig = 0;
+
+/* flag to indicate that a shutdown was requested */
+static int shutdown_pending;
+
+
+/* It is possible that we are currently running under setuid permissions */
+static int maybe_setuid = 1;
+
+/* Name of the communication socket */
+static char socket_name[128];
+
+/* Default values for options passed to the pinentry. */
+static char *default_display;
+static char *default_ttyname;
+static char *default_ttytype;
+static char *default_lc_ctype;
+static char *default_lc_messages;
+
+/* Name of a config file, which will be reread on a HUP if it is not NULL. */
+static char *config_filename;
+
+
+/* Local prototypes. */
+static void create_directories (void);
+#ifdef USE_GNU_PTH
+static void handle_connections (int listen_fd);
+#endif
+
+
+
+static const char *
+my_strusage (int level)
+{
+ const char *p;
+ switch (level)
+ {
+ case 11: p = "gpg-agent (GnuPG)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
+ break;
+ case 1:
+ case 40: p = _("Usage: gpg-agent [options] (-h for help)");
+ break;
+ case 41: p = _("Syntax: gpg-agent [options] [command [args]]\n"
+ "Secret key management for GnuPG\n");
+ break;
+
+ default: p = NULL;
+ }
+ return p;
+}
+
+
+
+static void
+i18n_init (void)
+{
+#ifdef USE_SIMPLE_GETTEXT
+ set_gettext_file( PACKAGE );
+#else
+#ifdef ENABLE_NLS
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+#endif
+#endif
+}
+
+
+
+/* Used by gcry for logging */
+static void
+my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr)
+{
+ /* translate the log levels */
+ switch (level)
+ {
+ case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break;
+ case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break;
+ case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break;
+ case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break;
+ case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break;
+ case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break;
+ case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break;
+ default: level = JNLIB_LOG_ERROR; break;
+ }
+ log_logv (level, fmt, arg_ptr);
+}
+
+
+static void
+cleanup (void)
+{
+ if (*socket_name)
+ {
+ char *p;
+
+ remove (socket_name);
+ p = strrchr (socket_name, '/');
+ if (p)
+ {
+ *p = 0;
+ rmdir (socket_name);
+ *p = '/';
+ }
+ *socket_name = 0;
+ }
+}
+
+
+static RETSIGTYPE
+cleanup_sh (int sig)
+{
+ if (caught_fatal_sig)
+ raise (sig);
+ caught_fatal_sig = 1;
+
+ /* gcry_control( GCRYCTL_TERM_SECMEM );*/
+ cleanup ();
+
+#ifndef HAVE_DOSISH_SYSTEM
+ { /* reset action to default action and raise signal again */
+ struct sigaction nact;
+ nact.sa_handler = SIG_DFL;
+ sigemptyset( &nact.sa_mask );
+ nact.sa_flags = 0;
+ sigaction( sig, &nact, NULL);
+ }
+#endif
+ raise( sig );
+}
+
+
+/* Handle options which are allowed to be reset after program start.
+ Return true when the current option in PARGS could be handled and
+ false if not. As a special feature, passing a value of NULL for
+ PARGS, resets the options to the default. */
+static int
+parse_rereadable_options (ARGPARSE_ARGS *pargs)
+{
+ if (!pargs)
+ { /* reset mode */
+ opt.quiet = 0;
+ opt.verbose = 0;
+ opt.debug = 0;
+ opt.no_grab = 0;
+ opt.pinentry_program = NULL;
+ opt.scdaemon_program = NULL;
+ opt.def_cache_ttl = 10*60; /* default to 10 minutes */
+ opt.ignore_cache_for_signing = 0;
+ return 1;
+ }
+
+ switch (pargs->r_opt)
+ {
+ case oQuiet: opt.quiet = 1; break;
+ case oVerbose: opt.verbose++; break;
+
+ case oDebug: opt.debug |= pargs->r.ret_ulong; break;
+ case oDebugAll: opt.debug = ~0; break;
+
+ case oNoGrab: opt.no_grab = 1; break;
+
+ case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break;
+ case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break;
+
+ case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break;
+
+ case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break;
+
+ default:
+ return 0; /* not handled */
+ }
+ return 1; /* handled */
+}
+
+
+int
+main (int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ int orig_argc;
+ int may_coredump;
+ char **orig_argv;
+ FILE *configfp = NULL;
+ char *configname = NULL;
+ const char *shell;
+ unsigned configlineno;
+ int parse_debug = 0;
+ int default_config =1;
+ int greeting = 0;
+ int nogreeting = 0;
+ int pipe_server = 0;
+ int is_daemon = 0;
+ int nodetach = 0;
+ int csh_style = 0;
+ char *logfile = NULL;
+ int debug_wait = 0;
+ int disable_pth = 0;
+
+ set_strusage (my_strusage);
+ gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+ /* Please note that we may running SUID(ROOT), so be very CAREFUL
+ when adding any stuff between here and the call to INIT_SECMEM()
+ somewhere after the option parsing */
+ log_set_prefix ("gpg-agent", 1|4);
+ i18n_init ();
+
+ /* We need to initialize Pth before libgcrypt, because the libgcrypt
+ initialization done by gcry_check_version internally sets up its
+ mutex system. Note that one must not link against pth if
+ USE_GNU_PTH is not defined. */
+#ifdef USE_GNU_PTH
+ if (!pth_init ())
+ {
+ log_error ("failed to initialize the Pth library\n");
+ exit (1);
+ }
+#endif /*USE_GNU_PTH*/
+
+ /* check that the libraries are suitable. Do it here because
+ the option parsing may need services of the library */
+ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
+ {
+ log_fatal( _("libgcrypt is too old (need %s, have %s)\n"),
+ NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
+ }
+
+ assuan_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
+
+ gcry_set_log_handler (my_gcry_logger, NULL);
+ gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
+
+ may_coredump = disable_core_dumps ();
+
+ parse_rereadable_options (NULL); /* Reset them to default values. */
+
+ shell = getenv ("SHELL");
+ if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
+ csh_style = 1;
+
+ opt.homedir = getenv("GNUPGHOME");
+ if (!opt.homedir || !*opt.homedir)
+ opt.homedir = GNUPG_DEFAULT_HOMEDIR;
+
+
+ /* check whether we have a config file on the commandline */
+ orig_argc = argc;
+ orig_argv = argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
+ while (arg_parse( &pargs, opts))
+ {
+ if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
+ parse_debug++;
+ else if (pargs.r_opt == oOptions)
+ { /* yes there is one, so we do not try the default one, but
+ read the option file when it is encountered at the
+ commandline */
+ default_config = 0;
+ }
+ else if (pargs.r_opt == oNoOptions)
+ default_config = 0; /* --no-options */
+ else if (pargs.r_opt == oHomedir)
+ opt.homedir = pargs.r.ret_str;
+ }
+
+ /* initialize the secure memory. */
+ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+ maybe_setuid = 0;
+
+ /*
+ Now we are now working under our real uid
+ */
+
+
+ if (default_config)
+ configname = make_filename (opt.homedir, "gpg-agent.conf", NULL );
+
+ argc = orig_argc;
+ argv = orig_argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1; /* do not remove the args */
+ next_pass:
+ if (configname)
+ {
+ configlineno = 0;
+ configfp = fopen (configname, "r");
+ if (!configfp)
+ {
+ if (default_config)
+ {
+ if( parse_debug )
+ log_info (_("NOTE: no default option file `%s'\n"),
+ configname );
+ }
+ else
+ {
+ log_error (_("option file `%s': %s\n"),
+ configname, strerror(errno) );
+ exit(2);
+ }
+ xfree (configname);
+ configname = NULL;
+ }
+ if (parse_debug && configname )
+ log_info (_("reading options from `%s'\n"), configname );
+ default_config = 0;
+ }
+
+ while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
+ {
+ if (parse_rereadable_options (&pargs))
+ continue; /* Already handled */
+ switch (pargs.r_opt)
+ {
+ case oBatch: opt.batch=1; break;
+
+ case oDebugWait: debug_wait = pargs.r.ret_int; break;
+
+ case oOptions:
+ /* config files may not be nested (silently ignore them) */
+ if (!configfp)
+ {
+ xfree(configname);
+ configname = xstrdup(pargs.r.ret_str);
+ goto next_pass;
+ }
+ break;
+ case oNoGreeting: nogreeting = 1; break;
+ case oNoVerbose: opt.verbose = 0; break;
+ case oNoOptions: break; /* no-options */
+ case oHomedir: opt.homedir = pargs.r.ret_str; break;
+ case oNoDetach: nodetach = 1; break;
+ case oLogFile: logfile = pargs.r.ret_str; break;
+ case oCsh: csh_style = 1; break;
+ case oSh: csh_style = 0; break;
+ case oServer: pipe_server = 1; break;
+ case oDaemon: is_daemon = 1; break;
+ case oDisablePth: disable_pth = 1; break;
+
+ case oDisplay: default_display = xstrdup (pargs.r.ret_str); break;
+ case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break;
+ case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break;
+ case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break;
+ case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str); break;
+
+ case oKeepTTY: opt.keep_tty = 1; break;
+ case oKeepDISPLAY: opt.keep_display = 1; break;
+
+ default : pargs.err = configfp? 1:2; break;
+ }
+ }
+ if (configfp)
+ {
+ fclose( configfp );
+ configfp = NULL;
+ /* Keep a copy of the name so that it can be read on SIGHUP. */
+ config_filename = configname;
+ configname = NULL;
+ goto next_pass;
+ }
+ xfree (configname);
+ configname = NULL;
+ if (log_get_errorcount(0))
+ exit(2);
+ if (nogreeting )
+ greeting = 0;
+
+ if (greeting)
+ {
+ fprintf (stderr, "%s %s; %s\n",
+ strusage(11), strusage(13), strusage(14) );
+ fprintf (stderr, "%s\n", strusage(15) );
+ }
+#ifdef IS_DEVELOPMENT_VERSION
+ log_info ("NOTE: this is a development version!\n");
+#endif
+
+
+ if (atexit (cleanup))
+ {
+ log_error ("atexit failed\n");
+ cleanup ();
+ exit (1);
+ }
+
+ create_directories ();
+
+ if (debug_wait && pipe_server)
+ {
+ log_debug ("waiting for debugger - my pid is %u .....\n",
+ (unsigned int)getpid());
+ sleep (debug_wait);
+ log_debug ("... okay\n");
+ }
+
+ if (!pipe_server && !is_daemon)
+ log_info (_("please use the option `--daemon'"
+ " to run the program in the background\n"));
+
+#ifdef ENABLE_NLS
+ /* gpg-agent usdually does not ooutput any messages becuase it runs
+ in the background. For log files it is acceptable to have
+ messages always encoded in utf-8. We switch here to utf-8, so
+ that commands like --help still give native messages. It is far
+ easier to swicthnonly once instead of for every message and it
+ actually helps when more then one thread is active (avoids
+ required an extra copy step). */
+ bind_textdomain_codeset (PACKAGE, "UTF-8");
+#endif
+
+ /* now start with logging to a file if this is desired */
+ if (logfile)
+ {
+ log_set_file (logfile);
+ log_set_prefix (NULL, 1|2|4);
+ }
+
+ /* Make sure that we have a default ttyname. */
+ if (!default_ttyname && ttyname (1))
+ default_ttyname = xstrdup (ttyname (1));
+ if (!default_ttytype && getenv ("TERM"))
+ default_ttytype = xstrdup (getenv ("TERM"));
+
+ if (pipe_server)
+ { /* this is the simple pipe based server */
+ start_command_handler (-1, -1);
+ }
+ else if (!is_daemon)
+ ;
+ else
+ { /* regular server mode */
+ int fd;
+ pid_t pid;
+ int len;
+ struct sockaddr_un serv_addr;
+ char *p;
+
+ /* Remove the DISPLAY variable so that a pinentry does not
+ default to a specific display. There is still a default
+ display when gpg-agent weas started using --display or a
+ client requested this using an OPTION command. */
+ if (!opt.keep_display)
+ unsetenv ("DISPLAY");
+
+ *socket_name = 0;
+ snprintf (socket_name, DIM(socket_name)-1,
+ "/tmp/gpg-XXXXXX/S.gpg-agent");
+ socket_name[DIM(socket_name)-1] = 0;
+ p = strrchr (socket_name, '/');
+ if (!p)
+ BUG ();
+ *p = 0;;
+ if (!mkdtemp(socket_name))
+ {
+ log_error ("can't create directory `%s': %s\n",
+ socket_name, strerror(errno) );
+ exit (1);
+ }
+ *p = '/';
+
+ if (strchr (socket_name, ':') )
+ {
+ log_error ("colons are not allowed in the socket name\n");
+ exit (1);
+ }
+ if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path )
+ {
+ log_error ("name of socket too long\n");
+ exit (1);
+ }
+
+
+ fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ {
+ log_error ("can't create socket: %s\n", strerror(errno) );
+ exit (1);
+ }
+
+ memset (&serv_addr, 0, sizeof serv_addr);
+ serv_addr.sun_family = AF_UNIX;
+ strcpy (serv_addr.sun_path, socket_name);
+ len = (offsetof (struct sockaddr_un, sun_path)
+ + strlen(serv_addr.sun_path) + 1);
+
+ if (bind (fd, (struct sockaddr*)&serv_addr, len) == -1)
+ {
+ log_error ("error binding socket to `%s': %s\n",
+ serv_addr.sun_path, strerror (errno) );
+ close (fd);
+ exit (1);
+ }
+
+ if (listen (fd, 5 ) == -1)
+ {
+ log_error ("listen() failed: %s\n", strerror (errno));
+ close (fd);
+ exit (1);
+ }
+
+ if (opt.verbose)
+ log_info ("listening on socket `%s'\n", socket_name );
+
+
+ fflush (NULL);
+ pid = fork ();
+ if (pid == (pid_t)-1)
+ {
+ log_fatal ("fork failed: %s\n", strerror (errno) );
+ exit (1);
+ }
+ else if (pid)
+ { /* we are the parent */
+ char *infostr;
+
+ close (fd);
+
+ /* create the info string: <name>:<pid>:<protocol_version> */
+ if (asprintf (&infostr, "GPG_AGENT_INFO=%s:%lu:1",
+ socket_name, (ulong)pid ) < 0)
+ {
+ log_error ("out of core\n");
+ kill (pid, SIGTERM);
+ exit (1);
+ }
+ *socket_name = 0; /* don't let cleanup() remove the socket -
+ the child should do this from now on */
+ if (argc)
+ { /* run the program given on the commandline */
+ if (putenv (infostr))
+ {
+ log_error ("failed to set environment: %s\n",
+ strerror (errno) );
+ kill (pid, SIGTERM );
+ exit (1);
+ }
+ execvp (argv[0], argv);
+ log_error ("failed to run the command: %s\n", strerror (errno));
+ kill (pid, SIGTERM);
+ exit (1);
+ }
+ else
+ {
+ /* print the environment string, so that the caller can use
+ shell's eval to set it */
+ if (csh_style)
+ {
+ *strchr (infostr, '=') = ' ';
+ printf ( "setenv %s\n", infostr);
+ }
+ else
+ {
+ printf ( "%s; export GPG_AGENT_INFO;\n", infostr);
+ }
+ free (infostr);
+ exit (0);
+ }
+ /*NEVER REACHED*/
+ } /* end parent */
+
+
+ /* this is the child */
+
+ /* detach from tty and put process into a new session */
+ if (!nodetach )
+ {
+ int i;
+
+ /* close stdin, stdout and stderr unless it is the log stream */
+ for (i=0; i <= 2; i++)
+ {
+ if ( log_get_fd () != i)
+ close (i);
+ }
+ if (setsid() == -1)
+ {
+ log_error ("setsid() failed: %s\n", strerror(errno) );
+ cleanup ();
+ exit (1);
+ }
+ opt.running_detached = 1;
+ }
+
+ if (chdir("/"))
+ {
+ log_error ("chdir to / failed: %s\n", strerror (errno));
+ exit (1);
+ }
+
+
+#ifdef USE_GNU_PTH
+ if (!disable_pth)
+ {
+ struct sigaction sa;
+
+ sa.sa_handler = SIG_IGN;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction (SIGPIPE, &sa, NULL);
+ handle_connections (fd);
+ }
+ else
+#endif /*!USE_GNU_PTH*/
+ /* setup signals */
+ {
+ struct sigaction oact, nact;
+
+ nact.sa_handler = cleanup_sh;
+ sigemptyset (&nact.sa_mask);
+ nact.sa_flags = 0;
+
+ sigaction (SIGHUP, NULL, &oact);
+ if (oact.sa_handler != SIG_IGN)
+ sigaction (SIGHUP, &nact, NULL);
+ sigaction( SIGTERM, NULL, &oact );
+ if (oact.sa_handler != SIG_IGN)
+ sigaction (SIGTERM, &nact, NULL);
+ nact.sa_handler = SIG_IGN;
+ sigaction (SIGPIPE, &nact, NULL);
+ sigaction (SIGINT, &nact, NULL);
+
+ start_command_handler (fd, -1);
+ }
+ close (fd);
+ }
+
+ return 0;
+}
+
+void
+agent_exit (int rc)
+{
+ /*FIXME: update_random_seed_file();*/
+#if 1
+ /* at this time a bit annoying */
+ if (opt.debug & DBG_MEMSTAT_VALUE)
+ {
+ gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
+ gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
+ }
+ if (opt.debug)
+ gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
+#endif
+ gcry_control (GCRYCTL_TERM_SECMEM );
+ rc = rc? rc : log_get_errorcount(0)? 2 : 0;
+ exit (rc);
+}
+
+
+void
+agent_init_default_ctrl (struct server_control_s *ctrl)
+{
+ /* Note we ignore malloc errors because we can't do much about it
+ and the request will fail anyway shortly after this
+ initialization. */
+ if (ctrl->display)
+ free (ctrl->display);
+ ctrl->display = default_display? strdup (default_display) : NULL;
+
+ if (ctrl->ttyname)
+ free (ctrl->ttyname);
+ ctrl->ttyname = default_ttyname? strdup (default_ttyname) : NULL;
+
+ if (ctrl->ttytype)
+ free (ctrl->ttytype);
+ ctrl->ttytype = default_ttytype? strdup (default_ttytype) : NULL;
+
+ if (ctrl->lc_ctype)
+ free (ctrl->lc_ctype);
+ ctrl->lc_ctype = default_lc_ctype? strdup (default_lc_ctype) : NULL;
+
+ if (ctrl->lc_messages)
+ free (ctrl->lc_messages);
+ ctrl->lc_messages = default_lc_messages? strdup (default_lc_messages) : NULL;
+}
+
+
+/* Reread parts of the configuration. Note, that this function is
+ obviously not thread-safe and should only be called from the PTH
+ signal handler.
+
+ Fixme: Due to the way the argument parsing works, we create a
+ memory leak here for all string type arguments. There is currently
+ no clean way to tell whether the memory for the argument has been
+ allocated or points into the process' original arguments. Unless
+ we have a mechanism to tell this, we need to live on with this. */
+static void
+reread_configuration (void)
+{
+ ARGPARSE_ARGS pargs;
+ FILE *fp;
+ unsigned int configlineno = 0;
+ int dummy;
+
+ if (!config_filename)
+ return; /* No config file. */
+
+ fp = fopen (config_filename, "r");
+ if (!fp)
+ {
+ log_error (_("option file `%s': %s\n"),
+ config_filename, strerror(errno) );
+ return;
+ }
+
+ parse_rereadable_options (NULL); /* Start from the default values. */
+
+ memset (&pargs, 0, sizeof pargs);
+ dummy = 0;
+ pargs.argc = &dummy;
+ pargs.flags = 1; /* do not remove the args */
+ while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) )
+ {
+ if (pargs.r_opt < -1)
+ pargs.err = 1; /* Print a warning. */
+ else /* Try to parse this option - ignore unchangeable ones. */
+ parse_rereadable_options (&pargs);
+ }
+ fclose (fp);
+}
+
+
+static void
+create_private_keys_directory (const char *home)
+{
+ char *fname;
+ struct stat statbuf;
+
+ fname = make_filename (home, GNUPG_PRIVATE_KEYS_DIR, NULL);
+ if (stat (fname, &statbuf) && errno == ENOENT)
+ {
+ if (mkdir (fname, S_IRUSR|S_IWUSR|S_IXUSR ))
+ log_error (_("can't create directory `%s': %s\n"),
+ fname, strerror(errno) );
+ else if (!opt.quiet)
+ log_info (_("directory `%s' created\n"), fname);
+ }
+ xfree (fname);
+}
+
+/* Create the directory only if the supplied directory name is the
+ same as the default one. This way we avoid to create arbitrary
+ directories when a non-default home directory is used. To cope
+ with HOME, we compare only the suffix if we see that the default
+ homedir does start with a tilde. We don't stop here in case of
+ problems because other functions will throw an error anyway.*/
+static void
+create_directories (void)
+{
+ struct stat statbuf;
+ const char *defhome = GNUPG_DEFAULT_HOMEDIR;
+ char *home;
+
+ home = make_filename (opt.homedir, NULL);
+ if ( stat (home, &statbuf) )
+ {
+ if (errno == ENOENT)
+ {
+ if ( (*defhome == '~'
+ && (strlen (home) >= strlen (defhome+1)
+ && !strcmp (home + strlen(home)
+ - strlen (defhome+1), defhome+1)))
+ || (*defhome != '~' && !strcmp (home, defhome) )
+ )
+ {
+ if (mkdir (home, S_IRUSR|S_IWUSR|S_IXUSR ))
+ log_error (_("can't create directory `%s': %s\n"),
+ home, strerror(errno) );
+ else
+ {
+ if (!opt.quiet)
+ log_info (_("directory `%s' created\n"), home);
+ create_private_keys_directory (home);
+ }
+ }
+ }
+ else
+ log_error ("error stat-ing `%s': %s\n", home, strerror (errno));
+ }
+ else if ( !S_ISDIR(statbuf.st_mode))
+ {
+ log_error ("can't use `%s' as home directory\n", home);
+ }
+ else /* exists and is a directory. */
+ {
+ create_private_keys_directory (home);
+ }
+ xfree (home);
+}
+
+
+
+#ifdef USE_GNU_PTH
+static void
+handle_signal (int signo)
+{
+ switch (signo)
+ {
+ case SIGHUP:
+ log_info ("SIGHUP received - "
+ "re-reading configuration and flushing cache\n");
+ agent_flush_cache ();
+ reread_configuration ();
+ break;
+
+ case SIGUSR1:
+ if (opt.verbose < 5)
+ opt.verbose++;
+ log_info ("SIGUSR1 received - verbosity set to %d\n", opt.verbose);
+ break;
+
+ case SIGUSR2:
+ if (opt.verbose)
+ opt.verbose--;
+ log_info ("SIGUSR2 received - verbosity set to %d\n", opt.verbose );
+ break;
+
+ case SIGTERM:
+ if (!shutdown_pending)
+ log_info ("SIGTERM received - shutting down ...\n");
+ else
+ log_info ("SIGTERM received - still %ld running threads\n",
+ pth_ctrl( PTH_CTRL_GETTHREADS ));
+ shutdown_pending++;
+ if (shutdown_pending > 2)
+ {
+ log_info ("shutdown forced\n");
+ log_info ("%s %s stopped\n", strusage(11), strusage(13) );
+ cleanup ();
+ agent_exit (0);
+ }
+ break;
+
+ case SIGINT:
+ log_info ("SIGINT received - immediate shutdown\n");
+ log_info( "%s %s stopped\n", strusage(11), strusage(13));
+ cleanup ();
+ agent_exit (0);
+ break;
+
+ default:
+ log_info ("signal %d received - no action defined\n", signo);
+ }
+}
+
+
+static void *
+start_connection_thread (void *arg)
+{
+ int fd = (int)arg;
+
+ if (opt.verbose)
+ log_info ("handler for fd %d started\n", fd);
+ start_command_handler (-1, fd);
+ if (opt.verbose)
+ log_info ("handler for fd %d terminated\n", fd);
+
+ return NULL;
+}
+
+
+static void
+handle_connections (int listen_fd)
+{
+ pth_attr_t tattr;
+ pth_event_t ev;
+ sigset_t sigs;
+ int signo;
+ struct sockaddr_un paddr;
+ socklen_t plen = sizeof( paddr );
+ int fd;
+
+ tattr = pth_attr_new();
+ pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0);
+ pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 32*1024);
+ pth_attr_set (tattr, PTH_ATTR_NAME, "gpg-agent");
+
+ sigemptyset (&sigs );
+ sigaddset (&sigs, SIGHUP);
+ sigaddset (&sigs, SIGUSR1);
+ sigaddset (&sigs, SIGUSR2);
+ sigaddset (&sigs, SIGINT);
+ sigaddset (&sigs, SIGTERM);
+ ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
+
+ for (;;)
+ {
+ if (shutdown_pending)
+ {
+ if (pth_ctrl (PTH_CTRL_GETTHREADS) == 1)
+ break; /* ready */
+
+ /* Do not accept anymore connections and wait for existing
+ connections to terminate */
+ signo = 0;
+ pth_wait (ev);
+ if (pth_event_occurred (ev) && signo)
+ handle_signal (signo);
+ continue;
+ }
+
+ fd = pth_accept_ev (listen_fd, (struct sockaddr *)&paddr, &plen, ev);
+ if (fd == -1)
+ {
+#ifdef PTH_STATUS_OCCURRED /* This is Pth 2 */
+ if (pth_event_status (ev) == PTH_STATUS_OCCURRED)
+#else
+ if (pth_event_occurred (ev))
+#endif
+ {
+ handle_signal (signo);
+ continue;
+ }
+ log_error ("accept failed: %s - waiting 1s\n", strerror (errno));
+ pth_sleep(1);
+ continue;
+ }
+
+ if (!pth_spawn (tattr, start_connection_thread, (void*)fd))
+ {
+ log_error ("error spawning connection handler: %s\n",
+ strerror (errno) );
+ close (fd);
+ }
+ }
+
+ pth_event_free (ev, PTH_FREE_ALL);
+ cleanup ();
+ log_info ("%s %s stopped\n", strusage(11), strusage(13));
+}
+#endif /*USE_GNU_PTH*/
diff --git a/agent/learncard.c b/agent/learncard.c
new file mode 100644
index 000000000..28a74f972
--- /dev/null
+++ b/agent/learncard.c
@@ -0,0 +1,448 @@
+/* learncard.c - Handle the LEARN command
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "agent.h"
+#include <assuan.h>
+
+struct keypair_info_s {
+ struct keypair_info_s *next;
+ int no_cert;
+ char *id; /* points into grip */
+ char hexgrip[1];
+};
+typedef struct keypair_info_s *KEYPAIR_INFO;
+
+struct kpinfo_cb_parm_s {
+ int error;
+ KEYPAIR_INFO info;
+};
+
+
+struct certinfo_s {
+ struct certinfo_s *next;
+ int type;
+ int done;
+ char id[1];
+};
+typedef struct certinfo_s *CERTINFO;
+
+struct certinfo_cb_parm_s {
+ int error;
+ CERTINFO info;
+};
+
+
+struct sinfo_s {
+ struct sinfo_s *next;
+ char *data; /* Points into keyword. */
+ char keyword[1];
+};
+typedef struct sinfo_s *SINFO;
+
+struct sinfo_cb_parm_s {
+ int error;;
+ SINFO info;
+};
+
+
+
+static void
+release_keypair_info (KEYPAIR_INFO info)
+{
+ while (info)
+ {
+ KEYPAIR_INFO tmp = info->next;
+ xfree (info);
+ info = tmp;
+ }
+}
+
+static void
+release_certinfo (CERTINFO info)
+{
+ while (info)
+ {
+ CERTINFO tmp = info->next;
+ xfree (info);
+ info = tmp;
+ }
+}
+
+static void
+release_sinfo (SINFO info)
+{
+ while (info)
+ {
+ SINFO tmp = info->next;
+ xfree (info);
+ info = tmp;
+ }
+}
+
+
+
+/* This callback is used by agent_card_learn and passed the content of
+ all KEYPAIRINFO lines. It merely stores this data away */
+static void
+kpinfo_cb (void *opaque, const char *line)
+{
+ struct kpinfo_cb_parm_s *parm = opaque;
+ KEYPAIR_INFO item;
+ char *p;
+
+ if (parm->error)
+ return; /* no need to gather data after an error coccured */
+ item = xtrycalloc (1, sizeof *item + strlen (line));
+ if (!item)
+ {
+ parm->error = out_of_core ();
+ return;
+ }
+ strcpy (item->hexgrip, line);
+ for (p = item->hexgrip; hexdigitp (p); p++)
+ ;
+ if (p == item->hexgrip && *p == 'X' && spacep (p+1))
+ {
+ item->no_cert = 1;
+ p++;
+ }
+ else if ((p - item->hexgrip) != 40 || !spacep (p))
+ { /* not a 20 byte hex keygrip or not followed by a space */
+ parm->error = gpg_error (GPG_ERR_INV_RESPONSE);
+ xfree (item);
+ return;
+ }
+ *p++ = 0;
+ while (spacep (p))
+ p++;
+ item->id = p;
+ while (*p && !spacep (p))
+ p++;
+ if (p == item->id)
+ { /* invalid ID string */
+ parm->error = gpg_error (GPG_ERR_INV_RESPONSE);
+ xfree (item);
+ return;
+ }
+ *p = 0; /* ignore trailing stuff */
+
+ /* store it */
+ item->next = parm->info;
+ parm->info = item;
+}
+
+
+/* This callback is used by agent_card_learn and passed the content of
+ all CERTINFO lines. It merely stores this data away */
+static void
+certinfo_cb (void *opaque, const char *line)
+{
+ struct certinfo_cb_parm_s *parm = opaque;
+ CERTINFO item;
+ int type;
+ char *p, *pend;
+
+ if (parm->error)
+ return; /* no need to gather data after an error coccured */
+
+ type = strtol (line, &p, 10);
+ while (spacep (p))
+ p++;
+ for (pend = p; *pend && !spacep (pend); pend++)
+ ;
+ if (p == pend || !*p)
+ {
+ parm->error = gpg_error (GPG_ERR_INV_RESPONSE);
+ return;
+ }
+ *pend = 0; /* ignore trailing stuff */
+
+ item = xtrycalloc (1, sizeof *item + strlen (p));
+ if (!item)
+ {
+ parm->error = out_of_core ();
+ return;
+ }
+ item->type = type;
+ strcpy (item->id, p);
+ /* store it */
+ item->next = parm->info;
+ parm->info = item;
+}
+
+
+/* This callback is used by agent_card_learn and passed the content of
+ all SINFO lines. It merely stores this data away */
+static void
+sinfo_cb (void *opaque, const char *keyword, size_t keywordlen,
+ const char *data)
+{
+ struct sinfo_cb_parm_s *sparm = opaque;
+ SINFO item;
+
+ if (sparm->error)
+ return; /* no need to gather data after an error coccured */
+
+ item = xtrycalloc (1, sizeof *item + keywordlen + 1 + strlen (data));
+ if (!item)
+ {
+ sparm->error = out_of_core ();
+ return;
+ }
+ memcpy (item->keyword, keyword, keywordlen);
+ item->data = item->keyword + keywordlen;
+ *item->data = 0;
+ item->data++;
+ strcpy (item->data, data);
+ /* store it */
+ item->next = sparm->info;
+ sparm->info = item;
+}
+
+
+/* Create an S-expression with the shadow info. */
+static unsigned char *
+make_shadow_info (const char *serialno, const char *idstring)
+{
+ const char *s;
+ unsigned char *info, *p;
+ char numbuf[21];
+ int n;
+
+ for (s=serialno, n=0; *s && s[1]; s += 2)
+ n++;
+
+ info = p = xtrymalloc (1 + 21 + n
+ + 21 + strlen (idstring) + 1 + 1);
+ *p++ = '(';
+ sprintf (numbuf, "%d:", n);
+ p = stpcpy (p, numbuf);
+ for (s=serialno; *s && s[1]; s += 2)
+ *p++ = xtoi_2 (s);
+ sprintf (numbuf, "%d:", strlen (idstring));
+ p = stpcpy (p, numbuf);
+ p = stpcpy (p, idstring);
+ *p++ = ')';
+ *p = 0;
+ return info;
+}
+
+static int
+send_cert_back (const char *id, void *assuan_context)
+{
+ int rc;
+ char *derbuf;
+ size_t derbuflen;
+
+ rc = agent_card_readcert (id, &derbuf, &derbuflen);
+ if (rc)
+ {
+ log_error ("error reading certificate: %s\n",
+ gpg_strerror (rc));
+ return rc;
+ }
+
+ rc = assuan_send_data (assuan_context, derbuf, derbuflen);
+ xfree (derbuf);
+ if (!rc)
+ rc = assuan_send_data (assuan_context, NULL, 0);
+ if (!rc)
+ rc = assuan_write_line (assuan_context, "END");
+ if (rc)
+ {
+ log_error ("sending certificate failed: %s\n",
+ assuan_strerror (rc));
+ return map_assuan_err (rc);
+ }
+ return 0;
+}
+
+/* Perform the learn operation. If ASSUAN_CONTEXT is not NULL all new
+ certificates are send via Assuan */
+int
+agent_handle_learn (void *assuan_context)
+{
+ int rc;
+ struct kpinfo_cb_parm_s parm;
+ struct certinfo_cb_parm_s cparm;
+ struct sinfo_cb_parm_s sparm;
+ char *serialno = NULL;
+ KEYPAIR_INFO item;
+ SINFO sitem;
+ unsigned char grip[20];
+ char *p;
+ int i;
+ static int certtype_list[] = {
+ 101, /* trusted */
+ 102, /* useful */
+ 100, /* regular */
+ -1 /* end of list */
+ };
+
+
+ memset (&parm, 0, sizeof parm);
+ memset (&cparm, 0, sizeof cparm);
+ memset (&sparm, 0, sizeof sparm);
+
+ /* Check whether a card is present and get the serial number */
+ rc = agent_card_serialno (&serialno);
+ if (rc)
+ goto leave;
+
+ /* now gather all the available info */
+ rc = agent_card_learn (kpinfo_cb, &parm, certinfo_cb, &cparm,
+ sinfo_cb, &sparm);
+ if (!rc && (parm.error || cparm.error || sparm.error))
+ rc = parm.error? parm.error : cparm.error? cparm.error : sparm.error;
+ if (rc)
+ {
+ log_debug ("agent_card_learn failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ log_info ("card has S/N: %s\n", serialno);
+
+ /* Pass on all the collected status information. */
+ if (assuan_context)
+ {
+ for (sitem = sparm.info; sitem; sitem = sitem->next)
+ {
+ assuan_write_status (assuan_context, sitem->keyword, sitem->data);
+ }
+ }
+
+ /* Write out the certificates in a standard order. */
+ for (i=0; certtype_list[i] != -1; i++)
+ {
+ CERTINFO citem;
+ for (citem = cparm.info; citem; citem = citem->next)
+ {
+ if (certtype_list[i] != citem->type)
+ continue;
+
+ if (opt.verbose)
+ log_info (" id: %s (type=%d)\n",
+ citem->id, citem->type);
+
+ if (assuan_context)
+ {
+ rc = send_cert_back (citem->id, assuan_context);
+ if (rc)
+ goto leave;
+ citem->done = 1;
+ }
+ }
+ }
+
+ for (item = parm.info; item; item = item->next)
+ {
+ unsigned char *pubkey, *shdkey;
+ size_t n;
+
+ if (opt.verbose)
+ log_info (" id: %s (grip=%s)\n", item->id, item->hexgrip);
+
+ if (item->no_cert)
+ continue; /* no public key yet available */
+
+ for (p=item->hexgrip, i=0; i < 20; p += 2, i++)
+ grip[i] = xtoi_2 (p);
+
+ if (!agent_key_available (grip))
+ continue;
+
+ /* unknown - store it */
+ rc = agent_card_readkey (item->id, &pubkey);
+ if (rc)
+ {
+ log_debug ("agent_card_readkey failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ {
+ unsigned char *shadow_info = make_shadow_info (serialno, item->id);
+ if (!shadow_info)
+ {
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ xfree (pubkey);
+ goto leave;
+ }
+ rc = agent_shadow_key (pubkey, shadow_info, &shdkey);
+ xfree (shadow_info);
+ }
+ xfree (pubkey);
+ if (rc)
+ {
+ log_error ("shadowing the key failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ n = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
+ assert (n);
+
+ rc = agent_write_private_key (grip, shdkey, n, 0);
+ xfree (shdkey);
+ if (rc)
+ {
+ log_error ("error writing key: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ if (opt.verbose)
+ log_info ("stored\n");
+
+ if (assuan_context)
+ {
+ CERTINFO citem;
+
+ /* only send the certificate if we have not done so before */
+ for (citem = cparm.info; citem; citem = citem->next)
+ {
+ if (!strcmp (citem->id, item->id))
+ break;
+ }
+ if (!citem)
+ {
+ rc = send_cert_back (item->id, assuan_context);
+ if (rc)
+ goto leave;
+ }
+ }
+ }
+
+
+ leave:
+ xfree (serialno);
+ release_keypair_info (parm.info);
+ release_certinfo (cparm.info);
+ release_sinfo (sparm.info);
+ return rc;
+}
+
+
diff --git a/agent/minip12.c b/agent/minip12.c
new file mode 100644
index 000000000..255fef096
--- /dev/null
+++ b/agent/minip12.c
@@ -0,0 +1,1140 @@
+/* minip12.c - A 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <gcrypt.h>
+
+#undef TEST
+
+#ifdef TEST
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#endif
+
+#include "../jnlib/logging.h"
+#include "minip12.h"
+
+#ifndef DIM
+#define DIM(v) (sizeof(v)/sizeof((v)[0]))
+#endif
+
+enum
+{
+ UNIVERSAL = 0,
+ APPLICATION = 1,
+ CONTEXT = 2,
+ PRIVATE = 3
+};
+
+
+enum
+{
+ TAG_NONE = 0,
+ TAG_BOOLEAN = 1,
+ TAG_INTEGER = 2,
+ TAG_BIT_STRING = 3,
+ TAG_OCTET_STRING = 4,
+ TAG_NULL = 5,
+ TAG_OBJECT_ID = 6,
+ TAG_OBJECT_DESCRIPTOR = 7,
+ TAG_EXTERNAL = 8,
+ TAG_REAL = 9,
+ TAG_ENUMERATED = 10,
+ TAG_EMBEDDED_PDV = 11,
+ TAG_UTF8_STRING = 12,
+ TAG_REALTIVE_OID = 13,
+ TAG_SEQUENCE = 16,
+ TAG_SET = 17,
+ TAG_NUMERIC_STRING = 18,
+ TAG_PRINTABLE_STRING = 19,
+ TAG_TELETEX_STRING = 20,
+ TAG_VIDEOTEX_STRING = 21,
+ TAG_IA5_STRING = 22,
+ TAG_UTC_TIME = 23,
+ TAG_GENERALIZED_TIME = 24,
+ TAG_GRAPHIC_STRING = 25,
+ TAG_VISIBLE_STRING = 26,
+ TAG_GENERAL_STRING = 27,
+ TAG_UNIVERSAL_STRING = 28,
+ TAG_CHARACTER_STRING = 29,
+ TAG_BMP_STRING = 30
+};
+
+
+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_pkcs_8ShroudedKeyBag[11] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x02 };
+static unsigned char const oid_pbeWithSHAAnd3_KeyTripleDES_CBC[10] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03 };
+
+static unsigned char const oid_rsaEncryption[9] = {
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
+
+
+static unsigned char const data_3desiter1024[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, 0x04, 0x00 };
+#define DATA_3DESITER1024_SALT_OFF 18
+
+
+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 */
+};
+
+
+/* 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. */
+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 == UNIVERSAL && !ti->tag)
+ ti->length = 0;
+
+ if (ti->length > length)
+ return -1; /* data larger than buffer. */
+
+ *buffer = buf;
+ *size = length;
+ return 0;
+}
+
+
+static int
+string_to_key (int id, char *salt, 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;
+ }
+
+ /* Store salt and password in BUF_I */
+ p = buf_i;
+ for(i=0; i < 64; i++)
+ *p++ = salt [i%8];
+ 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, int iter, const char *pw)
+{
+ unsigned char keybuf[24];
+ int rc;
+
+ if (string_to_key (1, salt, iter, pw, 24, keybuf))
+ return -1;
+ rc = gcry_cipher_setkey (chd, keybuf, 24);
+ if (rc)
+ {
+ log_error ( "gcry_cipher_setkey failed: %s\n", gpg_strerror (rc));
+ return -1;
+ }
+
+ if (string_to_key (2, salt, 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 void
+crypt_block (unsigned char *buffer, size_t length, char *salt, int iter,
+ const char *pw, int encrypt)
+{
+ gcry_cipher_hd_t chd;
+ int rc;
+
+ rc = gcry_cipher_open (&chd, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0);
+ if (rc)
+ {
+ log_error ( "gcry_cipher_open failed: %s\n", gpg_strerror(-1));
+ return;
+ }
+ if (set_key_iv (chd, salt, iter, pw))
+ goto leave;
+
+ rc = encrypt? gcry_cipher_encrypt (chd, buffer, length, NULL, 0)
+ : gcry_cipher_decrypt (chd, buffer, length, NULL, 0);
+
+ if (rc)
+ {
+ log_error ( "en/de-crytion failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+/* { */
+/* FILE *fp = fopen("inner.der", "wb"); */
+/* fwrite (buffer, 1, length, fp); */
+/* fclose (fp); */
+/* } */
+
+ leave:
+ gcry_cipher_close (chd);
+}
+
+
+
+
+static int
+parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
+ int startoffset)
+{
+ struct tag_info ti;
+ const unsigned char *p = buffer;
+ size_t n = length;
+ const char *where;
+
+ where = "start";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.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);
+
+ /* fixme: continue parsing */
+
+ return 0;
+ bailout:
+ log_error ("encrptedData error at \"%s\", offset %u\n",
+ where, (p - buffer)+startoffset);
+ return -1;
+}
+
+static gcry_mpi_t *
+parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
+ const char *pw)
+{
+ int rc;
+ struct tag_info ti;
+ const unsigned char *p = buffer;
+ size_t n = length;
+ const char *where;
+ char salt[8];
+ unsigned int iter;
+ int len;
+ unsigned char *plain = NULL;
+ gcry_mpi_t *result = NULL;
+ int result_count, i;
+
+ where = "start";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != CONTEXT || ti.tag)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OCTET_STRING)
+ goto bailout;
+
+ where = "data.outerseqs";
+ 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;
+
+ where = "data.objectidentifier";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OBJECT_ID
+ || ti.length != DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag)
+ || memcmp (p, oid_pkcs_12_pkcs_8ShroudedKeyBag,
+ DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag)))
+ goto bailout;
+ p += DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag);
+ n -= DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag);
+
+ where = "shrouded,outerseqs";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.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 || ti.tag != TAG_OBJECT_ID
+ || ti.length != DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)
+ || memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC,
+ DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)))
+ goto bailout;
+ p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC);
+ n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC);
+
+ where = "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 )
+ goto bailout;
+ memcpy (salt, p, 8);
+ p += 8;
+ n -= 8;
+ 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 = "3des-ciphertext";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class || ti.tag != TAG_OCTET_STRING || !ti.length )
+ goto bailout;
+
+ log_info ("%lu bytes of 3DES encrypted text\n", ti.length);
+
+ plain = gcry_malloc_secure (ti.length);
+ if (!plain)
+ {
+ log_error ("error allocating decryption buffer\n");
+ goto bailout;
+ }
+ memcpy (plain, p, ti.length);
+ crypt_block (plain, ti.length, salt, iter, pw, 0);
+ n = ti.length;
+ startoffset = 0;
+ buffer = p = plain;
+
+ where = "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
+ || 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;
+
+ result = gcry_calloc (10, sizeof *result);
+ if (!result)
+ {
+ log_error ( "error allocating result array\n");
+ goto bailout;
+ }
+ result_count = 0;
+
+ where = "reading.key-parameters";
+ for (result_count=0; len && result_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 (!result_count && ti.length == 1 && !*p)
+ ; /* ignore the very first one if it is a 0 */
+ else
+ {
+ rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p,
+ ti.length, NULL);
+ if (rc)
+ {
+ log_error ("error parsing key parameter: %s\n",
+ gpg_strerror (rc));
+ goto bailout;
+ }
+ result_count++;
+ }
+ p += ti.length;
+ n -= ti.length;
+ }
+ if (len)
+ goto bailout;
+
+ return result;
+
+ bailout:
+ gcry_free (plain);
+ if (result)
+ {
+ for (i=0; result[i]; i++)
+ gcry_mpi_release (result[i]);
+ gcry_free (result);
+ }
+ log_error ( "data error at \"%s\", offset %u\n",
+ where, (p - buffer) + startoffset);
+ return NULL;
+}
+
+
+/* Parse a PKCS12 object and return an array of MPI representing the
+ secret key parameters. This is a very limited inplementation in
+ that it is only able to look for 3DES encoded enctyptedData and
+ tries to extract the first private key object it finds. In case of
+ an error NULL is returned. */
+gcry_mpi_t *
+p12_parse (const unsigned char *buffer, size_t length, const char *pw)
+{
+ struct tag_info ti;
+ const unsigned char *p = buffer;
+ size_t n = length;
+ const char *where;
+ int bagseqlength, len;
+
+ 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 != CONTEXT || ti.tag)
+ goto bailout;
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != UNIVERSAL || ti.tag != TAG_OCTET_STRING)
+ goto bailout;
+
+ where = "bags";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+ bagseqlength = ti.length;
+ while (bagseqlength)
+ {
+ /*log_debug ( "at offset %u\n", (p - buffer));*/
+ where = "bag-sequence";
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE)
+ goto bailout;
+
+ if (bagseqlength < ti.nhdr)
+ goto bailout;
+ bagseqlength -= ti.nhdr;
+ if (bagseqlength < ti.length)
+ goto bailout;
+ bagseqlength -= ti.length;
+ len = ti.length;
+
+ if (parse_tag (&p, &n, &ti))
+ goto bailout;
+ 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);
+ len -= DIM(oid_encryptedData);
+ where = "bag.encryptedData";
+ if (parse_bag_encrypted_data (p, n, (p - buffer)))
+ goto bailout;
+ }
+ 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);
+ len -= DIM(oid_data);
+ return parse_bag_data (p, n, (p-buffer), pw);
+ }
+ else
+ log_info ( "unknown bag type - skipped\n");
+
+ if (len < 0 || len > n)
+ goto bailout;
+ p += len;
+ n -= len;
+ }
+
+ return NULL;
+ bailout:
+ log_error ("error at \"%s\", offset %u\n", where, (p - buffer));
+ 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. That array is terminated with an NULL object */
+static unsigned char *
+create_final (struct buffer_s *sequences, size_t *r_length)
+{
+ int i;
+ size_t needed = 0;
+ size_t n, outseqlen, notsooutseqlen, out0taglen, octstrlen, inseqlen;
+ unsigned char *result, *p;
+ size_t resultlen;
+
+ for (i=0; sequences[i].buffer; i++)
+ needed += sequences[i].length;
+ /* This goes into a sequences. */
+ inseqlen = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+ /* And encapsulate all in an octet string. */
+ octstrlen = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+ /* And tag it with [0]. */
+ out0taglen = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+ /* Prepend an data OID. */
+ needed += 2 + DIM (oid_data);
+ /* This all into a sequences. */
+ notsooutseqlen = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+ /* Prepend the version integer 3. */
+ needed += 3;
+ /* And the final sequence. */
+ outseqlen = needed;
+ n = compute_tag_length (needed);
+ needed += n;
+
+ result = gcry_malloc (needed);
+ if (!result)
+ {
+ log_error ("error allocating buffer\n");
+ return NULL;
+ }
+ p = result;
+
+ /* Store the very outer sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, outseqlen);
+ /* Store the version integer 3. */
+ *p++ = TAG_INTEGER;
+ *p++ = 1;
+ *p++ = 3;
+ /* Store another sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, notsooutseqlen);
+ /* 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);
+ /* Next comes a context tag. */
+ p = store_tag_length (p, 0xa0, out0taglen);
+ /* And an octet string. */
+ p = store_tag_length (p, TAG_OCTET_STRING, octstrlen);
+ /* And the inner sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, inseqlen);
+ /* And append all the buffers. */
+ for (i=0; sequences[i].buffer; i++)
+ {
+ memcpy (p, sequences[i].buffer, sequences[i].length);
+ p += sequences[i].length;
+ }
+
+ /* Ready. */
+ resultlen = p - result;
+ if (needed != resultlen)
+ log_debug ("length mismatch: %u, %u\n", needed, resultlen);
+
+ *r_length = resultlen;
+ return result;
+}
+
+
+/* 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. */
+unsigned char *
+p12_build (gcry_mpi_t *kparms, const char *pw, size_t *r_length)
+{
+ int rc, i;
+ size_t needed, n;
+ unsigned char *plain, *p, *cipher;
+ size_t plainlen, cipherlen;
+ size_t outseqlen, oidseqlen, octstrlen, inseqlen;
+ size_t out0taglen, in0taglen, outoctstrlen;
+ size_t aseq1len, aseq2len, aseq3len;
+ char salt[8];
+
+ needed = 3; /* The version(?) integer of 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 paramters 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;
+ /* 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;
+ 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;
+ assert (needed == plainlen);
+ /* Append some pad characters; we already allocated extra space. */
+ n = 8 - plainlen % 8;
+ for (;(plainlen % 8); plainlen++)
+ *p++ = n;
+
+ {
+ FILE *fp = fopen("inner-out.der", "wb");
+ fwrite (plain, 1, plainlen, fp);
+ fclose (fp);
+ }
+
+
+ /* Encrypt it and prepend a lot of stupid things. */
+ gcry_randomize (salt, 8, GCRY_STRONG_RANDOM);
+ crypt_block (plain, plainlen, salt, 1024, pw, 1);
+ /* the data goes into an octet string. */
+ needed = compute_tag_length (plainlen);
+ needed += plainlen;
+ /* we prepend the the algorithm identifier (we use a pre-encoded one)*/
+ needed += DIM (data_3desiter1024);
+ /* we put a sequence around. */
+ aseq3len = needed;
+ needed += compute_tag_length (needed);
+ /* Prepend it with a [0] tag. */
+ in0taglen = needed;
+ needed += compute_tag_length (needed);
+ /* Prepend that shroudedKeyBag OID. */
+ needed += 2 + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag);
+ /* Put it all into two sequence. */
+ aseq2len = needed;
+ needed += compute_tag_length ( needed);
+ aseq1len = needed;
+ needed += compute_tag_length (needed);
+ /* This all goes into an octet string. */
+ outoctstrlen = needed;
+ needed += compute_tag_length (needed);
+ /* Prepend it with a [0] tag. */
+ out0taglen = needed;
+ needed += compute_tag_length (needed);
+ /* Prepend the data OID. */
+ needed += 2 + DIM (oid_data);
+ /* And a sequence. */
+ outseqlen = needed;
+ needed += compute_tag_length (needed);
+
+ cipher = gcry_malloc (needed);
+ if (!cipher)
+ {
+ log_error ("error allocating buffer\n");
+ gcry_free (plain);
+ return NULL;
+ }
+ p = cipher;
+ /* Store the first sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, outseqlen);
+ /* 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);
+ /* Next comes a context tag. */
+ p = store_tag_length (p, 0xa0, out0taglen);
+ /* And an octet string. */
+ p = store_tag_length (p, TAG_OCTET_STRING, outoctstrlen);
+ /* Two sequences. */
+ p = store_tag_length (p, TAG_SEQUENCE, aseq1len);
+ p = store_tag_length (p, TAG_SEQUENCE, aseq2len);
+ /* 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);
+ /* Next comes a context tag. */
+ p = store_tag_length (p, 0xa0, in0taglen);
+ /* And a sequence. */
+ p = store_tag_length (p, TAG_SEQUENCE, aseq3len);
+ /* Now for the pre-encoded algorithm indentifier and the salt. */
+ memcpy (p, data_3desiter1024, DIM (data_3desiter1024));
+ memcpy (p + DATA_3DESITER1024_SALT_OFF, salt, 8);
+ p += DIM (data_3desiter1024);
+ /* And finally the octet string with the encrypted data. */
+ p = store_tag_length (p, TAG_OCTET_STRING, plainlen);
+ memcpy (p, plain, plainlen);
+ p += plainlen;
+ cipherlen = p - cipher;
+
+ if (needed != cipherlen)
+ log_debug ("length mismatch: %u, %u\n", needed, cipherlen);
+ gcry_free (plain);
+
+ {
+ struct buffer_s seqlist[2];
+
+ seqlist[0].buffer = cipher;
+ seqlist[0].length = cipherlen;
+ seqlist[1].buffer = NULL;
+ seqlist[1].length = 0;
+
+ cipher = create_final (seqlist, &cipherlen);
+ gcry_free (seqlist[0].buffer);
+ }
+
+ *r_length = cipherlen;
+ return cipher;
+}
+
+
+#ifdef TEST
+int
+main (int argc, char **argv)
+{
+ FILE *fp;
+ struct stat st;
+ char *buf;
+ size_t buflen;
+ GcryMPI *result;
+
+ if (argc != 3)
+ {
+ fprintf (stderr, "usage: testp12 file passphrase\n");
+ return 1;
+ }
+
+ gcry_control (GCRYCTL_DISABLE_SECMEM, NULL);
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, NULL);
+
+ fp = fopen (argv[1], "rb");
+ if (!fp)
+ {
+ fprintf (stderr, "can't open `%s': %s\n", argv[1], strerror (errno));
+ return 1;
+ }
+
+ if (fstat (fileno(fp), &st))
+ {
+ fprintf (stderr, "can't stat `%s': %s\n", argv[1], strerror (errno));
+ return 1;
+ }
+
+ buflen = st.st_size;
+ buf = gcry_malloc (buflen+1);
+ if (!buf || fread (buf, buflen, 1, fp) != 1)
+ {
+ fprintf (stderr, "error reading `%s': %s\n", argv[1], strerror (errno));
+ return 1;
+ }
+ fclose (fp);
+
+ result = p12_parse (buf, buflen, argv[2]);
+ if (result)
+ {
+ int i, rc;
+ char *buf;
+
+ for (i=0; result[i]; i++)
+ {
+ rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, (void**)&buf,
+ NULL, result[i]);
+ if (rc)
+ printf ("%d: [error printing number: %s]\n",
+ i, gpg_strerror (rc));
+ else
+ {
+ printf ("%d: %s\n", i, buf);
+ gcry_free (buf);
+ }
+ }
+ }
+
+ return 0;
+
+}
+#endif /* TEST */
diff --git a/agent/minip12.h b/agent/minip12.h
new file mode 100644
index 000000000..122215549
--- /dev/null
+++ b/agent/minip12.h
@@ -0,0 +1,33 @@
+/* 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef MINIP12_H
+#define MINIP12_H
+
+#include <gcrypt.h>
+
+gcry_mpi_t *p12_parse (const unsigned char *buffer, size_t length,
+ const char *pw);
+
+unsigned char *p12_build (gcry_mpi_t *kparms, const char *pw,
+ size_t *r_length);
+
+
+#endif /*MINIP12_H*/
diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c
new file mode 100644
index 000000000..543a82737
--- /dev/null
+++ b/agent/pkdecrypt.c
@@ -0,0 +1,138 @@
+/* pkdecrypt.c - public key decryption (well, acually using a secret key)
+ * Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "agent.h"
+
+
+/* DECRYPT the stuff in ciphertext which is expected to be a S-Exp.
+ Try to get the key from CTRL and write the decoded stuff back to
+ OUTFP. */
+int
+agent_pkdecrypt (CTRL ctrl, const char *ciphertext, size_t ciphertextlen,
+ FILE *outfp)
+{
+ gcry_sexp_t s_skey = NULL, s_cipher = NULL, s_plain = NULL;
+ unsigned char *shadow_info = NULL;
+ int rc;
+ char *buf = NULL;
+ size_t len;
+
+ if (!ctrl->have_keygrip)
+ {
+ log_error ("speculative decryption not yet supported\n");
+ rc = gpg_error (GPG_ERR_NO_SECKEY);
+ goto leave;
+ }
+
+ rc = gcry_sexp_sscan (&s_cipher, NULL, ciphertext, ciphertextlen);
+ if (rc)
+ {
+ log_error ("failed to convert ciphertext: %s\n", gpg_strerror (rc));
+ rc = gpg_error (GPG_ERR_INV_DATA);
+ goto leave;
+ }
+
+ if (DBG_CRYPTO)
+ {
+ log_printhex ("keygrip:", ctrl->keygrip, 20);
+ log_printhex ("cipher: ", ciphertext, ciphertextlen);
+ }
+ s_skey = agent_key_from_file (ctrl, ctrl->keygrip, &shadow_info, 0);
+ if (!s_skey && !shadow_info)
+ {
+ log_error ("failed to read the secret key\n");
+ rc = gpg_error (GPG_ERR_NO_SECKEY);
+ goto leave;
+ }
+
+ if (!s_skey)
+ { /* divert operation to the smartcard */
+
+ if (!gcry_sexp_canon_len (ciphertext, ciphertextlen, NULL, NULL))
+ {
+ rc = gpg_error (GPG_ERR_INV_SEXP);
+ goto leave;
+ }
+
+ rc = divert_pkdecrypt (ctrl, ciphertext, shadow_info, &buf, &len );
+ if (rc)
+ {
+ log_error ("smartcard decryption failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ /* FIXME: don't use buffering and change the protocol to return
+ a complete S-expression and not just a part. */
+ fprintf (outfp, "%u:", (unsigned int)len);
+ fwrite (buf, 1, len, outfp);
+ putc (0, outfp);
+ }
+ else
+ { /* no smartcard, but a private key */
+ if (DBG_CRYPTO)
+ {
+ log_debug ("skey: ");
+ gcry_sexp_dump (s_skey);
+ }
+
+ rc = gcry_pk_decrypt (&s_plain, s_cipher, s_skey);
+ if (rc)
+ {
+ log_error ("decryption failed: %s\n", gpg_strerror (rc));
+ rc = map_gcry_err (rc);
+ goto leave;
+ }
+
+ if (DBG_CRYPTO)
+ {
+ log_debug ("plain: ");
+ gcry_sexp_dump (s_plain);
+ }
+ len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (len);
+ buf = xmalloc (len);
+ len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, buf, len);
+ assert (len);
+ /* FIXME: we must make sure that no buffering takes place or we are
+ in full control of the buffer memory (easy to do) - should go
+ into assuan. */
+ fwrite (buf, 1, len, outfp);
+ }
+
+
+ leave:
+ gcry_sexp_release (s_skey);
+ gcry_sexp_release (s_plain);
+ gcry_sexp_release (s_cipher);
+ xfree (buf);
+ xfree (shadow_info);
+ return rc;
+}
+
+
diff --git a/agent/pksign.c b/agent/pksign.c
new file mode 100644
index 000000000..fba2c652c
--- /dev/null
+++ b/agent/pksign.c
@@ -0,0 +1,185 @@
+/* pksign.c - public key signing (well, acually using a secret key)
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "agent.h"
+
+
+static int
+do_encode_md (const unsigned char *digest, size_t digestlen, int algo,
+ unsigned int nbits, gcry_mpi_t *r_val)
+{
+ int nframe = (nbits+7) / 8;
+ byte *frame;
+ int i, n;
+ byte asn[100];
+ size_t asnlen;
+
+ asnlen = DIM(asn);
+ 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);
+ }
+
+ if (digestlen + asnlen + 4 > nframe )
+ {
+ log_error ("can't encode a %d bit MD into a %d bits frame\n",
+ (int)(digestlen*8), (int)nbits);
+ return gpg_error (GPG_ERR_INTERNAL);
+ }
+
+ /* We encode the MD in this way:
+ *
+ * 0 1 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 - digestlen - 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, digest, digestlen ); n += digestlen;
+ assert ( n == nframe );
+ if (DBG_CRYPTO)
+ log_printhex ("encoded hash:", frame, nframe);
+
+ gcry_mpi_scan (r_val, GCRYMPI_FMT_USG, frame, n, &nframe);
+ xfree (frame);
+ return 0;
+}
+
+
+/* SIGN whatever information we have accumulated in CTRL and write it
+ back to OUTFP. */
+int
+agent_pksign (CTRL ctrl, FILE *outfp, int ignore_cache)
+{
+ gcry_sexp_t s_skey = NULL, s_hash = NULL, s_sig = NULL;
+ gcry_mpi_t frame = NULL;
+ unsigned char *shadow_info = NULL;
+ int rc;
+ char *buf = NULL;
+ size_t len;
+
+ if (!ctrl->have_keygrip)
+ return gpg_error (GPG_ERR_NO_SECKEY);
+
+ s_skey = agent_key_from_file (ctrl,
+ ctrl->keygrip, &shadow_info, ignore_cache);
+ if (!s_skey && !shadow_info)
+ {
+ log_error ("failed to read the secret key\n");
+ rc = gpg_error (GPG_ERR_NO_SECKEY);
+ goto leave;
+ }
+
+ if (!s_skey)
+ { /* divert operation to the smartcard */
+ unsigned char *sigbuf;
+
+ rc = divert_pksign (ctrl,
+ ctrl->digest.value,
+ ctrl->digest.valuelen,
+ ctrl->digest.algo,
+ shadow_info, &sigbuf);
+ if (rc)
+ {
+ log_error ("smartcard signing failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ len = gcry_sexp_canon_len (sigbuf, 0, NULL, NULL);
+ assert (len);
+ buf = sigbuf;
+ }
+ else
+ { /* no smartcard, but a private key */
+
+ /* put the hash into a sexp */
+ rc = do_encode_md (ctrl->digest.value,
+ ctrl->digest.valuelen,
+ ctrl->digest.algo,
+ gcry_pk_get_nbits (s_skey),
+ &frame);
+ if (rc)
+ goto leave;
+ if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) )
+ BUG ();
+
+ if (DBG_CRYPTO)
+ {
+ log_debug ("skey: ");
+ gcry_sexp_dump (s_skey);
+ }
+
+ /* sign */
+ rc = gcry_pk_sign (&s_sig, s_hash, s_skey);
+ if (rc)
+ {
+ log_error ("signing failed: %s\n", gpg_strerror (rc));
+ rc = map_gcry_err (rc);
+ goto leave;
+ }
+
+ if (DBG_CRYPTO)
+ {
+ log_debug ("result: ");
+ gcry_sexp_dump (s_sig);
+ }
+
+ len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (len);
+ buf = xmalloc (len);
+ len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len);
+ assert (len);
+ }
+
+ /* FIXME: we must make sure that no buffering takes place or we are
+ in full control of the buffer memory (easy to do) - should go
+ into assuan. */
+ fwrite (buf, 1, len, outfp);
+
+ leave:
+ gcry_sexp_release (s_skey);
+ gcry_sexp_release (s_hash);
+ gcry_sexp_release (s_sig);
+ gcry_mpi_release (frame);
+ xfree (buf);
+ xfree (shadow_info);
+ return rc;
+}
+
+
diff --git a/agent/protect-tool.c b/agent/protect-tool.c
new file mode 100644
index 000000000..e518c5672
--- /dev/null
+++ b/agent/protect-tool.c
@@ -0,0 +1,977 @@
+/* protect-tool.c - A tool to test the secret key protection
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define JNLIB_NEED_LOG_LOGV
+#include "agent.h"
+#include "minip12.h"
+#include "simple-pwquery.h"
+#include "i18n.h"
+
+enum cmd_and_opt_values
+{ aNull = 0,
+ oVerbose = 'v',
+ oArmor = 'a',
+ oPassphrase = 'P',
+
+ oProtect = 'p',
+ oUnprotect = 'u',
+
+ oNoVerbose = 500,
+ oShadow,
+ oShowShadowInfo,
+ oShowKeygrip,
+
+ oP12Import,
+ oP12Export,
+ oStore,
+ oForce,
+
+aTest };
+
+struct rsa_secret_key_s
+ {
+ gcry_mpi_t n; /* public modulus */
+ gcry_mpi_t e; /* public exponent */
+ gcry_mpi_t d; /* exponent */
+ gcry_mpi_t p; /* prime p. */
+ gcry_mpi_t q; /* prime q. */
+ gcry_mpi_t u; /* inverse of p mod q. */
+ };
+
+
+static int opt_armor;
+static int opt_store;
+static int opt_force;
+static const char *passphrase;
+
+static const char *get_passphrase (void);
+static int store_private_key (const unsigned char *grip,
+ const void *buffer, size_t length, int force);
+
+
+static ARGPARSE_OPTS opts[] = {
+
+ { 301, NULL, 0, N_("@Options:\n ") },
+
+ { oVerbose, "verbose", 0, "verbose" },
+ { oArmor, "armor", 0, "write output in advanced format" },
+ { oPassphrase, "passphrase", 2, "|STRING|use passphrase STRING" },
+ { oProtect, "protect", 256, "protect a private key"},
+ { oUnprotect, "unprotect", 256, "unprotect a private key"},
+ { oShadow, "shadow", 256, "create a shadow entry for a priblic key"},
+ { oShowShadowInfo, "show-shadow-info", 256, "return the shadow info"},
+ { oShowKeygrip, "show-keygrip", 256, "show the \"keygrip\""},
+
+ { oP12Import, "p12-import", 256, "import a PKCS-12 encoded private key"},
+ { oP12Export, "p12-export", 256, "export a private key PKCS-12 encoded"},
+ { oStore, "store", 0, "store the created key in the appropriate place"},
+ { oForce, "force", 0, "force overwriting"},
+ {0}
+};
+
+static const char *
+my_strusage (int level)
+{
+ const char *p;
+ switch (level)
+ {
+ case 11: p = "gpg-protect-tool (GnuPG)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
+ break;
+ case 1:
+ case 40: p = _("Usage: gpg-protect-tool [options] (-h for help)\n");
+ break;
+ case 41: p = _("Syntax: gpg-protect-tool [options] [args]]\n"
+ "Secret key maintenance tool\n");
+ break;
+
+ default: p = NULL;
+ }
+ return p;
+}
+
+
+
+static void
+i18n_init (void)
+{
+#ifdef USE_SIMPLE_GETTEXT
+ set_gettext_file( PACKAGE );
+#else
+#ifdef ENABLE_NLS
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+#endif
+#endif
+}
+
+
+
+/* Used by gcry for logging */
+static void
+my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr)
+{
+ /* translate the log levels */
+ switch (level)
+ {
+ case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break;
+ case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break;
+ case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break;
+ case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break;
+ case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break;
+ case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break;
+ case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break;
+ default: level = JNLIB_LOG_ERROR; break; }
+ log_logv (level, fmt, arg_ptr);
+}
+
+
+/* static void */
+/* print_mpi (const char *text, gcry_mpi_t a) */
+/* { */
+/* char *buf; */
+/* void *bufaddr = &buf; */
+/* int rc; */
+
+/* rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, bufaddr, NULL, a); */
+/* if (rc) */
+/* log_info ("%s: [error printing number: %s]\n", text, gpg_strerror (rc)); */
+/* else */
+/* { */
+/* log_info ("%s: %s\n", text, buf); */
+/* gcry_free (buf); */
+/* } */
+/* } */
+
+
+
+static unsigned char *
+make_canonical (const char *fname, const char *buf, size_t buflen)
+{
+ int rc;
+ size_t erroff, len;
+ gcry_sexp_t sexp;
+ unsigned char *result;
+
+ rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen);
+ if (rc)
+ {
+ log_error ("invalid S-Expression in `%s' (off=%u): %s\n",
+ fname, (unsigned int)erroff, gpg_strerror (rc));
+ return NULL;
+ }
+ len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (len);
+ result = xmalloc (len);
+ len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, result, len);
+ assert (len);
+ gcry_sexp_release (sexp);
+ return result;
+}
+
+static char *
+make_advanced (const unsigned char *buf, size_t buflen)
+{
+ int rc;
+ size_t erroff, len;
+ gcry_sexp_t sexp;
+ unsigned char *result;
+
+ rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen);
+ if (rc)
+ {
+ log_error ("invalid canonical S-Expression (off=%u): %s\n",
+ (unsigned int)erroff, gpg_strerror (rc));
+ return NULL;
+ }
+ len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
+ assert (len);
+ result = xmalloc (len);
+ len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len);
+ assert (len);
+ gcry_sexp_release (sexp);
+ return result;
+}
+
+
+static char *
+read_file (const char *fname, size_t *r_length)
+{
+ FILE *fp;
+ struct stat st;
+ char *buf;
+ size_t buflen;
+
+ fp = fopen (fname, "rb");
+ if (!fp)
+ {
+ log_error ("can't open `%s': %s\n", fname, strerror (errno));
+ return NULL;
+ }
+
+ if (fstat (fileno(fp), &st))
+ {
+ log_error ("can't stat `%s': %s\n", fname, strerror (errno));
+ fclose (fp);
+ return NULL;
+ }
+
+ buflen = st.st_size;
+ buf = xmalloc (buflen+1);
+ if (fread (buf, buflen, 1, fp) != 1)
+ {
+ log_error ("error reading `%s': %s\n", fname, strerror (errno));
+ fclose (fp);
+ xfree (buf);
+ return NULL;
+ }
+ fclose (fp);
+
+ *r_length = buflen;
+ return buf;
+}
+
+
+static unsigned char *
+read_key (const char *fname)
+{
+ char *buf;
+ size_t buflen;
+ unsigned char *key;
+
+ buf = read_file (fname, &buflen);
+ if (!buf)
+ return NULL;
+ key = make_canonical (fname, buf, buflen);
+ xfree (buf);
+ return key;
+}
+
+
+
+static void
+read_and_protect (const char *fname)
+{
+ int rc;
+ unsigned char *key;
+ unsigned char *result;
+ size_t resultlen;
+
+ key = read_key (fname);
+ if (!key)
+ return;
+
+ rc = agent_protect (key, get_passphrase (), &result, &resultlen);
+ xfree (key);
+ if (rc)
+ {
+ log_error ("protecting the key failed: %s\n", gpg_strerror (rc));
+ return;
+ }
+
+ if (opt_armor)
+ {
+ char *p = make_advanced (result, resultlen);
+ xfree (result);
+ if (!p)
+ return;
+ result = p;
+ resultlen = strlen (p);
+ }
+
+ fwrite (result, resultlen, 1, stdout);
+ xfree (result);
+}
+
+
+static void
+read_and_unprotect (const char *fname)
+{
+ int rc;
+ unsigned char *key;
+ unsigned char *result;
+ size_t resultlen;
+
+ key = read_key (fname);
+ if (!key)
+ return;
+
+ rc = agent_unprotect (key, get_passphrase (), &result, &resultlen);
+ xfree (key);
+ if (rc)
+ {
+ log_error ("unprotecting the key failed: %s\n", gpg_strerror (rc));
+ return;
+ }
+
+ if (opt_armor)
+ {
+ char *p = make_advanced (result, resultlen);
+ xfree (result);
+ if (!p)
+ return;
+ result = p;
+ resultlen = strlen (p);
+ }
+
+ fwrite (result, resultlen, 1, stdout);
+ xfree (result);
+}
+
+
+
+static void
+read_and_shadow (const char *fname)
+{
+ int rc;
+ unsigned char *key;
+ unsigned char *result;
+ size_t resultlen;
+
+ key = read_key (fname);
+ if (!key)
+ return;
+
+ rc = agent_shadow_key (key, "(8:313233342:43)", &result);
+ xfree (key);
+ if (rc)
+ {
+ log_error ("shadowing the key failed: %s\n", gpg_strerror (rc));
+ return;
+ }
+ resultlen = gcry_sexp_canon_len (result, 0, NULL,NULL);
+ assert (resultlen);
+
+ if (opt_armor)
+ {
+ char *p = make_advanced (result, resultlen);
+ xfree (result);
+ if (!p)
+ return;
+ result = p;
+ resultlen = strlen (p);
+ }
+
+ fwrite (result, resultlen, 1, stdout);
+ xfree (result);
+}
+
+static void
+show_shadow_info (const char *fname)
+{
+ int rc;
+ unsigned char *key;
+ const unsigned char *info;
+ size_t infolen;
+
+ key = read_key (fname);
+ if (!key)
+ return;
+
+ rc = agent_get_shadow_info (key, &info);
+ xfree (key);
+ if (rc)
+ {
+ log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc));
+ return;
+ }
+ infolen = gcry_sexp_canon_len (info, 0, NULL,NULL);
+ assert (infolen);
+
+ if (opt_armor)
+ {
+ char *p = make_advanced (info, infolen);
+ if (!p)
+ return;
+ fwrite (p, strlen (p), 1, stdout);
+ xfree (p);
+ }
+ else
+ fwrite (info, infolen, 1, stdout);
+}
+
+
+static void
+show_file (const char *fname)
+{
+ unsigned char *key;
+ size_t keylen;
+ char *p;
+
+ key = read_key (fname);
+ if (!key)
+ return;
+
+ keylen = gcry_sexp_canon_len (key, 0, NULL,NULL);
+ assert (keylen);
+
+ p = make_advanced (key, keylen);
+ xfree (key);
+ if (p)
+ {
+ fwrite (p, strlen (p), 1, stdout);
+ xfree (p);
+ }
+}
+
+static void
+show_keygrip (const char *fname)
+{
+ unsigned char *key;
+ gcry_sexp_t private;
+ unsigned char grip[20];
+ int i;
+
+ key = read_key (fname);
+ if (!key)
+ return;
+
+ if (gcry_sexp_new (&private, key, 0, 0))
+ {
+ log_error ("gcry_sexp_new failed\n");
+ return;
+ }
+ xfree (key);
+
+ if (!gcry_pk_get_keygrip (private, grip))
+ {
+ log_error ("can't calculate keygrip\n");
+ return;
+ }
+ gcry_sexp_release (private);
+
+ for (i=0; i < 20; i++)
+ printf ("%02X", grip[i]);
+ putchar ('\n');
+}
+
+
+static int
+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);
+ /* and must recompute u of course */
+ 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? -1:0;
+}
+
+
+static void
+import_p12_file (const char *fname)
+{
+ char *buf;
+ unsigned char *result;
+ size_t buflen, resultlen;
+ int i;
+ int rc;
+ gcry_mpi_t *kparms;
+ struct rsa_secret_key_s sk;
+ gcry_sexp_t s_key;
+ unsigned char *key;
+ unsigned char grip[20];
+
+ /* fixme: we should release some stuff on error */
+
+ buf = read_file (fname, &buflen);
+ if (!buf)
+ return;
+
+ kparms = p12_parse (buf, buflen, get_passphrase ());
+ xfree (buf);
+ if (!kparms)
+ {
+ log_error ("error parsing or decrypting the PKCS-1 file\n");
+ return;
+ }
+ for (i=0; kparms[i]; i++)
+ ;
+ if (i != 8)
+ {
+ log_error ("invalid structure of private key\n");
+ return;
+ }
+
+
+/* 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];
+ if (rsa_key_check (&sk))
+ return;
+/* 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-expresion from the parameters. */
+ rc = 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);
+ if (rc)
+ {
+ log_error ("failed to created S-expression from key: %s\n",
+ gpg_strerror (rc));
+ return;
+ }
+
+ /* Compute the keygrip. */
+ if (!gcry_pk_get_keygrip (s_key, grip))
+ {
+ log_error ("can't calculate keygrip\n");
+ return;
+ }
+ log_info ("keygrip: ");
+ for (i=0; i < 20; i++)
+ log_printf ("%02X", grip[i]);
+ log_printf ("\n");
+
+ /* convert to canonical encoding */
+ buflen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (buflen);
+ key = gcry_xmalloc_secure (buflen);
+ buflen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_CANON, key, buflen);
+ assert (buflen);
+ gcry_sexp_release (s_key);
+
+
+ rc = agent_protect (key, get_passphrase (), &result, &resultlen);
+ xfree (key);
+ if (rc)
+ {
+ log_error ("protecting the key failed: %s\n", gpg_strerror (rc));
+ return;
+ }
+
+ if (opt_armor)
+ {
+ char *p = make_advanced (result, resultlen);
+ xfree (result);
+ if (!p)
+ return;
+ result = p;
+ resultlen = strlen (p);
+ }
+
+ if (opt_store)
+ store_private_key (grip, result, resultlen, opt_force);
+ else
+ fwrite (result, resultlen, 1, stdout);
+
+ xfree (result);
+}
+
+
+
+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 i, 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. */
+ elems = "nedpqu";
+ array = xcalloc (strlen(elems) + 1, sizeof *array);
+ for (idx=0, s=elems; *s; s++, idx++ )
+ {
+ l2 = gcry_sexp_find_token (list, s, 1);
+ if (!l2)
+ {
+ for (i=0; i<idx; i++)
+ gcry_mpi_release (array[i]);
+ xfree (array);
+ gcry_sexp_release (list);
+ return NULL; /* required parameter not found */
+ }
+ array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
+ gcry_sexp_release (l2);
+ if (!array[idx])
+ {
+ for (i=0; i<idx; i++)
+ gcry_mpi_release (array[i]);
+ xfree (array);
+ gcry_sexp_release (list);
+ return NULL; /* required parameter is invalid */
+ }
+ }
+
+ gcry_sexp_release (list);
+ return array;
+}
+
+
+
+
+static void
+export_p12_file (const char *fname)
+{
+ gcry_mpi_t kparms[9], *kp;
+ unsigned char *key;
+ size_t keylen;
+ gcry_sexp_t private;
+ struct rsa_secret_key_s sk;
+ int i;
+
+ key = read_key (fname);
+ if (!key)
+ return;
+
+ if (gcry_sexp_new (&private, key, 0, 0))
+ {
+ log_error ("gcry_sexp_new failed\n");
+ return;
+ }
+ xfree (key);
+
+ kp = sexp_to_kparms (private);
+ gcry_sexp_release (private);
+ if (!kp)
+ {
+ log_error ("error converting key parameters\n");
+ return;
+ }
+ sk.n = kp[0];
+ sk.e = kp[1];
+ sk.d = kp[2];
+ sk.p = kp[3];
+ sk.q = kp[4];
+ sk.u = kp[5];
+ xfree (kp);
+
+
+ kparms[0] = sk.n;
+ kparms[1] = sk.e;
+ kparms[2] = sk.d;
+ kparms[3] = sk.q;
+ kparms[4] = sk.p;
+ kparms[5] = gcry_mpi_snew (0); /* compute d mod (p-1) */
+ gcry_mpi_sub_ui (kparms[5], kparms[3], 1);
+ gcry_mpi_mod (kparms[5], sk.d, kparms[5]);
+ kparms[6] = gcry_mpi_snew (0); /* compute d mod (q-1) */
+ gcry_mpi_sub_ui (kparms[6], kparms[4], 1);
+ gcry_mpi_mod (kparms[6], sk.d, kparms[6]);
+ kparms[7] = sk.u;
+ kparms[8] = NULL;
+
+ key = p12_build (kparms, get_passphrase (), &keylen);
+ for (i=0; i < 8; i++)
+ gcry_mpi_release (kparms[i]);
+ if (!key)
+ return;
+
+ fwrite (key, keylen, 1, stdout);
+ xfree (key);
+}
+
+
+int
+main (int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ int cmd = 0;
+
+ set_strusage (my_strusage);
+ gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+ log_set_prefix ("gpg-protect-tool", 1);
+ i18n_init ();
+
+ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
+ {
+ log_fatal( _("libgcrypt is too old (need %s, have %s)\n"),
+ NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
+ }
+
+ gcry_set_log_handler (my_gcry_logger, NULL);
+
+ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1; /* do not remove the args */
+ while (arg_parse (&pargs, opts) )
+ {
+ switch (pargs.r_opt)
+ {
+ case oVerbose: opt.verbose++; break;
+ case oArmor: opt_armor=1; break;
+
+ case oProtect: cmd = oProtect; break;
+ case oUnprotect: cmd = oUnprotect; break;
+ case oShadow: cmd = oShadow; break;
+ case oShowShadowInfo: cmd = oShowShadowInfo; break;
+ case oShowKeygrip: cmd = oShowKeygrip; break;
+ case oP12Import: cmd = oP12Import; break;
+ case oP12Export: cmd = oP12Export; break;
+
+ case oPassphrase: passphrase = pargs.r.ret_str; break;
+ case oStore: opt_store = 1; break;
+ case oForce: opt_force = 1; break;
+
+ default : pargs.err = 2; break;
+ }
+ }
+ if (log_get_errorcount(0))
+ exit(2);
+
+ if (argc != 1)
+ usage (1);
+
+ if (cmd == oProtect)
+ read_and_protect (*argv);
+ else if (cmd == oUnprotect)
+ read_and_unprotect (*argv);
+ else if (cmd == oShadow)
+ read_and_shadow (*argv);
+ else if (cmd == oShowShadowInfo)
+ show_shadow_info (*argv);
+ else if (cmd == oShowKeygrip)
+ show_keygrip (*argv);
+ else if (cmd == oP12Import)
+ import_p12_file (*argv);
+ else if (cmd == oP12Export)
+ export_p12_file (*argv);
+ else
+ show_file (*argv);
+
+ agent_exit (0);
+ return 8; /*NOTREACHED*/
+}
+
+void
+agent_exit (int rc)
+{
+ rc = rc? rc : log_get_errorcount(0)? 2 : 0;
+ exit (rc);
+}
+
+
+/* Return the passphrase string and ask the agent if it has not been
+ set from the command line. */
+static const char *
+get_passphrase (void)
+{
+ char *pw;
+ int err;
+
+ if (passphrase)
+ return passphrase;
+
+ pw = simple_pwquery (NULL,NULL,
+ _("Enter passphrase:"),
+ _("Please enter the passphrase or the PIN\n"
+ "needed to complete this operation."),
+ &err);
+ if (!pw)
+ {
+ if (err)
+ log_error ("error while asking for the passphrase\n");
+ else
+ log_info ("cancelled\n");
+ agent_exit (0);
+ }
+ passphrase = pw;
+ return passphrase;
+}
+
+
+static int
+store_private_key (const unsigned char *grip,
+ const void *buffer, size_t length, int force)
+{
+ int i;
+ const char *homedir;
+ char *fname;
+ FILE *fp;
+ char hexgrip[40+4+1];
+
+ for (i=0; i < 20; i++)
+ sprintf (hexgrip+2*i, "%02X", grip[i]);
+ strcpy (hexgrip+40, ".key");
+
+ homedir = getenv("GNUPGHOME");
+ if (!homedir || !*homedir)
+ homedir = GNUPG_DEFAULT_HOMEDIR;
+
+ fname = make_filename (homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+ if (force)
+ fp = fopen (fname, "wb");
+ else
+ {
+ if (!access (fname, F_OK))
+ {
+ log_error ("secret key file `%s' already exists\n", fname);
+ xfree (fname);
+ return -1;
+ }
+ fp = fopen (fname, "wbx"); /* FIXME: the x is a GNU extension - let
+ configure check whether this actually
+ works */
+ }
+
+ if (!fp)
+ {
+ log_error ("can't create `%s': %s\n", fname, strerror (errno));
+ xfree (fname);
+ return -1;
+ }
+
+ if (fwrite (buffer, length, 1, fp) != 1)
+ {
+ log_error ("error writing `%s': %s\n", fname, strerror (errno));
+ fclose (fp);
+ remove (fname);
+ xfree (fname);
+ return -1;
+ }
+ if ( fclose (fp) )
+ {
+ log_error ("error closing `%s': %s\n", fname, strerror (errno));
+ remove (fname);
+ xfree (fname);
+ return -1;
+ }
+ log_info ("secret key stored as `%s'\n", fname);
+
+ xfree (fname);
+ return 0;
+}
diff --git a/agent/protect.c b/agent/protect.c
new file mode 100644
index 000000000..e438d53b4
--- /dev/null
+++ b/agent/protect.c
@@ -0,0 +1,971 @@
+/* protect.c - Un/Protect a secret key
+ * Copyright (C) 1998, 1999, 2000, 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "agent.h"
+
+#include "sexp-parse.h"
+
+#define PROT_CIPHER GCRY_CIPHER_AES
+#define PROT_CIPHER_STRING "aes"
+#define PROT_CIPHER_KEYLEN (128/8)
+
+
+/* A table containing the information needed to create a protected
+ private key */
+static struct {
+ const char *algo;
+ const char *parmlist;
+ int prot_from, prot_to;
+} protect_info[] = {
+ { "rsa", "nedpqu", 2, 5 },
+ { NULL }
+};
+
+
+static int
+hash_passphrase (const char *passphrase, int hashalgo,
+ int s2kmode,
+ const unsigned char *s2ksalt, unsigned long s2kcount,
+ unsigned char *key, size_t keylen);
+
+
+
+/* Calculate the MIC for a private key S-Exp. SHA1HASH should pint to
+ a 20 byte buffer. This function is suitable for any algorithms. */
+static int
+calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash)
+{
+ const unsigned char *hash_begin, *hash_end;
+ const unsigned char *s;
+ size_t n;
+
+ s = plainkey;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "private-key"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ hash_begin = s;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n; /* skip over the algorithm name */
+
+ while (*s == '(')
+ {
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n;
+ if ( *s != ')' )
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ }
+ if (*s != ')')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ hash_end = s;
+
+ gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash,
+ hash_begin, hash_end - hash_begin);
+
+ return 0;
+}
+
+
+
+/* Encrypt the parameter block starting at PROTBEGIN with length
+ PROTLEN using the utf8 encoded key PASSPHRASE and return the entire
+ encrypted block in RESULT or ereturn with an error code. SHA1HASH
+ is the 20 byte SHA-1 hash required for the integrity code.
+
+ The parameter block is expected to be an incomplete S-Expression of
+ the form (example in advanced format):
+
+ (d #046129F..[some bytes not shown]..81#)
+ (p #00e861b..[some bytes not shown]..f1#)
+ (q #00f7a7c..[some bytes not shown]..61#)
+ (u #304559a..[some bytes not shown]..9b#)
+
+ the returned block is the S-Expression:
+
+ (protected mode (parms) encrypted_octet_string)
+
+*/
+static int
+do_encryption (const char *protbegin, size_t protlen,
+ const char *passphrase, const unsigned char *sha1hash,
+ unsigned char **result, size_t *resultlen)
+{
+ gcry_cipher_hd_t hd;
+ const char *modestr = "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc";
+ int blklen, enclen, outlen;
+ char *iv = NULL;
+ int rc;
+ char *outbuf = NULL;
+ char *p;
+ int saltpos, ivpos, encpos;
+
+ rc = gcry_cipher_open (&hd, PROT_CIPHER, GCRY_CIPHER_MODE_CBC,
+ GCRY_CIPHER_SECURE);
+ if (rc)
+ return rc;
+
+
+ /* We need to work on a copy of the data because this makes it
+ easier to add the trailer and the padding and more important we
+ have to prefix the text with 2 parenthesis, so we have to
+ allocate enough space for:
+
+ ((<parameter_list>)(4:hash4:sha120:<hashvalue>)) + padding
+
+ We always append a full block of random bytes as padding but
+ encrypt only what is needed for a full blocksize */
+ blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER);
+ outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen;
+ enclen = outlen/blklen * blklen;
+ outbuf = gcry_malloc_secure (outlen);
+ if (!outbuf)
+ rc = out_of_core ();
+ if (!rc)
+ {
+ /* allocate random bytes to be used as IV, padding and s2k salt*/
+ iv = gcry_random_bytes (blklen*2+8, GCRY_WEAK_RANDOM);
+ if (!iv)
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ else
+ rc = gcry_cipher_setiv (hd, iv, blklen);
+ }
+ if (!rc)
+ {
+ unsigned char *key;
+ size_t keylen = PROT_CIPHER_KEYLEN;
+
+ key = gcry_malloc_secure (keylen);
+ if (!key)
+ rc = out_of_core ();
+ else
+ {
+ rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
+ 3, iv+2*blklen, 96, key, keylen);
+ if (!rc)
+ rc = gcry_cipher_setkey (hd, key, keylen);
+ xfree (key);
+ }
+ }
+ if (!rc)
+ {
+ p = outbuf;
+ *p++ = '(';
+ *p++ = '(';
+ memcpy (p, protbegin, protlen);
+ p += protlen;
+ memcpy (p, ")(4:hash4:sha120:", 17);
+ p += 17;
+ memcpy (p, sha1hash, 20);
+ p += 20;
+ *p++ = ')';
+ *p++ = ')';
+ memcpy (p, iv+blklen, blklen);
+ p += blklen;
+ assert ( p - outbuf == outlen);
+ rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0);
+ }
+ gcry_cipher_close (hd);
+ if (rc)
+ {
+ xfree (iv);
+ xfree (outbuf);
+ return rc;
+ }
+
+ /* Now allocate the buffer we want to return. This is
+
+ (protected openpgp-s2k3-sha1-aes-cbc
+ ((sha1 salt no_of_iterations) 16byte_iv)
+ encrypted_octet_string)
+
+ in canoncical format of course. We use asprintf and %n modifier
+ and spaces as palceholders. */
+ asprintf (&p,
+ "(9:protected%d:%s((4:sha18:%n_8bytes_2:96)%d:%n%*s)%d:%n%*s)",
+ (int)strlen (modestr), modestr,
+ &saltpos,
+ blklen, &ivpos, blklen, "",
+ enclen, &encpos, enclen, "");
+ if (p)
+ { /* asprintf does not use our malloc system */
+ char *psave = p;
+ p = xtrymalloc (strlen (psave)+1);
+ if (p)
+ strcpy (p, psave);
+ free (psave);
+ }
+ if (!p)
+ {
+ gpg_error_t tmperr = out_of_core ();
+ xfree (iv);
+ xfree (outbuf);
+ return tmperr;
+ }
+ *resultlen = strlen (p);
+ *result = p;
+ memcpy (p+saltpos, iv+2*blklen, 8);
+ memcpy (p+ivpos, iv, blklen);
+ memcpy (p+encpos, outbuf, enclen);
+ xfree (iv);
+ xfree (outbuf);
+ return 0;
+}
+
+
+
+/* Protect the key encoded in canonical format in plainkey. We assume
+ a valid S-Exp here. */
+int
+agent_protect (const unsigned char *plainkey, const char *passphrase,
+ unsigned char **result, size_t *resultlen)
+{
+ int rc;
+ const unsigned char *s;
+ const unsigned char *hash_begin, *hash_end;
+ const unsigned char *prot_begin, *prot_end, *real_end;
+ size_t n;
+ int c, infidx, i;
+ unsigned char hashvalue[20];
+ unsigned char *protected;
+ size_t protectedlen;
+ int depth = 0;
+ unsigned char *p;
+
+ s = plainkey;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "private-key"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ depth++;
+ hash_begin = s;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+
+ for (infidx=0; protect_info[infidx].algo
+ && !smatch (&s, n, protect_info[infidx].algo); infidx++)
+ ;
+ if (!protect_info[infidx].algo)
+ return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+
+ prot_begin = prot_end = NULL;
+ for (i=0; (c=protect_info[infidx].parmlist[i]); i++)
+ {
+ if (i == protect_info[infidx].prot_from)
+ prot_begin = s;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (n != 1 || c != *s)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s +=n; /* skip value */
+ if (*s != ')')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth--;
+ if (i == protect_info[infidx].prot_to)
+ prot_end = s;
+ s++;
+ }
+ if (*s != ')' || !prot_begin || !prot_end )
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth--;
+ hash_end = s;
+ s++;
+ /* skip to the end of the S-exp */
+ assert (depth == 1);
+ rc = sskip (&s, &depth);
+ if (rc)
+ return rc;
+ assert (!depth);
+ real_end = s-1;
+
+ gcry_md_hash_buffer (GCRY_MD_SHA1, hashvalue,
+ hash_begin, hash_end - hash_begin + 1);
+
+ rc = do_encryption (prot_begin, prot_end - prot_begin + 1,
+ passphrase, hashvalue,
+ &protected, &protectedlen);
+ if (rc)
+ return rc;
+
+ /* Now create the protected version of the key. Note that the 10
+ extra bytes are for for the inserted "protected-" string (the
+ beginning of the plaintext reads: "((11:private-key(" ). */
+ *resultlen = (10
+ + (prot_begin-plainkey)
+ + protectedlen
+ + (real_end-prot_end));
+ *result = p = xtrymalloc (*resultlen);
+ if (!p)
+ {
+ gpg_error_t tmperr = out_of_core ();
+ xfree (protected);
+ return tmperr;
+ }
+ memcpy (p, "(21:protected-", 14);
+ p += 14;
+ memcpy (p, plainkey+4, prot_begin - plainkey - 4);
+ p += prot_begin - plainkey - 4;
+ memcpy (p, protected, protectedlen);
+ p += protectedlen;
+ memcpy (p, prot_end+1, real_end - prot_end);
+ p += real_end - prot_end;
+ assert ( p - *result == *resultlen);
+ xfree (protected);
+ return 0;
+}
+
+
+/* Do the actual decryption and check the return list for consistency. */
+static int
+do_decryption (const unsigned char *protected, size_t protectedlen,
+ const char *passphrase,
+ const unsigned char *s2ksalt, unsigned long s2kcount,
+ const unsigned char *iv, size_t ivlen,
+ unsigned char **result)
+{
+ int rc = 0;
+ int blklen;
+ gcry_cipher_hd_t hd;
+ unsigned char *outbuf;
+ size_t reallen;
+
+ blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER);
+ if (protectedlen < 4 || (protectedlen%blklen))
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+
+ rc = gcry_cipher_open (&hd, PROT_CIPHER, GCRY_CIPHER_MODE_CBC,
+ GCRY_CIPHER_SECURE);
+ if (rc)
+ return rc;
+
+ outbuf = gcry_malloc_secure (protectedlen);
+ if (!outbuf)
+ rc = out_of_core ();
+ if (!rc)
+ rc = gcry_cipher_setiv (hd, iv, ivlen);
+ if (!rc)
+ {
+ unsigned char *key;
+ size_t keylen = PROT_CIPHER_KEYLEN;
+
+ key = gcry_malloc_secure (keylen);
+ if (!key)
+ rc = out_of_core ();
+ else
+ {
+ rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
+ 3, s2ksalt, s2kcount, key, keylen);
+ if (!rc)
+ rc = gcry_cipher_setkey (hd, key, keylen);
+ xfree (key);
+ }
+ }
+ if (!rc)
+ rc = gcry_cipher_decrypt (hd, outbuf, protectedlen,
+ protected, protectedlen);
+ gcry_cipher_close (hd);
+ if (rc)
+ {
+ xfree (outbuf);
+ return rc;
+ }
+ /* do a quick check first */
+ if (*outbuf != '(' && outbuf[1] != '(')
+ {
+ xfree (outbuf);
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+ }
+ /* check that we have a consistent S-Exp */
+ reallen = gcry_sexp_canon_len (outbuf, protectedlen, NULL, NULL);
+ if (!reallen || (reallen + blklen < protectedlen) )
+ {
+ xfree (outbuf);
+ return gpg_error (GPG_ERR_BAD_PASSPHRASE);
+ }
+ *result = outbuf;
+ return 0;
+}
+
+
+/* Merge the parameter list contained in CLEARTEXT with the original
+ protect lists PROTECTEDKEY by replacing the list at REPLACEPOS.
+ Return the new list in RESULT and the MIC value in the 20 byte
+ buffer SHA1HASH. */
+static int
+merge_lists (const unsigned char *protectedkey,
+ size_t replacepos,
+ const unsigned char *cleartext,
+ unsigned char *sha1hash, unsigned char **result)
+{
+ size_t n, newlistlen;
+ unsigned char *newlist, *p;
+ const unsigned char *s;
+ const unsigned char *startpos, *endpos;
+ int i, rc;
+
+ if (replacepos < 26)
+ return gpg_error (GPG_ERR_BUG);
+
+ /* Estimate the required size of the resulting list. We have a large
+ safety margin of >20 bytes (MIC hash from CLEARTEXT and the
+ removed "protected-" */
+ newlistlen = gcry_sexp_canon_len (protectedkey, 0, NULL, NULL);
+ if (!newlistlen)
+ return gpg_error (GPG_ERR_BUG);
+ n = gcry_sexp_canon_len (cleartext, 0, NULL, NULL);
+ if (!n)
+ return gpg_error (GPG_ERR_BUG);
+ newlistlen += n;
+ newlist = gcry_malloc_secure (newlistlen);
+ if (!newlist)
+ return out_of_core ();
+
+ /* Copy the initial segment */
+ strcpy (newlist, "(11:private-key");
+ p = newlist + 15;
+ memcpy (p, protectedkey+15+10, replacepos-15-10);
+ p += replacepos-15-10;
+
+ /* copy the cleartext */
+ s = cleartext;
+ if (*s != '(' && s[1] != '(')
+ return gpg_error (GPG_ERR_BUG); /*we already checked this */
+ s += 2;
+ startpos = s;
+ while ( *s == '(' )
+ {
+ s++;
+ n = snext (&s);
+ if (!n)
+ goto invalid_sexp;
+ s += n;
+ n = snext (&s);
+ if (!n)
+ goto invalid_sexp;
+ s += n;
+ if ( *s != ')' )
+ goto invalid_sexp;
+ s++;
+ }
+ if ( *s != ')' )
+ goto invalid_sexp;
+ endpos = s;
+ s++;
+ /* short intermezzo: Get the MIC */
+ if (*s != '(')
+ goto invalid_sexp;
+ s++;
+ n = snext (&s);
+ if (!smatch (&s, n, "hash"))
+ goto invalid_sexp;
+ n = snext (&s);
+ if (!smatch (&s, n, "sha1"))
+ goto invalid_sexp;
+ n = snext (&s);
+ if (n != 20)
+ goto invalid_sexp;
+ memcpy (sha1hash, s, 20);
+ s += n;
+ if (*s != ')')
+ goto invalid_sexp;
+ /* end intermezzo */
+
+ /* append the parameter list */
+ memcpy (p, startpos, endpos - startpos);
+ p += endpos - startpos;
+
+ /* skip overt the protected list element in the original list */
+ s = protectedkey + replacepos;
+ assert (*s == '(');
+ s++;
+ i = 1;
+ rc = sskip (&s, &i);
+ if (rc)
+ goto failure;
+ startpos = s;
+ i = 2; /* we are inside this level */
+ rc = sskip (&s, &i);
+ if (rc)
+ goto failure;
+ assert (s[-1] == ')');
+ endpos = s; /* one behind the end of the list */
+
+ /* append the rest */
+ memcpy (p, startpos, endpos - startpos);
+ p += endpos - startpos;
+
+ /* ready */
+ *result = newlist;
+ return 0;
+
+ failure:
+ xfree (newlist);
+ return rc;
+
+ invalid_sexp:
+ xfree (newlist);
+ return gpg_error (GPG_ERR_INV_SEXP);
+}
+
+
+
+/* Unprotect the key encoded in canonical format. We assume a valid
+ S-Exp here. */
+int
+agent_unprotect (const unsigned char *protectedkey, const char *passphrase,
+ unsigned char **result, size_t *resultlen)
+{
+ int rc;
+ const unsigned char *s;
+ size_t n;
+ int infidx, i;
+ unsigned char sha1hash[20], sha1hash2[20];
+ const unsigned char *s2ksalt;
+ unsigned long s2kcount;
+ const unsigned char *iv;
+ const unsigned char *prot_begin;
+ unsigned char *cleartext;
+ unsigned char *final;
+
+ s = protectedkey;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "protected-private-key"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+
+ for (infidx=0; protect_info[infidx].algo
+ && !smatch (&s, n, protect_info[infidx].algo); infidx++)
+ ;
+ if (!protect_info[infidx].algo)
+ return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+
+ /* now find the list with the protected information. Here is an
+ example for such a list:
+ (protected openpgp-s2k3-sha1-aes-cbc
+ ((sha1 <salt> <count>) <Initialization_Vector>)
+ <encrypted_data>)
+ */
+ for (;;)
+ {
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ prot_begin = s;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (smatch (&s, n, "protected"))
+ break;
+ s += n;
+ i = 1;
+ rc = sskip (&s, &i);
+ if (rc)
+ return rc;
+ }
+ /* found */
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc"))
+ return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
+ if (*s != '(' || s[1] != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += 2;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "sha1"))
+ return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
+ n = snext (&s);
+ if (n != 8)
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ s2ksalt = s;
+ s += n;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ /* We expect a list close as next, so we can simply use strtoul()
+ here. We might want to check that we only have digits - but this
+ is nothing we should worry about */
+ if (s[n] != ')' )
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s2kcount = strtoul (s, NULL, 10);
+ if (!s2kcount)
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ s += n;
+ s++; /* skip list end */
+
+ n = snext (&s);
+ if (n != 16) /* Wrong blocksize for IV (we support ony aes-128) */
+ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ iv = s;
+ s += n;
+ if (*s != ')' )
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+
+ rc = do_decryption (s, n,
+ passphrase, s2ksalt, s2kcount,
+ iv, 16,
+ &cleartext);
+ if (rc)
+ return rc;
+
+ rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext,
+ sha1hash, &final);
+ xfree (cleartext);
+ if (rc)
+ return rc;
+
+ rc = calculate_mic (final, sha1hash2);
+ if (!rc && memcmp (sha1hash, sha1hash2, 20))
+ rc = gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
+ if (rc)
+ {
+ xfree (final);
+ return rc;
+ }
+
+ *result = final;
+ *resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL);
+ return 0;
+}
+
+/* Check the type of the private key, this is one of the constants:
+ PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the
+ value 0), PRIVATE_KEY_CLEAR for an unprotected private key.
+ PRIVATE_KEY_PROTECTED for an protected private key or
+ PRIVATE_KEY_SHADOWED for a sub key where the secret parts are stored
+ elsewhere. */
+int
+agent_private_key_type (const unsigned char *privatekey)
+{
+ const unsigned char *s;
+ size_t n;
+
+ s = privatekey;
+ if (*s != '(')
+ return PRIVATE_KEY_UNKNOWN;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return PRIVATE_KEY_UNKNOWN;
+ if (smatch (&s, n, "protected-private-key"))
+ return PRIVATE_KEY_PROTECTED;
+ if (smatch (&s, n, "shadowed-private-key"))
+ return PRIVATE_KEY_SHADOWED;
+ if (smatch (&s, n, "private-key"))
+ return PRIVATE_KEY_CLEAR;
+ return PRIVATE_KEY_UNKNOWN;
+}
+
+
+
+/* Transform a passphrase into a suitable key of length KEYLEN and
+ store this key in the caller provided buffer KEY. The caller must
+ provide an HASHALGO, a valid S2KMODE (see rfc-2440) and depending on
+ that mode an S2KSALT of 8 random bytes and an S2KCOUNT (a suitable
+ value is 96).
+
+ Returns an error code on failure. */
+static int
+hash_passphrase (const char *passphrase, int hashalgo,
+ int s2kmode,
+ const unsigned char *s2ksalt,
+ unsigned long s2kcount,
+ unsigned char *key, size_t keylen)
+{
+ int rc;
+ gcry_md_hd_t md;
+ int pass, i;
+ int used = 0;
+ int pwlen = strlen (passphrase);
+
+ if ( (s2kmode != 0 && s2kmode != 1 && s2kmode != 3)
+ || !hashalgo || !keylen || !key || !passphrase)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if ((s2kmode == 1 ||s2kmode == 3) && !s2ksalt)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ rc = gcry_md_open (&md, hashalgo, GCRY_MD_FLAG_SECURE);
+ if (rc)
+ return rc;
+
+ for (pass=0; used < keylen; pass++)
+ {
+ if (pass)
+ {
+ gcry_md_reset (md);
+ for (i=0; i < pass; i++) /* preset the hash context */
+ gcry_md_putc (md, 0);
+ }
+
+ if (s2kmode == 1 || s2kmode == 3)
+ {
+ int len2 = pwlen + 8;
+ unsigned long count = len2;
+
+ if (s2kmode == 3)
+ {
+ count = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6);
+ if (count < len2)
+ count = len2;
+ }
+
+ while (count > len2)
+ {
+ gcry_md_write (md, s2ksalt, 8);
+ gcry_md_write (md, passphrase, pwlen);
+ count -= len2;
+ }
+ if (count < 8)
+ gcry_md_write (md, s2ksalt, count);
+ else
+ {
+ gcry_md_write (md, s2ksalt, 8);
+ count -= 8;
+ gcry_md_write (md, passphrase, count);
+ }
+ }
+ else
+ gcry_md_write (md, passphrase, pwlen);
+
+ gcry_md_final (md);
+ i = gcry_md_get_algo_dlen (hashalgo);
+ if (i > keylen - used)
+ i = keylen - used;
+ memcpy (key+used, gcry_md_read (md, hashalgo), i);
+ used += i;
+ }
+ gcry_md_close(md);
+ return 0;
+}
+
+
+
+/* Create a shadow key from a public key. We use the shadow protocol
+ "ti-v1" and insert the S-expressionn SHADOW_INFO. The resulting
+ S-expression is returned in an allocated buffer RESULT will point
+ to. The input parameters are expected to be valid canonilized
+ S-expressions */
+int
+agent_shadow_key (const unsigned char *pubkey,
+ const unsigned char *shadow_info,
+ unsigned char **result)
+{
+ const unsigned char *s;
+ const unsigned char *point;
+ size_t n;
+ int depth = 0;
+ unsigned char *p;
+ size_t pubkey_len = gcry_sexp_canon_len (pubkey, 0, NULL,NULL);
+ size_t shadow_info_len = gcry_sexp_canon_len (shadow_info, 0, NULL,NULL);
+
+ if (!pubkey_len || !shadow_info_len)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ s = pubkey;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "public-key"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n; /* skip over the algorithm name */
+
+ while (*s != ')')
+ {
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s +=n; /* skip value */
+ if (*s != ')')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth--;
+ s++;
+ }
+ point = s; /* insert right before the point */
+ depth--;
+ s++;
+ assert (depth == 1);
+
+ /* calculate required length by taking in account: the "shadowed-"
+ prefix, the "shadowed", "t1-v1" as well as some parenthesis */
+ n = 12 + pubkey_len + 1 + 3+8 + 2+5 + shadow_info_len + 1;
+ *result = p = xtrymalloc (n);
+ if (!p)
+ return out_of_core ();
+ p = stpcpy (p, "(20:shadowed-private-key");
+ /* (10:public-key ...)*/
+ memcpy (p, pubkey+14, point - (pubkey+14));
+ p += point - (pubkey+14);
+ p = stpcpy (p, "(8:shadowed5:t1-v1");
+ memcpy (p, shadow_info, shadow_info_len);
+ p += shadow_info_len;
+ *p++ = ')';
+ memcpy (p, point, pubkey_len - (point - pubkey));
+ p += pubkey_len - (point - pubkey);
+
+ return 0;
+}
+
+/* Parse a canonical encoded shadowed key and return a pointer to the
+ inner list with the shadow_info */
+int
+agent_get_shadow_info (const unsigned char *shadowkey,
+ unsigned char const **shadow_info)
+{
+ const unsigned char *s;
+ size_t n;
+ int depth = 0;
+
+ s = shadowkey;
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (!smatch (&s, n, "shadowed-private-key"))
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s += n; /* skip over the algorithm name */
+
+ for (;;)
+ {
+ if (*s == ')')
+ return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth++;
+ s++;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (smatch (&s, n, "shadowed"))
+ break;
+ s += n;
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ s +=n; /* skip value */
+ if (*s != ')')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ depth--;
+ s++;
+ }
+ /* found the shadowed list, s points to the protocol */
+ n = snext (&s);
+ if (!n)
+ return gpg_error (GPG_ERR_INV_SEXP);
+ if (smatch (&s, n, "t1-v1"))
+ {
+ if (*s != '(')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ *shadow_info = s;
+ }
+ else
+ return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
+ return 0;
+}
+
diff --git a/agent/simple-pwquery.c b/agent/simple-pwquery.c
new file mode 100644
index 000000000..e870122cb
--- /dev/null
+++ b/agent/simple-pwquery.c
@@ -0,0 +1,486 @@
+/* simple-pwquery.c - A simple password query client for gpg-agent
+ * Copyright (C) 2002 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+/* This module is intended as a standalone client implementation to
+ gpg-agent's GET_PASSPHRASE command. In particular it does not use
+ the Assuan library and can only cope with an already running
+ gpg-agent. Some stuff is configurable in the header file. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#define SIMPLE_PWQUERY_IMPLEMENTATION 1
+#include "simple-pwquery.h"
+
+#if defined(SPWQ_USE_LOGGING) && !defined(HAVE_JNLIB_LOGGING)
+# undef SPWQ_USE_LOGGING
+#endif
+
+#ifndef _
+#define _(a) (a)
+#endif
+
+#if !defined (hexdigitp) && !defined (xtoi_2)
+#define digitp(p) (*(p) >= '0' && *(p) <= '9')
+#define hexdigitp(a) (digitp (a) \
+ || (*(a) >= 'A' && *(a) <= 'F') \
+ || (*(a) >= 'a' && *(a) <= 'f'))
+#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
+ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
+#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
+#endif
+
+
+/* Write NBYTES of BUF to file descriptor FD. */
+static int
+writen (int fd, const void *buf, size_t nbytes)
+{
+ size_t nleft = nbytes;
+ int nwritten;
+
+ while (nleft > 0)
+ {
+ nwritten = write( fd, buf, nleft );
+ if (nwritten < 0)
+ {
+ if (errno == EINTR)
+ nwritten = 0;
+ else {
+#ifdef SPWQ_USE_LOGGING
+ log_error ("write failed: %s\n", strerror (errno));
+#endif
+ return SPWQ_IO_ERROR;
+ }
+ }
+ nleft -= nwritten;
+ buf = (const char*)buf + nwritten;
+ }
+
+ return 0;
+}
+
+
+/* Read an entire line and return number of bytes read. */
+static int
+readline (int fd, char *buf, size_t buflen)
+{
+ size_t nleft = buflen;
+ char *p;
+ int nread = 0;
+
+ while (nleft > 0)
+ {
+ int n = read (fd, buf, nleft);
+ if (n < 0)
+ {
+ if (errno == EINTR)
+ continue;
+ return -(SPWQ_IO_ERROR);
+ }
+ else if (!n)
+ {
+ return -(SPWQ_PROTOCOL_ERROR); /* incomplete line */
+ }
+ p = buf;
+ nleft -= n;
+ buf += n;
+ nread += n;
+
+ for (; n && *p != '\n'; n--, p++)
+ ;
+ if (n)
+ {
+ break; /* at least one full line available - that's enough.
+ This function is just a simple implementation, so
+ it is okay to forget about pending bytes */
+ }
+ }
+
+ return nread;
+}
+
+
+/* Send an option to the agent */
+static int
+agent_send_option (int fd, const char *name, const char *value)
+{
+ char buf[200];
+ int nread;
+ char *line;
+ int i;
+
+ line = spwq_malloc (7 + strlen (name) + 1 + strlen (value) + 2);
+ if (!line)
+ return SPWQ_OUT_OF_CORE;
+ strcpy (stpcpy (stpcpy (stpcpy (
+ stpcpy (line, "OPTION "), name), "="), value), "\n");
+ i = writen (fd, line, strlen (line));
+ spwq_free (line);
+ if (i)
+ return i;
+
+ /* get response */
+ nread = readline (fd, buf, DIM(buf)-1);
+ if (nread < 0)
+ return -nread;
+ if (nread < 3)
+ return SPWQ_PROTOCOL_ERROR;
+
+ if (buf[0] == 'O' && buf[1] == 'K' && (buf[2] == ' ' || buf[2] == '\n'))
+ return 0; /* okay */
+
+ return SPWQ_ERR_RESPONSE;
+}
+
+
+/* Send all available options to the agent. */
+static int
+agent_send_all_options (int fd)
+{
+ char *dft_display = NULL;
+ char *dft_ttyname = NULL;
+ char *dft_ttytype = NULL;
+ int rc = 0;
+
+ dft_display = getenv ("DISPLAY");
+ if (dft_display)
+ {
+ if ((rc = agent_send_option (fd, "display", dft_display)))
+ return rc;
+ }
+
+ dft_ttyname = getenv ("GPG_TTY");
+ if ((!dft_ttyname || !*dft_ttyname) && ttyname (0))
+ dft_ttyname = ttyname (0);
+ if (dft_ttyname && *dft_ttyname)
+ {
+ if ((rc=agent_send_option (fd, "ttyname", dft_ttyname)))
+ return rc;
+ }
+
+ dft_ttytype = getenv ("TERM");
+ if (dft_ttyname && dft_ttytype)
+ {
+ if ((rc = agent_send_option (fd, "ttytype", dft_ttytype)))
+ return rc;
+ }
+
+#if defined(HAVE_SETLOCALE)
+ {
+ char *old_lc = NULL;
+ char *dft_lc = NULL;
+
+#if defined(LC_CTYPE)
+ old_lc = setlocale (LC_CTYPE, NULL);
+ if (old_lc)
+ {
+ char *p = spwq_malloc (strlen (old_lc)+1);
+ if (!p)
+ return SPWQ_OUT_OF_CORE;
+ strcpy (p, old_lc);
+ old_lc = p;
+ }
+ dft_lc = setlocale (LC_CTYPE, "");
+ if (dft_ttyname && dft_lc)
+ rc = agent_send_option (fd, "lc-ctype", dft_lc);
+ if (old_lc)
+ {
+ setlocale (LC_CTYPE, old_lc);
+ spwq_free (old_lc);
+ }
+ if (rc)
+ return rc;
+#endif
+
+#if defined(LC_MESSAGES)
+ old_lc = setlocale (LC_MESSAGES, NULL);
+ if (old_lc)
+ {
+ char *p = spwq_malloc (strlen (old_lc)+1);
+ if (!p)
+ return SPWQ_OUT_OF_CORE;
+ strcpy (p, old_lc);
+ old_lc = p;
+ }
+ dft_lc = setlocale (LC_MESSAGES, "");
+ if (dft_ttyname && dft_lc)
+ rc = agent_send_option (fd, "lc-messages", dft_lc);
+ if (old_lc)
+ {
+ setlocale (LC_MESSAGES, old_lc);
+ spwq_free (old_lc);
+ }
+ if (rc)
+ return rc;
+#endif
+ }
+#endif /*HAVE_SETLOCALE*/
+
+ return 0;
+}
+
+
+
+/* Try to open a connection to the agent, send all options and return
+ the file descriptor for the connection. Return -1 in case of
+ error. */
+static int
+agent_open (int *rfd)
+{
+ int rc;
+ int fd;
+ char *infostr, *p;
+ struct sockaddr_un client_addr;
+ size_t len;
+ int prot;
+ char line[200];
+ int nread;
+
+ *rfd = -1;
+ infostr = getenv ( "GPG_AGENT_INFO" );
+ if ( !infostr )
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error (_("gpg-agent is not available in this session\n"));
+#endif
+ return SPWQ_NO_AGENT;
+ }
+
+ if ( !(p = strchr ( infostr, ':')) || p == infostr
+ || (p-infostr)+1 >= sizeof client_addr.sun_path )
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error ( _("malformed GPG_AGENT_INFO environment variable\n"));
+#endif
+ return SPWQ_NO_AGENT;
+ }
+ *p++ = 0;
+
+ while (*p && *p != ':')
+ p++;
+ prot = *p? atoi (p+1) : 0;
+ if ( prot != 1)
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error (_("gpg-agent protocol version %d is not supported\n"),prot);
+#endif
+ return SPWQ_PROTOCOL_ERROR;
+ }
+
+ if( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 )
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error ("can't create socket: %s\n", strerror(errno) );
+#endif
+ return SPWQ_SYS_ERROR;
+ }
+
+ memset (&client_addr, 0, sizeof client_addr);
+ client_addr.sun_family = AF_UNIX;
+ strcpy (client_addr.sun_path, infostr);
+ len = (offsetof (struct sockaddr_un, sun_path)
+ + strlen(client_addr.sun_path) + 1);
+
+ if (connect (fd, (struct sockaddr*)&client_addr, len ) == -1)
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error ( _("can't connect to `%s': %s\n"), infostr, strerror (errno));
+#endif
+ close (fd );
+ return SPWQ_IO_ERROR;
+ }
+
+ nread = readline (fd, line, DIM(line));
+ if (nread < 3 || !(line[0] == 'O' && line[1] == 'K'
+ && (line[2] == '\n' || line[2] == ' ')) )
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error ( _("communication problem with gpg-agent\n"));
+#endif
+ close (fd );
+ return SPWQ_PROTOCOL_ERROR;
+ }
+
+ rc = agent_send_all_options (fd);
+ if (rc)
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error (_("problem setting the gpg-agent options\n"));
+#endif
+ close (fd);
+ return rc;
+ }
+
+ *rfd = fd;
+ return 0;
+}
+
+
+/* Copy text to BUFFER and escape as required. Return a poiinter to
+ the end of the new buffer. NOte that BUFFER must be large enough
+ to keep the entire text; allocataing it 3 times the size of TEXT
+ is sufficient. */
+static char *
+copy_and_escape (char *buffer, const char *text)
+{
+ int i;
+ char *p = buffer;
+
+ for (i=0; text[i]; i++)
+ {
+ if (text[i] < ' ' || text[i] == '+')
+ {
+ sprintf (p, "%%%02X", text[i]);
+ p += 3;
+ }
+ else if (text[i] == ' ')
+ *p++ = '+';
+ else
+ *p++ = text[i];
+ }
+ return p;
+}
+
+
+/* Ask the gpg-agent for a passphrase and present the user with a
+ DESCRIPTION, a PROMPT and optiaonlly with a TRYAGAIN extra text.
+ If a CACHEID is not NULL it is used to locate the passphrase in in
+ the cache and store it under this ID. If ERRORCODE is not NULL it
+ should point a variable receiving an errorcode; thsi errocode might
+ be 0 if the user canceled the operation. The function returns NULL
+ to indicate an error. */
+char *
+simple_pwquery (const char *cacheid,
+ const char *tryagain,
+ const char *prompt,
+ const char *description,
+ int *errorcode)
+{
+ int fd = -1;
+ int nread;
+ char *result = NULL;
+ char *pw = NULL;
+ char *p;
+ int rc, i;
+
+ rc = agent_open (&fd);
+ if (rc)
+ goto leave;
+
+ if (!cacheid)
+ cacheid = "X";
+ if (!tryagain)
+ tryagain = "X";
+ if (!prompt)
+ prompt = "X";
+ if (!description)
+ description = "X";
+
+ {
+ char *line;
+ /* We allocate 3 times the needed space so that there is enough
+ space for escaping. */
+ line = spwq_malloc (15
+ + 3*strlen (cacheid) + 1
+ + 3*strlen (tryagain) + 1
+ + 3*strlen (prompt) + 1
+ + 3*strlen (description) + 1
+ + 2);
+ if (!line)
+ {
+ rc = SPWQ_OUT_OF_CORE;
+ goto leave;
+ }
+ strcpy (line, "GET_PASSPHRASE ");
+ p = line+15;
+ p = copy_and_escape (p, cacheid);
+ *p++ = ' ';
+ p = copy_and_escape (p, tryagain);
+ *p++ = ' ';
+ p = copy_and_escape (p, prompt);
+ *p++ = ' ';
+ p = copy_and_escape (p, description);
+ *p++ = '\n';
+ rc = writen (fd, line, p - line);
+ spwq_free (line);
+ if (rc)
+ goto leave;
+ }
+
+ /* get response */
+ pw = spwq_secure_malloc (500);
+ nread = readline (fd, pw, 499);
+ if (nread < 0)
+ {
+ rc = -nread;
+ goto leave;
+ }
+ if (nread < 3)
+ {
+ rc = SPWQ_PROTOCOL_ERROR;
+ goto leave;
+ }
+
+ if (pw[0] == 'O' && pw[1] == 'K' && pw[2] == ' ')
+ { /* we got a passphrase - convert it back from hex */
+ size_t pwlen = 0;
+
+ for (i=3; i < nread && hexdigitp (pw+i); i+=2)
+ pw[pwlen++] = xtoi_2 (pw+i);
+ pw[pwlen] = 0; /* make a C String */
+ result = pw;
+ pw = NULL;
+ }
+ else if (nread > 7 && !memcmp (pw, "ERR 111", 7)
+ && (pw[7] == ' ' || pw[7] == '\n') )
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_info (_("canceled by user\n") );
+#endif
+ *errorcode = 0; /* canceled */
+ }
+ else
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error (_("problem with the agent\n"));
+#endif
+ rc = SPWQ_ERR_RESPONSE;
+ }
+
+ leave:
+ if (errorcode)
+ *errorcode = rc;
+ if (fd != -1)
+ close (fd);
+ if (pw)
+ spwq_free (pw);
+ return result;
+}
diff --git a/common/ChangeLog b/common/ChangeLog
new file mode 100644
index 000000000..4870a4a5d
--- /dev/null
+++ b/common/ChangeLog
@@ -0,0 +1,219 @@
+2003-07-15 Werner Koch <wk@gnupg.org>
+
+ * simple-pwquery.c, simple-pwquery.h: New; moved from ../agent.
+ * Makefile.am (libsimple_pwquery_a_LIBADD): New.
+
+2003-06-25 Werner Koch <wk@gnupg.org>
+
+ * maperror.c (map_to_assuan_status): Directly map 0 to 0.
+
+2003-06-17 Werner Koch <wk@gnupg.org>
+
+ * gettime.c (scan_isodatestr,add_days_to_timestamp,strtimevalue)
+ (strtimestamp,asctimestamp): New. Code taken from gnupg 1.3.2
+ mischelp.c.
+
+ * yesno.c: New. Code taken from gnupg 1.3.2 mischelp.c
+
+ * miscellaneous.c: New.
+
+ * util.h: Include utf8conf.h
+
+2003-06-16 Werner Koch <wk@gnupg.org>
+
+ * gettime.c (make_timestamp): New.
+
+ * ttyio.c: New. Taken from gnupg 1.2.
+ * ttyio.h: Move from ../include.
+
+2003-06-13 Werner Koch <wk@gnupg.org>
+
+ * util.h (seterr): Removed macro.
+ (xmalloc_secure,xcalloc_secure): New.
+
+2003-06-11 Werner Koch <wk@gnupg.org>
+
+ * iobuf.c (iobuf_writebyte,iobuf_write): Return error code from
+ iobuf_flush.
+ (iobuf_writestr): Ditto.
+
+2003-06-10 Werner Koch <wk@gnupg.org>
+
+ * iobuf.c, iobuf.h: New. Taken from current gnupg 1.3 CVS. Run
+ indent on it and adjusted error handling to libgpg-error style.
+ Replaced IOBUF by iobuf_t. Renamed malloc functions.
+
+2003-06-04 Werner Koch <wk@gnupg.org>
+
+ * errors.h: Removed all error codes. We keep the status codes for
+ now.
+ * Makefile.am: Do not create errors.c anymore; remove it from the
+ sources.
+
+ * maperror.c: Don't include error.h. Change all error codes to
+ libgpg-error style.
+ (map_assuan_err): Changed to new Assuan error code convention.
+ (map_to_assuan_status): Likewise.
+ (map_gcry_err,map_kbx_err): Not needed. For now dummy functions.
+
+ * membuf.c, membuf.h: New. Code taken from ../sm/call-agent.h.
+ * Makefile.am: Added above.
+
+2003-04-29 Werner Koch <wk@gnupg.org>
+
+ * util.h (fopencokokie): Removed prototype and struct.
+
+ * fopencookie.c: Removed.
+
+ * maperror.c: Use system assuan.h
+
+2002-10-31 Neal H. Walfield <neal@g10code.de>
+
+ * isascii.c: New file.
+ * putc_unlocked.c: Likewise.
+
+2002-10-28 Neal H. Walfield <neal@g10code.de>
+
+ * signal.c (caught_fatal_sig): Remove superfluous zero
+ initializer.
+ (caught_sigusr1): Likewise.
+
+2002-09-04 Neal H. Walfield <neal@g10code.de>
+
+ * vasprintf.c (vasprintf) [va_copy]: Use va_copy.
+ [!va_copy && __va_copy]: Use __va_copy.
+ [!va_copy && !__va_copy]: Only now fall back to using memcpy.
+
+2002-08-21 Werner Koch <wk@gnupg.org>
+
+ * errors.h: Added STATUS_IMPORT_PROBLEM.
+
+2002-08-20 Werner Koch <wk@gnupg.org>
+
+ * vasprintf.c: Hack to handle NULL for %s.
+
+2002-08-09 Werner Koch <wk@gnupg.org>
+
+ * signal.c: New. Taken from GnuPG 1.1.91.
+
+2002-07-23 Werner Koch <wk@gnupg.org>
+
+ * util.h (_IO_cookie_io_functions_t): Fixed typo. Noted by
+ Richard Lefebvre.
+
+2002-07-22 Werner Koch <wk@gnupg.org>
+
+ * fseeko.c, ftello.c: New.
+
+2002-06-28 Werner Koch <wk@gnupg.org>
+
+ * maperror.c (map_to_assuan_status): Map more errorcodes to Bad
+ Certificate.
+
+2002-06-26 Werner Koch <wk@gnupg.org>
+
+ * maperror.c (map_to_assuan_status): Map EOF to No_Data_Available.
+
+2002-06-10 Werner Koch <wk@gnupg.org>
+
+ * errors.h (gnupg_error_token): Add new prototype.
+ (STATUS_ERROR): New.
+
+ * mkerrtok: New.
+ * Makefile.am: Use it to create the new error token function.
+
+2002-06-04 Werner Koch <wk@gnupg.org>
+
+ * maperror.c (map_to_assuan_status): Map Bad_CA_Certificate.
+
+2002-05-23 Werner Koch <wk@gnupg.org>
+
+ * no-pth.c, Makefile.am: Removed.
+
+2002-05-22 Werner Koch <wk@gnupg.org>
+
+ * mkdtemp.c: Replaced byte by unsigned char because it is no longer
+ defined in gcrypt.h.
+
+2002-05-21 Werner Koch <wk@gnupg.org>
+
+ * maperror.c (map_gcry_err): Add libgcrypt's new S-expression errors.
+ (map_ksba_err): Add a few mappings.
+
+2002-05-14 Werner Koch <wk@gnupg.org>
+
+ * gettime.c: New.
+
+2002-05-03 Werner Koch <wk@gnupg.org>
+
+ * errors.h: Added STARUS_EXPSIG and STATUS_EXPKEYSIG.
+
+2002-04-15 Werner Koch <wk@gnupg.org>
+
+ * cryptmiss.c: New.
+
+2002-02-14 Werner Koch <wk@gnupg.org>
+
+ * maperror.c: Add more assuan<->gnupg mappings.
+
+2002-02-12 Werner Koch <wk@gnupg.org>
+
+ * fopencookie.c: Dummy function.
+
+ * vasprintf.c: New. Taken from binutils-2.9.1 and dropped all non
+ ANSI-C stuff. Merged with asprintf version.
+
+ * no-pth.c: New.
+
+2002-01-23 Werner Koch <wk@gnupg.org>
+
+ * mkdtemp.c: Copied from gnupg-1.0.6c and changed to use libgcrypt.
+
+2002-01-19 Werner Koch <wk@gnupg.org>
+
+ * sysutils.c: New. This is the misc.c file from gnupg 1.0.6 with
+ the OpenPGP stuff removed.
+ * sysutils.h: New.
+
+2002-01-15 Werner Koch <wk@gnupg.org>
+
+ * maperror.c: Add mapping for Not_Trusted.
+
+2002-01-11 Werner Koch <wk@gnupg.org>
+
+ * maperror.c (map_assuan_err): Codes for CRL
+
+2002-01-08 Werner Koch <wk@gnupg.org>
+
+ * util.h (spacep): New.
+
+2002-01-02 Werner Koch <wk@gnupg.org>
+
+ * maperror.c (map_to_assuan_status): New. Merged from ../agent
+ and ../sm.
+
+2001-12-20 Werner Koch <wk@gnupg.org>
+
+ * maperror.c (map_gcry_err): Add some mappings.
+
+2001-12-18 Werner Koch <wk@gnupg.org>
+
+ * Makefile.am (AM_CPPFLAGS): Include flags for gcrypt and ksba
+
+2001-12-14 Werner Koch <wk@gnupg.org>
+
+ * util.h (digitp, hexdigitp): New ctype like macros.
+ (atoi_1,atoi_2,atoi_4,xtoi_1,xtoi_2): New.
+
+
+ Copyright 2001, 2002 Free Software Foundation, Inc.
+
+ This file is free software; as a special exception the author gives
+ unlimited permission to copy and/or distribute it, with or without
+ modifications, as long as this notice is preserved.
+
+ This file is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+
diff --git a/common/Makefile.am b/common/Makefile.am
new file mode 100644
index 000000000..2b99a19eb
--- /dev/null
+++ b/common/Makefile.am
@@ -0,0 +1,58 @@
+# Makefile for common gnupg modules
+# Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+#
+# This file is part of GnuPG.
+#
+# GnuPG is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+## Process this file with automake to produce Makefile.in
+
+EXTRA_DIST = mkerrors mkerrtok
+#INCLUDES =
+
+noinst_LIBRARIES = libcommon.a libsimple-pwquery.a
+
+AM_CPPFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS)
+
+libcommon_a_SOURCES = \
+ util.h i18n.h \
+ errors.h \
+ maperror.c \
+ sysutils.c sysutils.h \
+ cryptmiss.c \
+ gettime.c \
+ yesno.c \
+ miscellaneous.c \
+ membuf.c membuf.h \
+ iobuf.c iobuf.h \
+ ttyio.c ttyio.h \
+ signal.c
+
+
+libcommon_a_LIBADD = @LIBOBJS@
+
+libsimple_pwquery_a_SOURCES = \
+ simple-pwquery.c simple-pwquery.h
+
+libsimple_pwquery_a_LIBADD = @LIBOBJS@
+
+
+
+
+
+
+
+
+
diff --git a/common/README b/common/README
new file mode 100644
index 000000000..a90224bab
--- /dev/null
+++ b/common/README
@@ -0,0 +1,11 @@
+Stuff used by several modules of GnuPG.
+
+These directories use it:
+
+gpg
+sm
+agent
+
+These directories don't use it:
+
+kbx \ No newline at end of file
diff --git a/common/errors.h b/common/errors.h
new file mode 100644
index 000000000..a5643f08a
--- /dev/null
+++ b/common/errors.h
@@ -0,0 +1,110 @@
+/* errors.h - Globally used error codes
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef GNUPG_COMMON_ERRORS_H
+#define GNUPG_COMMON_ERRORS_H
+
+#include "util.h"
+
+/* Status codes - fixme: should go into another file */
+enum {
+ STATUS_ENTER,
+ STATUS_LEAVE,
+ STATUS_ABORT,
+ STATUS_GOODSIG,
+ STATUS_BADSIG,
+ STATUS_ERRSIG,
+ STATUS_BADARMOR,
+ STATUS_RSA_OR_IDEA,
+ STATUS_SIGEXPIRED,
+ STATUS_KEYREVOKED,
+ STATUS_TRUST_UNDEFINED,
+ STATUS_TRUST_NEVER,
+ STATUS_TRUST_MARGINAL,
+ STATUS_TRUST_FULLY,
+ STATUS_TRUST_ULTIMATE,
+
+ STATUS_SHM_INFO,
+ STATUS_SHM_GET,
+ STATUS_SHM_GET_BOOL,
+ STATUS_SHM_GET_HIDDEN,
+
+ STATUS_NEED_PASSPHRASE,
+ STATUS_VALIDSIG,
+ STATUS_SIG_ID,
+ STATUS_ENC_TO,
+ STATUS_NODATA,
+ STATUS_BAD_PASSPHRASE,
+ STATUS_NO_PUBKEY,
+ STATUS_NO_SECKEY,
+ STATUS_NEED_PASSPHRASE_SYM,
+ STATUS_DECRYPTION_FAILED,
+ STATUS_DECRYPTION_OKAY,
+ STATUS_MISSING_PASSPHRASE,
+ STATUS_GOOD_PASSPHRASE,
+ STATUS_GOODMDC,
+ STATUS_BADMDC,
+ STATUS_ERRMDC,
+ STATUS_IMPORTED,
+ STATUS_IMPORT_PROBLEM,
+ STATUS_IMPORT_RES,
+ STATUS_FILE_START,
+ STATUS_FILE_DONE,
+ STATUS_FILE_ERROR,
+
+ STATUS_BEGIN_DECRYPTION,
+ STATUS_END_DECRYPTION,
+ STATUS_BEGIN_ENCRYPTION,
+ STATUS_END_ENCRYPTION,
+
+ STATUS_DELETE_PROBLEM,
+ STATUS_GET_BOOL,
+ STATUS_GET_LINE,
+ STATUS_GET_HIDDEN,
+ STATUS_GOT_IT,
+ STATUS_PROGRESS,
+ STATUS_SIG_CREATED,
+ STATUS_SESSION_KEY,
+ STATUS_NOTATION_NAME,
+ STATUS_NOTATION_DATA,
+ STATUS_POLICY_URL,
+ STATUS_BEGIN_STREAM,
+ STATUS_END_STREAM,
+ STATUS_KEY_CREATED,
+ STATUS_USERID_HIN,
+ STATUS_UNEXPECTED,
+ STATUS_INV_RECP,
+ STATUS_NO_RECP,
+ STATUS_ALREADY_SIGNED,
+
+ STATUS_EXPSIG,
+ STATUS_EXPKEYSIG,
+
+ STATUS_TRUNCATED,
+ STATUS_ERROR
+};
+
+
+/*-- errors.c (build by mkerror and mkerrtok) --*/
+const char *gnupg_strerror (int err);
+const char *gnupg_error_token (int err);
+
+
+#endif /*GNUPG_COMMON_ERRORS_H*/
diff --git a/common/gettime.c b/common/gettime.c
new file mode 100644
index 000000000..a7914d348
--- /dev/null
+++ b/common/gettime.c
@@ -0,0 +1,250 @@
+/* gettime.c - Wrapper for time functions
+ * Copyright (C) 1998, 2002 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <time.h>
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif
+
+#include "util.h"
+
+static unsigned long timewarp;
+static enum { NORMAL = 0, FROZEN, FUTURE, PAST } timemode;
+
+/* Wrapper for the time(3). We use this here so we can fake the time
+ for tests */
+time_t
+gnupg_get_time ()
+{
+ time_t current = time (NULL);
+ if (timemode == NORMAL)
+ return current;
+ else if (timemode == FROZEN)
+ return timewarp;
+ else if (timemode == FUTURE)
+ return current + timewarp;
+ else
+ return current - timewarp;
+}
+
+/* set the time to NEWTIME so that gnupg_get_time returns a time
+ starting with this one. With FREEZE set to 1 the returned time
+ will never change. Just for completeness, a value of (time_t)-1
+ for NEWTIME gets you back to rality. Note that this is obviously
+ not thread-safe but this is not required. */
+void
+gnupg_set_time (time_t newtime, int freeze)
+{
+ time_t current = time (NULL);
+
+ if ( newtime == (time_t)-1 || current == newtime)
+ {
+ timemode = NORMAL;
+ timewarp = 0;
+ }
+ else if (freeze)
+ {
+ timemode = FROZEN;
+ timewarp = current;
+ }
+ else if (newtime > current)
+ {
+ timemode = FUTURE;
+ timewarp = newtime - current;
+ }
+ else
+ {
+ timemode = PAST;
+ timewarp = current - newtime;
+ }
+}
+
+/* Returns true when we are in timewarp mode */
+int
+gnupg_faked_time_p (void)
+{
+ return timemode;
+}
+
+
+/* This function is used by gpg because OpenPGP defines the timestamp
+ as an unsigned 32 bit value. */
+u32
+make_timestamp (void)
+{
+ time_t t = gnupg_get_time ();
+
+ if (t == (time_t)-1)
+ log_fatal ("gnupg_get_time() failed\n");
+ return (u32)t;
+}
+
+
+
+/****************
+ * Scan a date string and return a timestamp.
+ * The only supported format is "yyyy-mm-dd"
+ * Returns 0 for an invalid date.
+ */
+u32
+scan_isodatestr( const char *string )
+{
+ int year, month, day;
+ struct tm tmbuf;
+ time_t stamp;
+ int i;
+
+ if( strlen(string) != 10 || string[4] != '-' || string[7] != '-' )
+ return 0;
+ for( i=0; i < 4; i++ )
+ if( !digitp (string+i) )
+ return 0;
+ if( !digitp (string+5) || !digitp(string+6) )
+ return 0;
+ if( !digitp(string+8) || !digitp(string+9) )
+ return 0;
+ year = atoi(string);
+ month = atoi(string+5);
+ day = atoi(string+8);
+ /* some basic checks */
+ if( year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 )
+ return 0;
+ memset( &tmbuf, 0, sizeof tmbuf );
+ tmbuf.tm_mday = day;
+ tmbuf.tm_mon = month-1;
+ tmbuf.tm_year = year - 1900;
+ tmbuf.tm_isdst = -1;
+ stamp = mktime( &tmbuf );
+ if( stamp == (time_t)-1 )
+ return 0;
+ return stamp;
+}
+
+
+u32
+add_days_to_timestamp( u32 stamp, u16 days )
+{
+ return stamp + days*86400L;
+}
+
+
+/****************
+ * Return a string with a time value in the form: x Y, n D, n H
+ */
+
+const char *
+strtimevalue( u32 value )
+{
+ static char buffer[30];
+ unsigned int years, days, hours, minutes;
+
+ value /= 60;
+ minutes = value % 60;
+ value /= 60;
+ hours = value % 24;
+ value /= 24;
+ days = value % 365;
+ value /= 365;
+ years = value;
+
+ sprintf(buffer,"%uy%ud%uh%um", years, days, hours, minutes );
+ if( years )
+ return buffer;
+ if( days )
+ return strchr( buffer, 'y' ) + 1;
+ return strchr( buffer, 'd' ) + 1;
+}
+
+
+/****************
+ * Note: this function returns GMT
+ */
+const char *
+strtimestamp( u32 stamp )
+{
+ static char buffer[11+5];
+ struct tm *tp;
+ time_t atime = stamp;
+
+ if (atime < 0) {
+ strcpy (buffer, "????" "-??" "-??");
+ }
+ else {
+ tp = gmtime( &atime );
+ sprintf(buffer,"%04d-%02d-%02d",
+ 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday );
+ }
+ return buffer;
+}
+
+/****************
+ * Note: this function returns local time
+ */
+const char *
+asctimestamp( u32 stamp )
+{
+ static char buffer[50];
+#if defined (HAVE_STRFTIME) && defined (HAVE_NL_LANGINFO)
+ static char fmt[50];
+#endif
+ struct tm *tp;
+ time_t atime = stamp;
+
+ if (atime < 0) {
+ strcpy (buffer, "????" "-??" "-??");
+ return buffer;
+ }
+
+ tp = localtime( &atime );
+#ifdef HAVE_STRFTIME
+#if defined(HAVE_NL_LANGINFO)
+ mem2str( fmt, nl_langinfo(D_T_FMT), DIM(fmt)-3 );
+ if( strstr( fmt, "%Z" ) == NULL )
+ strcat( fmt, " %Z");
+ strftime( buffer, DIM(buffer)-1, fmt, tp );
+#else
+ /* fixme: we should check whether the locale appends a " %Z"
+ * These locales from glibc don't put the " %Z":
+ * fi_FI hr_HR ja_JP lt_LT lv_LV POSIX ru_RU ru_SU sv_FI sv_SE zh_CN
+ */
+ strftime( buffer, DIM(buffer)-1, "%c %Z", tp );
+#endif
+ buffer[DIM(buffer)-1] = 0;
+#else
+ mem2str( buffer, asctime(tp), DIM(buffer) );
+#endif
+ return buffer;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/common/iobuf.c b/common/iobuf.c
new file mode 100644
index 000000000..773e2993b
--- /dev/null
+++ b/common/iobuf.c
@@ -0,0 +1,2415 @@
+/* iobuf.c - file handling
+ * Copyright (C) 1998, 1999, 2000, 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#ifdef HAVE_DOSISH_SYSTEM
+#include <windows.h>
+#endif
+#ifdef __riscos__
+#include <kernel.h>
+#include <swis.h>
+#endif /* __riscos__ */
+
+#include "memory.h"
+#include "util.h"
+#include "iobuf.h"
+
+#undef FILE_FILTER_USES_STDIO
+
+#ifdef HAVE_DOSISH_SYSTEM
+#define USE_SETMODE 1
+#endif
+
+#ifdef FILE_FILTER_USES_STDIO
+#define my_fileno(a) fileno ((a))
+#define my_fopen_ro(a,b) fopen ((a),(b))
+#define my_fopen(a,b) fopen ((a),(b))
+typedef FILE *FILEP_OR_FD;
+#define INVALID_FP NULL
+#define FILEP_OR_FD_FOR_STDIN (stdin)
+#define FILEP_OR_FD_FOR_STDOUT (stdout)
+typedef struct
+{
+ FILE *fp; /* open file handle */
+ int keep_open;
+ int no_cache;
+ int print_only_name; /* flags indicating that fname is not a real file */
+ char fname[1]; /* name of the file */
+}
+file_filter_ctx_t;
+#else
+#define my_fileno(a) (a)
+#define my_fopen_ro(a,b) fd_cache_open ((a),(b))
+#define my_fopen(a,b) direct_open ((a),(b))
+#ifdef HAVE_DOSISH_SYSTEM
+typedef HANDLE FILEP_OR_FD;
+#define INVALID_FP ((HANDLE)-1)
+#define FILEP_OR_FD_FOR_STDIN (GetStdHandle (STD_INPUT_HANDLE))
+#define FILEP_OR_FD_FOR_STDOUT (GetStdHandle (STD_OUTPUT_HANDLE))
+#undef USE_SETMODE
+#else
+typedef int FILEP_OR_FD;
+#define INVALID_FP (-1)
+#define FILEP_OR_FD_FOR_STDIN (0)
+#define FILEP_OR_FD_FOR_STDOUT (1)
+#endif
+typedef struct
+{
+ FILEP_OR_FD fp; /* open file handle */
+ int keep_open;
+ int no_cache;
+ int eof_seen;
+ int print_only_name; /* flags indicating that fname is not a real file */
+ char fname[1]; /* name of the file */
+}
+file_filter_ctx_t;
+
+struct close_cache_s
+{
+ struct close_cache_s *next;
+ FILEP_OR_FD fp;
+ char fname[1];
+};
+typedef struct close_cache_s *CLOSE_CACHE;
+static CLOSE_CACHE close_cache;
+#endif
+
+#ifdef __MINGW32__
+typedef struct
+{
+ int sock;
+ int keep_open;
+ int no_cache;
+ int eof_seen;
+ int print_only_name; /* flags indicating that fname is not a real file */
+ char fname[1]; /* name of the file */
+}
+sock_filter_ctx_t;
+#endif /*__MINGW32__*/
+
+/* The first partial length header block must be of size 512
+ * to make it easier (and efficienter) we use a min. block size of 512
+ * for all chunks (but the last one) */
+#define OP_MIN_PARTIAL_CHUNK 512
+#define OP_MIN_PARTIAL_CHUNK_2POW 9
+
+typedef struct
+{
+ int use;
+ size_t size;
+ size_t count;
+ int partial; /* 1 = partial header, 2 in last partial packet */
+ char *buffer; /* used for partial header */
+ size_t buflen; /* used size of buffer */
+ int first_c; /* of partial header (which is > 0) */
+ int eof;
+}
+block_filter_ctx_t;
+
+static int special_names_enabled;
+
+static int underflow (iobuf_t a);
+static int translate_file_handle (int fd, int for_write);
+
+#ifndef FILE_FILTER_USES_STDIO
+
+/*
+ * Invalidate (i.e. close) a cached iobuf
+ */
+static void
+fd_cache_invalidate (const char *fname)
+{
+ CLOSE_CACHE cc;
+
+ assert (fname);
+ if (DBG_IOBUF)
+ log_debug ("fd_cache_invalidate (%s)\n", fname);
+
+ for (cc = close_cache; cc; cc = cc->next)
+ {
+ if (cc->fp != INVALID_FP && !strcmp (cc->fname, fname))
+ {
+ if (DBG_IOBUF)
+ log_debug (" did (%s)\n", cc->fname);
+#ifdef HAVE_DOSISH_SYSTEM
+ CloseHandle (cc->fp);
+#else
+ close (cc->fp);
+#endif
+ cc->fp = INVALID_FP;
+ }
+ }
+}
+
+
+
+static FILEP_OR_FD
+direct_open (const char *fname, const char *mode)
+{
+#ifdef HAVE_DOSISH_SYSTEM
+ unsigned long da, cd, sm;
+ HANDLE hfile;
+
+ /* Note, that we do not handle all mode combinations */
+
+ /* According to the ReactOS source it seems that open() of the
+ * standard MSW32 crt does open the file in share mode which is
+ * something new for MS applications ;-)
+ */
+ if (strchr (mode, '+'))
+ {
+ fd_cache_invalidate (fname);
+ da = GENERIC_READ | GENERIC_WRITE;
+ cd = OPEN_EXISTING;
+ sm = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ }
+ else if (strchr (mode, 'w'))
+ {
+ fd_cache_invalidate (fname);
+ da = GENERIC_WRITE;
+ cd = CREATE_ALWAYS;
+ sm = FILE_SHARE_WRITE;
+ }
+ else
+ {
+ da = GENERIC_READ;
+ cd = OPEN_EXISTING;
+ sm = FILE_SHARE_READ;
+ }
+
+ hfile = CreateFile (fname, da, sm, NULL, cd, FILE_ATTRIBUTE_NORMAL, NULL);
+ return hfile;
+#else
+ int oflag;
+ int cflag = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+
+ /* Note, that we do not handle all mode combinations */
+ if (strchr (mode, '+'))
+ {
+ fd_cache_invalidate (fname);
+ oflag = O_RDWR;
+ }
+ else if (strchr (mode, 'w'))
+ {
+ fd_cache_invalidate (fname);
+ oflag = O_WRONLY | O_CREAT | O_TRUNC;
+ }
+ else
+ {
+ oflag = O_RDONLY;
+ }
+#ifdef O_BINARY
+ if (strchr (mode, 'b'))
+ oflag |= O_BINARY;
+#endif
+#ifndef __riscos__
+ return open (fname, oflag, cflag);
+#else
+ {
+ struct stat buf;
+ int rc = stat (fname, &buf);
+
+ /* Don't allow iobufs on directories */
+ if (!rc && S_ISDIR (buf.st_mode) && !S_ISREG (buf.st_mode))
+ return __set_errno (EISDIR);
+ else
+ return open (fname, oflag, cflag);
+ }
+#endif
+#endif
+}
+
+
+/*
+ * Instead of closing an FD we keep it open and cache it for later reuse
+ * Note that this caching strategy only works if the process does not chdir.
+ */
+static void
+fd_cache_close (const char *fname, FILEP_OR_FD fp)
+{
+ CLOSE_CACHE cc;
+
+ assert (fp);
+ if (!fname || !*fname)
+ {
+#ifdef HAVE_DOSISH_SYSTEM
+ CloseHandle (fp);
+#else
+ close (fp);
+#endif
+ if (DBG_IOBUF)
+ log_debug ("fd_cache_close (%p) real\n", (void *) fp);
+ return;
+ }
+ /* try to reuse a slot */
+ for (cc = close_cache; cc; cc = cc->next)
+ {
+ if (cc->fp == INVALID_FP && !strcmp (cc->fname, fname))
+ {
+ cc->fp = fp;
+ if (DBG_IOBUF)
+ log_debug ("fd_cache_close (%s) used existing slot\n", fname);
+ return;
+ }
+ }
+ /* add a new one */
+ if (DBG_IOBUF)
+ log_debug ("fd_cache_close (%s) new slot created\n", fname);
+ cc = xcalloc (1, sizeof *cc + strlen (fname));
+ strcpy (cc->fname, fname);
+ cc->fp = fp;
+ cc->next = close_cache;
+ close_cache = cc;
+}
+
+/*
+ * Do an direct_open on FNAME but first try to reuse one from the fd_cache
+ */
+static FILEP_OR_FD
+fd_cache_open (const char *fname, const char *mode)
+{
+ CLOSE_CACHE cc;
+
+ assert (fname);
+ for (cc = close_cache; cc; cc = cc->next)
+ {
+ if (cc->fp != INVALID_FP && !strcmp (cc->fname, fname))
+ {
+ FILEP_OR_FD fp = cc->fp;
+ cc->fp = INVALID_FP;
+ if (DBG_IOBUF)
+ log_debug ("fd_cache_open (%s) using cached fp\n", fname);
+#ifdef HAVE_DOSISH_SYSTEM
+ if (SetFilePointer (fp, 0, NULL, FILE_BEGIN) == 0xffffffff)
+ {
+ log_error ("rewind file failed on handle %p: ec=%d\n",
+ fp, (int) GetLastError ());
+ fp = INVALID_FP;
+ }
+#else
+ if (lseek (fp, 0, SEEK_SET) == (off_t) - 1)
+ {
+ log_error ("can't rewind fd %d: %s\n", fp, strerror (errno));
+ fp = INVALID_FP;
+ }
+#endif
+ return fp;
+ }
+ }
+ if (DBG_IOBUF)
+ log_debug ("fd_cache_open (%s) not cached\n", fname);
+ return direct_open (fname, mode);
+}
+
+
+#endif /*FILE_FILTER_USES_STDIO */
+
+
+/****************
+ * Read data from a file into buf which has an allocated length of *LEN.
+ * return the number of read bytes in *LEN. OPAQUE is the FILE * of
+ * the stream. A is not used.
+ * control may be:
+ * IOBUFCTRL_INIT: called just before the function is linked into the
+ * list of function. This can be used to prepare internal
+ * data structures of the function.
+ * IOBUFCTRL_FREE: called just before the function is removed from the
+ * list of functions and can be used to release internal
+ * data structures or close a file etc.
+ * IOBUFCTRL_UNDERFLOW: called by iobuf_underflow to fill the buffer
+ * with new stuff. *RET_LEN is the available size of the
+ * buffer, and should be set to the number of bytes
+ * which were put into the buffer. The function
+ * returns 0 to indicate success, -1 on EOF and
+ * GPG_ERR_xxxxx for other errors.
+ *
+ * IOBUFCTRL_FLUSH: called by iobuf_flush() to write out the collected stuff.
+ * *RET_LAN is the number of bytes in BUF.
+ *
+ * IOBUFCTRL_CANCEL: send to all filters on behalf of iobuf_cancel. The
+ * filter may take appropriate action on this message.
+ */
+static int
+file_filter (void *opaque, int control, iobuf_t chain, byte * buf,
+ size_t * ret_len)
+{
+ file_filter_ctx_t *a = opaque;
+ FILEP_OR_FD f = a->fp;
+ size_t size = *ret_len;
+ size_t nbytes = 0;
+ int rc = 0;
+
+#ifdef FILE_FILTER_USES_STDIO
+ if (control == IOBUFCTRL_UNDERFLOW)
+ {
+ assert (size); /* need a buffer */
+ if (feof (f))
+ { /* On terminals you could easiely read as many EOFs as you call */
+ rc = -1; /* fread() or fgetc() repeatly. Every call will block until you press */
+ *ret_len = 0; /* CTRL-D. So we catch this case before we call fread() again. */
+ }
+ else
+ {
+ clearerr (f);
+ nbytes = fread (buf, 1, size, f);
+ if (feof (f) && !nbytes)
+ {
+ rc = -1; /* okay: we can return EOF now. */
+ }
+ else if (ferror (f) && errno != EPIPE)
+ {
+ rc = gpg_error_from_errno (errno);
+ log_error ("%s: read error: %s\n", a->fname, strerror (errno));
+ }
+ *ret_len = nbytes;
+ }
+ }
+ else if (control == IOBUFCTRL_FLUSH)
+ {
+ if (size)
+ {
+ clearerr (f);
+ nbytes = fwrite (buf, 1, size, f);
+ if (ferror (f))
+ {
+ rc = gpg_error_from_errno (errno);
+ log_error ("%s: write error: %s\n", a->fname, strerror (errno));
+ }
+ }
+ *ret_len = nbytes;
+ }
+ else if (control == IOBUFCTRL_INIT)
+ {
+ a->keep_open = a->no_cache = 0;
+ }
+ else if (control == IOBUFCTRL_DESC)
+ {
+ *(char **) buf = "file_filter";
+ }
+ else if (control == IOBUFCTRL_FREE)
+ {
+ if (f != stdin && f != stdout)
+ {
+ if (DBG_IOBUF)
+ log_debug ("%s: close fd %d\n", a->fname, fileno (f));
+ if (!a->keep_open)
+ fclose (f);
+ }
+ f = NULL;
+ xfree (a); /* we can free our context now */
+ }
+#else /* !stdio implementation */
+
+ if (control == IOBUFCTRL_UNDERFLOW)
+ {
+ assert (size); /* need a buffer */
+ if (a->eof_seen)
+ {
+ rc = -1;
+ *ret_len = 0;
+ }
+ else
+ {
+#ifdef HAVE_DOSISH_SYSTEM
+ unsigned long nread;
+
+ nbytes = 0;
+ if (!ReadFile (f, buf, size, &nread, NULL))
+ {
+ int ec = (int) GetLastError ();
+ if (ec != ERROR_BROKEN_PIPE)
+ {
+ rc = gpg_error_from_errno (ec);
+ log_error ("%s: read error: ec=%d\n", a->fname, ec);
+ }
+ }
+ else if (!nread)
+ {
+ a->eof_seen = 1;
+ rc = -1;
+ }
+ else
+ {
+ nbytes = nread;
+ }
+
+#else
+
+ int n;
+
+ nbytes = 0;
+ do
+ {
+ n = read (f, buf, size);
+ }
+ while (n == -1 && errno == EINTR);
+ if (n == -1)
+ { /* error */
+ if (errno != EPIPE)
+ {
+ rc = gpg_error_from_errno (errno);
+ log_error ("%s: read error: %s\n",
+ a->fname, strerror (errno));
+ }
+ }
+ else if (!n)
+ { /* eof */
+ a->eof_seen = 1;
+ rc = -1;
+ }
+ else
+ {
+ nbytes = n;
+ }
+#endif
+ *ret_len = nbytes;
+ }
+ }
+ else if (control == IOBUFCTRL_FLUSH)
+ {
+ if (size)
+ {
+#ifdef HAVE_DOSISH_SYSTEM
+ byte *p = buf;
+ unsigned long n;
+
+ nbytes = size;
+ do
+ {
+ if (size && !WriteFile (f, p, nbytes, &n, NULL))
+ {
+ int ec = (int) GetLastError ();
+ rc = gpg_error_from_errno (ec);
+ log_error ("%s: write error: ec=%d\n", a->fname, ec);
+ break;
+ }
+ p += n;
+ nbytes -= n;
+ }
+ while (nbytes);
+ nbytes = p - buf;
+#else
+ byte *p = buf;
+ int n;
+
+ nbytes = size;
+ do
+ {
+ do
+ {
+ n = write (f, p, nbytes);
+ }
+ while (n == -1 && errno == EINTR);
+ if (n > 0)
+ {
+ p += n;
+ nbytes -= n;
+ }
+ }
+ while (n != -1 && nbytes);
+ if (n == -1)
+ {
+ rc = gpg_error_from_errno (errno);
+ log_error ("%s: write error: %s\n", a->fname, strerror (errno));
+ }
+ nbytes = p - buf;
+#endif
+ }
+ *ret_len = nbytes;
+ }
+ else if (control == IOBUFCTRL_INIT)
+ {
+ a->eof_seen = 0;
+ a->keep_open = 0;
+ a->no_cache = 0;
+ }
+ else if (control == IOBUFCTRL_DESC)
+ {
+ *(char **) buf = "file_filter(fd)";
+ }
+ else if (control == IOBUFCTRL_FREE)
+ {
+#ifdef HAVE_DOSISH_SYSTEM
+ if (f != FILEP_OR_FD_FOR_STDIN && f != FILEP_OR_FD_FOR_STDOUT)
+ {
+ if (DBG_IOBUF)
+ log_debug ("%s: close handle %p\n", a->fname, f);
+ if (!a->keep_open)
+ fd_cache_close (a->no_cache ? NULL : a->fname, f);
+ }
+#else
+ if ((int) f != 0 && (int) f != 1)
+ {
+ if (DBG_IOBUF)
+ log_debug ("%s: close fd %d\n", a->fname, f);
+ if (!a->keep_open)
+ fd_cache_close (a->no_cache ? NULL : a->fname, f);
+ }
+ f = INVALID_FP;
+#endif
+ xfree (a); /* we can free our context now */
+ }
+#endif /* !stdio implementation */
+ return rc;
+}
+
+#ifdef __MINGW32__
+/* Becuase sockets are an special object under Lose32 we have to
+ * use a special filter */
+static int
+sock_filter (void *opaque, int control, iobuf_t chain, byte * buf,
+ size_t * ret_len)
+{
+ sock_filter_ctx_t *a = opaque;
+ size_t size = *ret_len;
+ size_t nbytes = 0;
+ int rc = 0;
+
+ if (control == IOBUFCTRL_UNDERFLOW)
+ {
+ assert (size); /* need a buffer */
+ if (a->eof_seen)
+ {
+ rc = -1;
+ *ret_len = 0;
+ }
+ else
+ {
+ int nread;
+
+ nread = recv (a->sock, buf, size, 0);
+ if (nread == SOCKET_ERROR)
+ {
+ int ec = (int) WSAGetLastError ();
+ rc = gpg_error_from_errno (ec);
+ log_error ("socket read error: ec=%d\n", ec);
+ }
+ else if (!nread)
+ {
+ a->eof_seen = 1;
+ rc = -1;
+ }
+ else
+ {
+ nbytes = nread;
+ }
+ *ret_len = nbytes;
+ }
+ }
+ else if (control == IOBUFCTRL_FLUSH)
+ {
+ if (size)
+ {
+ byte *p = buf;
+ int n;
+
+ nbytes = size;
+ do
+ {
+ n = send (a->sock, p, nbytes, 0);
+ if (n == SOCKET_ERROR)
+ {
+ int ec = (int) WSAGetLastError ();
+ rc = gpg_error_from_errno (ec);
+ log_error ("socket write error: ec=%d\n", ec);
+ break;
+ }
+ p += n;
+ nbytes -= n;
+ }
+ while (nbytes);
+ nbytes = p - buf;
+ }
+ *ret_len = nbytes;
+ }
+ else if (control == IOBUFCTRL_INIT)
+ {
+ a->eof_seen = 0;
+ a->keep_open = 0;
+ a->no_cache = 0;
+ }
+ else if (control == IOBUFCTRL_DESC)
+ {
+ *(char **) buf = "sock_filter";
+ }
+ else if (control == IOBUFCTRL_FREE)
+ {
+ if (!a->keep_open)
+ closesocket (a->sock);
+ xfree (a); /* we can free our context now */
+ }
+ return rc;
+}
+#endif /*__MINGW32__*/
+
+/****************
+ * This is used to implement the block write mode.
+ * Block reading is done on a byte by byte basis in readbyte(),
+ * without a filter
+ */
+static int
+block_filter (void *opaque, int control, iobuf_t chain, byte * buf,
+ size_t * ret_len)
+{
+ block_filter_ctx_t *a = opaque;
+ size_t size = *ret_len;
+ int c, needed, rc = 0;
+ char *p;
+
+ if (control == IOBUFCTRL_UNDERFLOW)
+ {
+ size_t n = 0;
+
+ p = buf;
+ assert (size); /* need a buffer */
+ if (a->eof) /* don't read any further */
+ rc = -1;
+ while (!rc && size)
+ {
+ if (!a->size)
+ { /* get the length bytes */
+ if (a->partial == 2)
+ {
+ a->eof = 1;
+ if (!n)
+ rc = -1;
+ break;
+ }
+ else if (a->partial)
+ {
+ /* These OpenPGP introduced huffman like encoded length
+ * bytes are really a mess :-( */
+ if (a->first_c)
+ {
+ c = a->first_c;
+ a->first_c = 0;
+ }
+ else if ((c = iobuf_get (chain)) == -1)
+ {
+ log_error ("block_filter: 1st length byte missing\n");
+ rc = GPG_ERR_BAD_DATA;
+ break;
+ }
+ if (c < 192)
+ {
+ a->size = c;
+ a->partial = 2;
+ if (!a->size)
+ {
+ a->eof = 1;
+ if (!n)
+ rc = -1;
+ break;
+ }
+ }
+ else if (c < 224)
+ {
+ a->size = (c - 192) * 256;
+ if ((c = iobuf_get (chain)) == -1)
+ {
+ log_error
+ ("block_filter: 2nd length byte missing\n");
+ rc = GPG_ERR_BAD_DATA;
+ break;
+ }
+ a->size += c + 192;
+ a->partial = 2;
+ if (!a->size)
+ {
+ a->eof = 1;
+ if (!n)
+ rc = -1;
+ break;
+ }
+ }
+ else if (c == 255)
+ {
+ a->size = iobuf_get (chain) << 24;
+ a->size |= iobuf_get (chain) << 16;
+ a->size |= iobuf_get (chain) << 8;
+ if ((c = iobuf_get (chain)) == -1)
+ {
+ log_error ("block_filter: invalid 4 byte length\n");
+ rc = GPG_ERR_BAD_DATA;
+ break;
+ }
+ a->size |= c;
+ }
+ else
+ { /* next partial body length */
+ a->size = 1 << (c & 0x1f);
+ }
+ /* log_debug("partial: ctx=%p c=%02x size=%u\n", a, c, a->size); */
+ }
+ else
+ { /* the gnupg partial length scheme - much better :-) */
+ c = iobuf_get (chain);
+ a->size = c << 8;
+ c = iobuf_get (chain);
+ a->size |= c;
+ if (c == -1)
+ {
+ log_error ("block_filter: error reading length info\n");
+ rc = GPG_ERR_BAD_DATA;
+ }
+ if (!a->size)
+ {
+ a->eof = 1;
+ if (!n)
+ rc = -1;
+ break;
+ }
+ }
+ }
+
+ while (!rc && size && a->size)
+ {
+ needed = size < a->size ? size : a->size;
+ c = iobuf_read (chain, p, needed);
+ if (c < needed)
+ {
+ if (c == -1)
+ c = 0;
+ log_error
+ ("block_filter %p: read error (size=%lu,a->size=%lu)\n",
+ a, (ulong) size + c, (ulong) a->size + c);
+ rc = GPG_ERR_BAD_DATA;
+ }
+ else
+ {
+ size -= c;
+ a->size -= c;
+ p += c;
+ n += c;
+ }
+ }
+ }
+ *ret_len = n;
+ }
+ else if (control == IOBUFCTRL_FLUSH)
+ {
+ if (a->partial)
+ { /* the complicated openpgp scheme */
+ size_t blen, n, nbytes = size + a->buflen;
+
+ assert (a->buflen <= OP_MIN_PARTIAL_CHUNK);
+ if (nbytes < OP_MIN_PARTIAL_CHUNK)
+ {
+ /* not enough to write a partial block out; so we store it */
+ if (!a->buffer)
+ a->buffer = xmalloc (OP_MIN_PARTIAL_CHUNK);
+ memcpy (a->buffer + a->buflen, buf, size);
+ a->buflen += size;
+ }
+ else
+ { /* okay, we can write out something */
+ /* do this in a loop to use the most efficient block lengths */
+ p = buf;
+ do
+ {
+ /* find the best matching block length - this is limited
+ * by the size of the internal buffering */
+ for (blen = OP_MIN_PARTIAL_CHUNK * 2,
+ c = OP_MIN_PARTIAL_CHUNK_2POW + 1; blen <= nbytes;
+ blen *= 2, c++)
+ ;
+ blen /= 2;
+ c--;
+ /* write the partial length header */
+ assert (c <= 0x1f); /*;-) */
+ c |= 0xe0;
+ iobuf_put (chain, c);
+ if ((n = a->buflen))
+ { /* write stuff from the buffer */
+ assert (n == OP_MIN_PARTIAL_CHUNK);
+ if (iobuf_write (chain, a->buffer, n))
+ rc = gpg_error_from_errno (errno);
+ a->buflen = 0;
+ nbytes -= n;
+ }
+ if ((n = nbytes) > blen)
+ n = blen;
+ if (n && iobuf_write (chain, p, n))
+ rc = gpg_error_from_errno (errno);
+ p += n;
+ nbytes -= n;
+ }
+ while (!rc && nbytes >= OP_MIN_PARTIAL_CHUNK);
+ /* store the rest in the buffer */
+ if (!rc && nbytes)
+ {
+ assert (!a->buflen);
+ assert (nbytes < OP_MIN_PARTIAL_CHUNK);
+ if (!a->buffer)
+ a->buffer = xmalloc (OP_MIN_PARTIAL_CHUNK);
+ memcpy (a->buffer, p, nbytes);
+ a->buflen = nbytes;
+ }
+ }
+ }
+ else
+ { /* the gnupg scheme (which is not openpgp compliant) */
+ size_t avail, n;
+
+ for (p = buf; !rc && size;)
+ {
+ n = size;
+ avail = a->size - a->count;
+ if (!avail)
+ {
+ if (n > a->size)
+ {
+ iobuf_put (chain, (a->size >> 8) & 0xff);
+ iobuf_put (chain, a->size & 0xff);
+ avail = a->size;
+ a->count = 0;
+ }
+ else
+ {
+ iobuf_put (chain, (n >> 8) & 0xff);
+ iobuf_put (chain, n & 0xff);
+ avail = n;
+ a->count = a->size - n;
+ }
+ }
+ if (n > avail)
+ n = avail;
+ if (iobuf_write (chain, p, n))
+ rc = gpg_error_from_errno (errno);
+ a->count += n;
+ p += n;
+ size -= n;
+ }
+ }
+ }
+ else if (control == IOBUFCTRL_INIT)
+ {
+ if (DBG_IOBUF)
+ log_debug ("init block_filter %p\n", a);
+ if (a->partial)
+ a->count = 0;
+ else if (a->use == 1)
+ a->count = a->size = 0;
+ else
+ a->count = a->size; /* force first length bytes */
+ a->eof = 0;
+ a->buffer = NULL;
+ a->buflen = 0;
+ }
+ else if (control == IOBUFCTRL_DESC)
+ {
+ *(char **) buf = "block_filter";
+ }
+ else if (control == IOBUFCTRL_FREE)
+ {
+ if (a->use == 2)
+ { /* write the end markers */
+ if (a->partial)
+ {
+ u32 len;
+ /* write out the remaining bytes without a partial header
+ * the length of this header may be 0 - but if it is
+ * the first block we are not allowed to use a partial header
+ * and frankly we can't do so, because this length must be
+ * a power of 2. This is _really_ complicated because we
+ * have to check the possible length of a packet prior
+ * to it's creation: a chain of filters becomes complicated
+ * and we need a lot of code to handle compressed packets etc.
+ * :-(((((((
+ */
+ /* construct header */
+ len = a->buflen;
+ /*log_debug("partial: remaining length=%u\n", len ); */
+ if (len < 192)
+ rc = iobuf_put (chain, len);
+ else if (len < 8384)
+ {
+ if (!(rc = iobuf_put (chain, ((len - 192) / 256) + 192)))
+ rc = iobuf_put (chain, ((len - 192) % 256));
+ }
+ else
+ { /* use a 4 byte header */
+ if (!(rc = iobuf_put (chain, 0xff)))
+ if (!(rc = iobuf_put (chain, (len >> 24) & 0xff)))
+ if (!(rc = iobuf_put (chain, (len >> 16) & 0xff)))
+ if (!(rc = iobuf_put (chain, (len >> 8) & 0xff)))
+ rc = iobuf_put (chain, len & 0xff);
+ }
+ if (!rc && len)
+ rc = iobuf_write (chain, a->buffer, len);
+ if (rc)
+ {
+ log_error ("block_filter: write error: %s\n",
+ strerror (errno));
+ rc = gpg_error_from_errno (errno);
+ }
+ xfree (a->buffer);
+ a->buffer = NULL;
+ a->buflen = 0;
+ }
+ else
+ {
+ iobuf_writebyte (chain, 0);
+ iobuf_writebyte (chain, 0);
+ }
+ }
+ else if (a->size)
+ {
+ log_error ("block_filter: pending bytes!\n");
+ }
+ if (DBG_IOBUF)
+ log_debug ("free block_filter %p\n", a);
+ xfree (a); /* we can free our context now */
+ }
+
+ return rc;
+}
+
+
+static void
+print_chain (iobuf_t a)
+{
+ if (!DBG_IOBUF)
+ return;
+ for (; a; a = a->chain)
+ {
+ size_t dummy_len = 0;
+ const char *desc = "[none]";
+
+ if (a->filter)
+ a->filter (a->filter_ov, IOBUFCTRL_DESC, NULL,
+ (byte *) & desc, &dummy_len);
+
+ log_debug ("iobuf chain: %d.%d `%s' filter_eof=%d start=%d len=%d\n",
+ a->no, a->subno, desc, a->filter_eof,
+ (int) a->d.start, (int) a->d.len);
+ }
+}
+
+int
+iobuf_print_chain (iobuf_t a)
+{
+ print_chain (a);
+ return 0;
+}
+
+/****************
+ * Allocate a new io buffer, with no function assigned.
+ * Use is the desired usage: 1 for input, 2 for output, 3 for temp buffer
+ * BUFSIZE is a suggested buffer size.
+ */
+iobuf_t
+iobuf_alloc (int use, size_t bufsize)
+{
+ iobuf_t a;
+ static int number = 0;
+
+ a = xcalloc (1, sizeof *a);
+ a->use = use;
+ a->d.buf = xmalloc (bufsize);
+ a->d.size = bufsize;
+ a->no = ++number;
+ a->subno = 0;
+ a->opaque = NULL;
+ a->real_fname = NULL;
+ return a;
+}
+
+int
+iobuf_close (iobuf_t a)
+{
+ iobuf_t a2;
+ size_t dummy_len = 0;
+ int rc = 0;
+
+ if (a && a->directfp)
+ {
+ fclose (a->directfp);
+ xfree (a->real_fname);
+ if (DBG_IOBUF)
+ log_debug ("iobuf_close -> %p\n", a->directfp);
+ return 0;
+ }
+
+ for (; a && !rc; a = a2)
+ {
+ a2 = a->chain;
+ if (a->use == 2 && (rc = iobuf_flush (a)))
+ log_error ("iobuf_flush failed on close: %s\n", gpg_strerror (rc));
+
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: close `%s'\n", a->no, a->subno, a->desc);
+ if (a->filter && (rc = a->filter (a->filter_ov, IOBUFCTRL_FREE,
+ a->chain, NULL, &dummy_len)))
+ log_error ("IOBUFCTRL_FREE failed on close: %s\n", gpg_strerror (rc));
+ xfree (a->real_fname);
+ if (a->d.buf)
+ {
+ memset (a->d.buf, 0, a->d.size); /* erase the buffer */
+ xfree (a->d.buf);
+ }
+ xfree (a);
+ }
+ return rc;
+}
+
+int
+iobuf_cancel (iobuf_t a)
+{
+ const char *s;
+ iobuf_t a2;
+ int rc;
+#if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__)
+ char *remove_name = NULL;
+#endif
+
+ if (a && a->use == 2)
+ {
+ s = iobuf_get_real_fname (a);
+ if (s && *s)
+ {
+#if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__)
+ remove_name = m_strdup (s);
+#else
+ remove (s);
+#endif
+ }
+ }
+
+ /* send a cancel message to all filters */
+ for (a2 = a; a2; a2 = a2->chain)
+ {
+ size_t dummy;
+ if (a2->filter)
+ a2->filter (a2->filter_ov, IOBUFCTRL_CANCEL, a2->chain, NULL, &dummy);
+ }
+
+ rc = iobuf_close (a);
+#if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__)
+ if (remove_name)
+ {
+ /* Argg, MSDOS does not allow to remove open files. So
+ * we have to do it here */
+ remove (remove_name);
+ xfree (remove_name);
+ }
+#endif
+ return rc;
+}
+
+
+/****************
+ * create a temporary iobuf, which can be used to collect stuff
+ * in an iobuf and later be written by iobuf_write_temp() to another
+ * iobuf.
+ */
+iobuf_t
+iobuf_temp ()
+{
+ iobuf_t a;
+
+ a = iobuf_alloc (3, 8192);
+
+ return a;
+}
+
+iobuf_t
+iobuf_temp_with_content (const char *buffer, size_t length)
+{
+ iobuf_t a;
+
+ a = iobuf_alloc (3, length);
+ memcpy (a->d.buf, buffer, length);
+ a->d.len = length;
+
+ return a;
+}
+
+void
+iobuf_enable_special_filenames (int yes)
+{
+ special_names_enabled = yes;
+}
+
+/*
+ * see whether the filename has the for "-&nnnn", where n is a
+ * non-zero number.
+ * Returns this number or -1 if it is not the case.
+ */
+static int
+check_special_filename (const char *fname)
+{
+ if (special_names_enabled && fname && *fname == '-' && fname[1] == '&')
+ {
+ int i;
+
+ fname += 2;
+ for (i = 0; isdigit (fname[i]); i++)
+ ;
+ if (!fname[i])
+ return atoi (fname);
+ }
+ return -1;
+}
+
+/****************
+ * Create a head iobuf for reading from a file
+ * returns: NULL if an error occures and sets errno
+ */
+iobuf_t
+iobuf_open (const char *fname)
+{
+ iobuf_t a;
+ FILEP_OR_FD fp;
+ file_filter_ctx_t *fcx;
+ size_t len;
+ int print_only = 0;
+ int fd;
+
+ if (!fname || (*fname == '-' && !fname[1]))
+ {
+ fp = FILEP_OR_FD_FOR_STDIN;
+#ifdef USE_SETMODE
+ setmode (my_fileno (fp), O_BINARY);
+#endif
+ fname = "[stdin]";
+ print_only = 1;
+ }
+ else if ((fd = check_special_filename (fname)) != -1)
+ return iobuf_fdopen (translate_file_handle (fd, 0), "rb");
+ else if ((fp = my_fopen_ro (fname, "rb")) == INVALID_FP)
+ return NULL;
+ a = iobuf_alloc (1, 8192);
+ fcx = xmalloc (sizeof *fcx + strlen (fname));
+ fcx->fp = fp;
+ fcx->print_only_name = print_only;
+ strcpy (fcx->fname, fname);
+ if (!print_only)
+ a->real_fname = xstrdup (fname);
+ a->filter = file_filter;
+ a->filter_ov = fcx;
+ file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len);
+ file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: open `%s' fd=%d\n",
+ a->no, a->subno, fname, (int) my_fileno (fcx->fp));
+
+ return a;
+}
+
+/****************
+ * Create a head iobuf for reading from a file
+ * returns: NULL if an error occures and sets errno
+ */
+iobuf_t
+iobuf_fdopen (int fd, const char *mode)
+{
+ iobuf_t a;
+ FILEP_OR_FD fp;
+ file_filter_ctx_t *fcx;
+ size_t len;
+
+#ifdef FILE_FILTER_USES_STDIO
+ if (!(fp = fdopen (fd, mode)))
+ return NULL;
+#else
+ fp = (FILEP_OR_FD) fd;
+#endif
+ a = iobuf_alloc (strchr (mode, 'w') ? 2 : 1, 8192);
+ fcx = xmalloc (sizeof *fcx + 20);
+ fcx->fp = fp;
+ fcx->print_only_name = 1;
+ sprintf (fcx->fname, "[fd %d]", fd);
+ a->filter = file_filter;
+ a->filter_ov = fcx;
+ file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len);
+ file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: fdopen `%s'\n", a->no, a->subno, fcx->fname);
+ iobuf_ioctl (a, 3, 1, NULL); /* disable fd caching */
+ return a;
+}
+
+
+iobuf_t
+iobuf_sockopen (int fd, const char *mode)
+{
+ iobuf_t a;
+#ifdef __MINGW32__
+ sock_filter_ctx_t *scx;
+ size_t len;
+
+ a = iobuf_alloc (strchr (mode, 'w') ? 2 : 1, 8192);
+ scx = m_alloc (sizeof *scx + 25);
+ scx->sock = fd;
+ scx->print_only_name = 1;
+ sprintf (scx->fname, "[sock %d]", fd);
+ a->filter = sock_filter;
+ a->filter_ov = scx;
+ sock_filter (scx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len);
+ sock_filter (scx, IOBUFCTRL_INIT, NULL, NULL, &len);
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: sockopen `%s'\n", a->no, a->subno, scx->fname);
+ iobuf_ioctl (a, 3, 1, NULL); /* disable fd caching */
+#else
+ a = iobuf_fdopen (fd, mode);
+#endif
+ return a;
+}
+
+/****************
+ * create an iobuf for writing to a file; the file will be created.
+ */
+iobuf_t
+iobuf_create (const char *fname)
+{
+ iobuf_t a;
+ FILEP_OR_FD fp;
+ file_filter_ctx_t *fcx;
+ size_t len;
+ int print_only = 0;
+ int fd;
+
+ if (!fname || (*fname == '-' && !fname[1]))
+ {
+ fp = FILEP_OR_FD_FOR_STDOUT;
+#ifdef USE_SETMODE
+ setmode (my_fileno (fp), O_BINARY);
+#endif
+ fname = "[stdout]";
+ print_only = 1;
+ }
+ else if ((fd = check_special_filename (fname)) != -1)
+ return iobuf_fdopen (translate_file_handle (fd, 1), "wb");
+ else if ((fp = my_fopen (fname, "wb")) == INVALID_FP)
+ return NULL;
+ a = iobuf_alloc (2, 8192);
+ fcx = xmalloc (sizeof *fcx + strlen (fname));
+ fcx->fp = fp;
+ fcx->print_only_name = print_only;
+ strcpy (fcx->fname, fname);
+ if (!print_only)
+ a->real_fname = xstrdup (fname);
+ a->filter = file_filter;
+ a->filter_ov = fcx;
+ file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len);
+ file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: create `%s'\n", a->no, a->subno, a->desc);
+
+ return a;
+}
+
+/****************
+ * append to an iobuf; if the file does not exist, create it.
+ * cannot be used for stdout.
+ * Note: This is not used.
+ */
+#if 0 /* not used */
+iobuf_t
+iobuf_append (const char *fname)
+{
+ iobuf_t a;
+ FILE *fp;
+ file_filter_ctx_t *fcx;
+ size_t len;
+
+ if (!fname)
+ return NULL;
+ else if (!(fp = my_fopen (fname, "ab")))
+ return NULL;
+ a = iobuf_alloc (2, 8192);
+ fcx = m_alloc (sizeof *fcx + strlen (fname));
+ fcx->fp = fp;
+ strcpy (fcx->fname, fname);
+ a->real_fname = m_strdup (fname);
+ a->filter = file_filter;
+ a->filter_ov = fcx;
+ file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len);
+ file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: append `%s'\n", a->no, a->subno, a->desc);
+
+ return a;
+}
+#endif
+
+iobuf_t
+iobuf_openrw (const char *fname)
+{
+ iobuf_t a;
+ FILEP_OR_FD fp;
+ file_filter_ctx_t *fcx;
+ size_t len;
+
+ if (!fname)
+ return NULL;
+ else if ((fp = my_fopen (fname, "r+b")) == INVALID_FP)
+ return NULL;
+ a = iobuf_alloc (2, 8192);
+ fcx = xmalloc (sizeof *fcx + strlen (fname));
+ fcx->fp = fp;
+ strcpy (fcx->fname, fname);
+ a->real_fname = xstrdup (fname);
+ a->filter = file_filter;
+ a->filter_ov = fcx;
+ file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len);
+ file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: openrw `%s'\n", a->no, a->subno, a->desc);
+
+ return a;
+}
+
+
+int
+iobuf_ioctl (iobuf_t a, int cmd, int intval, void *ptrval)
+{
+ if (cmd == 1)
+ { /* keep system filepointer/descriptor open */
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: ioctl `%s' keep=%d\n",
+ a ? a->no : -1, a ? a->subno : -1, a ? a->desc : "?",
+ intval);
+ for (; a; a = a->chain)
+ if (!a->chain && a->filter == file_filter)
+ {
+ file_filter_ctx_t *b = a->filter_ov;
+ b->keep_open = intval;
+ return 0;
+ }
+#ifdef __MINGW32__
+ else if (!a->chain && a->filter == sock_filter)
+ {
+ sock_filter_ctx_t *b = a->filter_ov;
+ b->keep_open = intval;
+ return 0;
+ }
+#endif
+ }
+ else if (cmd == 2)
+ { /* invalidate cache */
+ if (DBG_IOBUF)
+ log_debug ("iobuf-*.*: ioctl `%s' invalidate\n",
+ ptrval ? (char *) ptrval : "?");
+ if (!a && !intval && ptrval)
+ {
+#ifndef FILE_FILTER_USES_STDIO
+ fd_cache_invalidate (ptrval);
+#endif
+ return 0;
+ }
+ }
+ else if (cmd == 3)
+ { /* disallow/allow caching */
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: ioctl `%s' no_cache=%d\n",
+ a ? a->no : -1, a ? a->subno : -1, a ? a->desc : "?",
+ intval);
+ for (; a; a = a->chain)
+ if (!a->chain && a->filter == file_filter)
+ {
+ file_filter_ctx_t *b = a->filter_ov;
+ b->no_cache = intval;
+ return 0;
+ }
+#ifdef __MINGW32__
+ else if (!a->chain && a->filter == sock_filter)
+ {
+ sock_filter_ctx_t *b = a->filter_ov;
+ b->no_cache = intval;
+ return 0;
+ }
+#endif
+ }
+
+ return -1;
+}
+
+
+/****************
+ * Register an i/o filter.
+ */
+int
+iobuf_push_filter (iobuf_t a,
+ int (*f) (void *opaque, int control,
+ iobuf_t chain, byte * buf, size_t * len),
+ void *ov)
+{
+ return iobuf_push_filter2 (a, f, ov, 0);
+}
+
+int
+iobuf_push_filter2 (iobuf_t a,
+ int (*f) (void *opaque, int control,
+ iobuf_t chain, byte * buf, size_t * len),
+ void *ov, int rel_ov)
+{
+ iobuf_t b;
+ size_t dummy_len = 0;
+ int rc = 0;
+
+ if (a->directfp)
+ BUG ();
+
+ if (a->use == 2 && (rc = iobuf_flush (a)))
+ return rc;
+ /* make a copy of the current stream, so that
+ * A is the new stream and B the original one.
+ * The contents of the buffers are transferred to the
+ * new stream.
+ */
+ b = xmalloc (sizeof *b);
+ memcpy (b, a, sizeof *b);
+ /* fixme: it is stupid to keep a copy of the name at every level
+ * but we need the name somewhere because the name known by file_filter
+ * may have been released when we need the name of the file */
+ b->real_fname = a->real_fname ? xstrdup (a->real_fname) : NULL;
+ /* remove the filter stuff from the new stream */
+ a->filter = NULL;
+ a->filter_ov = NULL;
+ a->filter_ov_owner = 0;
+ a->filter_eof = 0;
+ if (a->use == 3)
+ a->use = 2; /* make a write stream from a temp stream */
+
+ if (a->use == 2)
+ { /* allocate a fresh buffer for the
+ original stream */
+ b->d.buf = xmalloc (a->d.size);
+ b->d.len = 0;
+ b->d.start = 0;
+ }
+ else
+ { /* allocate a fresh buffer for the new
+ stream */
+ a->d.buf = xmalloc (a->d.size);
+ a->d.len = 0;
+ a->d.start = 0;
+ }
+ /* disable nlimit for the new stream */
+ a->ntotal = b->ntotal + b->nbytes;
+ a->nlimit = a->nbytes = 0;
+ a->nofast &= ~1;
+ /* make a link from the new stream to the original stream */
+ a->chain = b;
+ a->opaque = b->opaque;
+
+ /* setup the function on the new stream */
+ a->filter = f;
+ a->filter_ov = ov;
+ a->filter_ov_owner = rel_ov;
+
+ a->subno = b->subno + 1;
+ f (ov, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &dummy_len);
+
+ if (DBG_IOBUF)
+ {
+ log_debug ("iobuf-%d.%d: push `%s'\n", a->no, a->subno, a->desc);
+ print_chain (a);
+ }
+
+ /* now we can initialize the new function if we have one */
+ if (a->filter && (rc = a->filter (a->filter_ov, IOBUFCTRL_INIT, a->chain,
+ NULL, &dummy_len)))
+ log_error ("IOBUFCTRL_INIT failed: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+/****************
+ * Remove an i/o filter.
+ */
+int
+pop_filter (iobuf_t a, int (*f) (void *opaque, int control,
+ iobuf_t chain, byte * buf, size_t * len),
+ void *ov)
+{
+ iobuf_t b;
+ size_t dummy_len = 0;
+ int rc = 0;
+
+ if (a->directfp)
+ BUG ();
+
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: pop `%s'\n", a->no, a->subno, a->desc);
+ if (!a->filter)
+ { /* this is simple */
+ b = a->chain;
+ assert (b);
+ xfree (a->d.buf);
+ xfree (a->real_fname);
+ memcpy (a, b, sizeof *a);
+ xfree (b);
+ return 0;
+ }
+ for (b = a; b; b = b->chain)
+ if (b->filter == f && (!ov || b->filter_ov == ov))
+ break;
+ if (!b)
+ log_bug ("pop_filter(): filter function not found\n");
+
+ /* flush this stream if it is an output stream */
+ if (a->use == 2 && (rc = iobuf_flush (b)))
+ {
+ log_error ("iobuf_flush failed in pop_filter: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+ /* and tell the filter to free it self */
+ if (b->filter && (rc = b->filter (b->filter_ov, IOBUFCTRL_FREE, b->chain,
+ NULL, &dummy_len)))
+ {
+ log_error ("IOBUFCTRL_FREE failed: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+ if (b->filter_ov && b->filter_ov_owner)
+ {
+ xfree (b->filter_ov);
+ b->filter_ov = NULL;
+ }
+
+
+ /* and see how to remove it */
+ if (a == b && !b->chain)
+ log_bug ("can't remove the last filter from the chain\n");
+ else if (a == b)
+ { /* remove the first iobuf from the chain */
+ /* everything from b is copied to a. This is save because
+ * a flush has been done on the to be removed entry
+ */
+ b = a->chain;
+ xfree (a->d.buf);
+ xfree (a->real_fname);
+ memcpy (a, b, sizeof *a);
+ xfree (b);
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: popped filter\n", a->no, a->subno);
+ }
+ else if (!b->chain)
+ { /* remove the last iobuf from the chain */
+ log_bug ("Ohh jeee, trying to remove a head filter\n");
+ }
+ else
+ { /* remove an intermediate iobuf from the chain */
+ log_bug ("Ohh jeee, trying to remove an intermediate filter\n");
+ }
+
+ return rc;
+}
+
+
+/****************
+ * read underflow: read more bytes into the buffer and return
+ * the first byte or -1 on EOF.
+ */
+static int
+underflow (iobuf_t a)
+{
+ size_t len;
+ int rc;
+
+ assert (a->d.start == a->d.len);
+ if (a->use == 3)
+ return -1; /* EOF because a temp buffer can't do an underflow */
+
+ if (a->filter_eof)
+ {
+ if (a->chain)
+ {
+ iobuf_t b = a->chain;
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: pop `%s' in underflow\n",
+ a->no, a->subno, a->desc);
+ xfree (a->d.buf);
+ xfree (a->real_fname);
+ memcpy (a, b, sizeof *a);
+ xfree (b);
+ print_chain (a);
+ }
+ else
+ a->filter_eof = 0; /* for the top level filter */
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: underflow: eof (due to filter eof)\n",
+ a->no, a->subno);
+ return -1; /* return one(!) EOF */
+ }
+ if (a->error)
+ {
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: error\n", a->no, a->subno);
+ return -1;
+ }
+
+ if (a->directfp)
+ {
+ FILE *fp = a->directfp;
+
+ len = fread (a->d.buf, 1, a->d.size, fp);
+ if (len < a->d.size)
+ {
+ if (ferror (fp))
+ a->error = gpg_error_from_errno (errno);
+ }
+ a->d.len = len;
+ a->d.start = 0;
+ return len ? a->d.buf[a->d.start++] : -1;
+ }
+
+
+ if (a->filter)
+ {
+ len = a->d.size;
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: underflow: req=%lu\n",
+ a->no, a->subno, (ulong) len);
+ rc = a->filter (a->filter_ov, IOBUFCTRL_UNDERFLOW, a->chain,
+ a->d.buf, &len);
+ if (DBG_IOBUF)
+ {
+ log_debug ("iobuf-%d.%d: underflow: got=%lu rc=%d\n",
+ a->no, a->subno, (ulong) len, rc);
+/* if( a->no == 1 ) */
+/* log_hexdump (" data:", a->d.buf, len); */
+ }
+ if (a->use == 1 && rc == -1)
+ { /* EOF: we can remove the filter */
+ size_t dummy_len = 0;
+
+ /* and tell the filter to free itself */
+ if ((rc = a->filter (a->filter_ov, IOBUFCTRL_FREE, a->chain,
+ NULL, &dummy_len)))
+ log_error ("IOBUFCTRL_FREE failed: %s\n", gpg_strerror (rc));
+ if (a->filter_ov && a->filter_ov_owner)
+ {
+ xfree (a->filter_ov);
+ a->filter_ov = NULL;
+ }
+ a->filter = NULL;
+ a->desc = NULL;
+ a->filter_ov = NULL;
+ a->filter_eof = 1;
+ if (!len && a->chain)
+ {
+ iobuf_t b = a->chain;
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: pop `%s' in underflow (!len)\n",
+ a->no, a->subno, a->desc);
+ xfree (a->d.buf);
+ xfree (a->real_fname);
+ memcpy (a, b, sizeof *a);
+ xfree (b);
+ print_chain (a);
+ }
+ }
+ else if (rc)
+ a->error = rc;
+
+ if (!len)
+ {
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: underflow: eof\n", a->no, a->subno);
+ return -1;
+ }
+ a->d.len = len;
+ a->d.start = 0;
+ return a->d.buf[a->d.start++];
+ }
+ else
+ {
+ if (DBG_IOBUF)
+ log_debug ("iobuf-%d.%d: underflow: eof (no filter)\n",
+ a->no, a->subno);
+ return -1; /* no filter; return EOF */
+ }
+}
+
+
+int
+iobuf_flush (iobuf_t a)
+{
+ size_t len;
+ int rc;
+
+ if (a->directfp)
+ return 0;
+
+ if (a->use == 3)
+ { /* increase the temp buffer */
+ char *newbuf;
+ size_t newsize = a->d.size + 8192;
+
+ if (DBG_IOBUF)
+ log_debug ("increasing temp iobuf from %lu to %lu\n",
+ (ulong) a->d.size, (ulong) newsize);
+ newbuf = xmalloc (newsize);
+ memcpy (newbuf, a->d.buf, a->d.len);
+ xfree (a->d.buf);
+ a->d.buf = newbuf;
+ a->d.size = newsize;
+ return 0;
+ }
+ else if (a->use != 2)
+ log_bug ("flush on non-output iobuf\n");
+ else if (!a->filter)
+ log_bug ("iobuf_flush: no filter\n");
+ len = a->d.len;
+ rc = a->filter (a->filter_ov, IOBUFCTRL_FLUSH, a->chain, a->d.buf, &len);
+ if (!rc && len != a->d.len)
+ {
+ log_info ("iobuf_flush did not write all!\n");
+ rc = GPG_ERR_INTERNAL;
+ }
+ else if (rc)
+ a->error = rc;
+ a->d.len = 0;
+
+ return rc;
+}
+
+
+/****************
+ * Read a byte from the iobuf; returns -1 on EOF
+ */
+int
+iobuf_readbyte (iobuf_t a)
+{
+ int c;
+
+ /* nlimit does not work together with unget */
+ /* nbytes is also not valid! */
+ if (a->unget.buf)
+ {
+ if (a->unget.start < a->unget.len)
+ return a->unget.buf[a->unget.start++];
+ xfree (a->unget.buf);
+ a->unget.buf = NULL;
+ a->nofast &= ~2;
+ }
+
+ if (a->nlimit && a->nbytes >= a->nlimit)
+ return -1; /* forced EOF */
+
+ if (a->d.start < a->d.len)
+ {
+ c = a->d.buf[a->d.start++];
+ }
+ else if ((c = underflow (a)) == -1)
+ return -1; /* EOF */
+
+ a->nbytes++;
+ return c;
+}
+
+
+int
+iobuf_read (iobuf_t a, byte * buf, unsigned buflen)
+{
+ int c, n;
+
+ if (a->unget.buf || a->nlimit)
+ {
+ /* handle special cases */
+ for (n = 0; n < buflen; n++)
+ {
+ if ((c = iobuf_readbyte (a)) == -1)
+ {
+ if (!n)
+ return -1; /* eof */
+ break;
+ }
+ else if (buf)
+ *buf = c;
+ if (buf)
+ buf++;
+ }
+ return n;
+ }
+
+ n = 0;
+ do
+ {
+ if (n < buflen && a->d.start < a->d.len)
+ {
+ unsigned size = a->d.len - a->d.start;
+ if (size > buflen - n)
+ size = buflen - n;
+ if (buf)
+ memcpy (buf, a->d.buf + a->d.start, size);
+ n += size;
+ a->d.start += size;
+ if (buf)
+ buf += size;
+ }
+ if (n < buflen)
+ {
+ if ((c = underflow (a)) == -1)
+ {
+ a->nbytes += n;
+ return n ? n : -1 /*EOF*/;
+ }
+ if (buf)
+ *buf++ = c;
+ n++;
+ }
+ }
+ while (n < buflen);
+ a->nbytes += n;
+ return n;
+}
+
+
+/****************
+ * Have a look at the iobuf.
+ * NOTE: This only works in special cases.
+ */
+int
+iobuf_peek (iobuf_t a, byte * buf, unsigned buflen)
+{
+ int n = 0;
+
+ if (a->filter_eof)
+ return -1;
+
+ if (!(a->d.start < a->d.len))
+ {
+ if (underflow (a) == -1)
+ return -1;
+ /* and unget this character */
+ assert (a->d.start == 1);
+ a->d.start = 0;
+ }
+
+ for (n = 0; n < buflen && (a->d.start + n) < a->d.len; n++, buf++)
+ *buf = a->d.buf[n];
+ return n;
+}
+
+
+
+
+int
+iobuf_writebyte (iobuf_t a, unsigned c)
+{
+ int rc;
+
+ if (a->directfp)
+ BUG ();
+
+ if (a->d.len == a->d.size)
+ if ((rc=iobuf_flush (a)))
+ return rc;
+
+ assert (a->d.len < a->d.size);
+ a->d.buf[a->d.len++] = c;
+ return 0;
+}
+
+
+int
+iobuf_write (iobuf_t a, byte * buf, unsigned buflen)
+{
+ int rc;
+
+ if (a->directfp)
+ BUG ();
+
+ do
+ {
+ if (buflen && a->d.len < a->d.size)
+ {
+ unsigned size = a->d.size - a->d.len;
+ if (size > buflen)
+ size = buflen;
+ memcpy (a->d.buf + a->d.len, buf, size);
+ buflen -= size;
+ buf += size;
+ a->d.len += size;
+ }
+ if (buflen)
+ {
+ rc = iobuf_flush (a);
+ if (rc)
+ return rc;
+ }
+ }
+ while (buflen);
+ return 0;
+}
+
+
+int
+iobuf_writestr (iobuf_t a, const char *buf)
+{
+ int rc;
+
+ for (; *buf; buf++)
+ if ((rc=iobuf_writebyte (a, *buf)))
+ return rc;
+ return 0;
+}
+
+
+
+/****************
+ * copy the contents of TEMP to A.
+ */
+int
+iobuf_write_temp (iobuf_t a, iobuf_t temp)
+{
+ while (temp->chain)
+ pop_filter (temp, temp->filter, NULL);
+ return iobuf_write (a, temp->d.buf, temp->d.len);
+}
+
+/****************
+ * copy the contents of the temp io stream to BUFFER.
+ */
+size_t
+iobuf_temp_to_buffer (iobuf_t a, byte * buffer, size_t buflen)
+{
+ size_t n = a->d.len;
+
+ if (n > buflen)
+ n = buflen;
+ memcpy (buffer, a->d.buf, n);
+ return n;
+}
+
+
+/****************
+ * Call this function to terminate processing of the temp stream
+ * without closing it. This removes all filters from the stream
+ * makes sure that iobuf_get_temp_{buffer,length}() returns correct
+ * values.
+ */
+void
+iobuf_flush_temp (iobuf_t temp)
+{
+ while (temp->chain)
+ pop_filter (temp, temp->filter, NULL);
+}
+
+
+/****************
+ * Set a limit on how many bytes may be read from the input stream A.
+ * Setting the limit to 0 disables this feature.
+ */
+void
+iobuf_set_limit (iobuf_t a, off_t nlimit)
+{
+ if (nlimit)
+ a->nofast |= 1;
+ else
+ a->nofast &= ~1;
+ a->nlimit = nlimit;
+ a->ntotal += a->nbytes;
+ a->nbytes = 0;
+}
+
+
+
+/****************
+ * Return the length of an open file
+ */
+off_t
+iobuf_get_filelength (iobuf_t a)
+{
+ struct stat st;
+
+ if (a->directfp)
+ {
+ FILE *fp = a->directfp;
+
+ if (!fstat (fileno (fp), &st))
+ return st.st_size;
+ log_error ("fstat() failed: %s\n", strerror (errno));
+ return 0;
+ }
+
+ /* Hmmm: file_filter may have already been removed */
+ for (; a; a = a->chain)
+ if (!a->chain && a->filter == file_filter)
+ {
+ file_filter_ctx_t *b = a->filter_ov;
+ FILEP_OR_FD fp = b->fp;
+
+#if defined(HAVE_DOSISH_SYSTEM) && !defined(FILE_FILTER_USES_STDIO)
+ ulong size;
+
+ if ((size = GetFileSize (fp, NULL)) != 0xffffffff)
+ return size;
+ log_error ("GetFileSize for handle %p failed: ec=%d\n",
+ fp, (int) GetLastError ());
+#else
+ if (!fstat (my_fileno (fp), &st))
+ return st.st_size;
+ log_error ("fstat() failed: %s\n", strerror (errno));
+#endif
+ break;
+ }
+
+ return 0;
+}
+
+/****************
+ * Tell the file position, where the next read will take place
+ */
+off_t
+iobuf_tell (iobuf_t a)
+{
+ return a->ntotal + a->nbytes;
+}
+
+
+#if !defined(HAVE_FSEEKO) && !defined(fseeko)
+
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
+#ifndef LONG_MAX
+# define LONG_MAX ((long) ((unsigned long) -1 >> 1))
+#endif
+#ifndef LONG_MIN
+# define LONG_MIN (-1 - LONG_MAX)
+#endif
+
+/****************
+ * A substitute for fseeko, for hosts that don't have it.
+ */
+static int
+fseeko (FILE * stream, off_t newpos, int whence)
+{
+ while (newpos != (long) newpos)
+ {
+ long pos = newpos < 0 ? LONG_MIN : LONG_MAX;
+ if (fseek (stream, pos, whence) != 0)
+ return -1;
+ newpos -= pos;
+ whence = SEEK_CUR;
+ }
+ return fseek (stream, (long) newpos, whence);
+}
+#endif
+
+/****************
+ * This is a very limited implementation. It simply discards all internal
+ * buffering and removes all filters but the first one.
+ */
+int
+iobuf_seek (iobuf_t a, off_t newpos)
+{
+ file_filter_ctx_t *b = NULL;
+
+ if (a->directfp)
+ {
+ FILE *fp = a->directfp;
+ if (fseeko (fp, newpos, SEEK_SET))
+ {
+ log_error ("can't seek: %s\n", strerror (errno));
+ return -1;
+ }
+ clearerr (fp);
+ }
+ else
+ {
+ for (; a; a = a->chain)
+ {
+ if (!a->chain && a->filter == file_filter)
+ {
+ b = a->filter_ov;
+ break;
+ }
+ }
+ if (!a)
+ return -1;
+#ifdef FILE_FILTER_USES_STDIO
+ if (fseeko (b->fp, newpos, SEEK_SET))
+ {
+ log_error ("can't fseek: %s\n", strerror (errno));
+ return -1;
+ }
+#else
+#ifdef HAVE_DOSISH_SYSTEM
+ if (SetFilePointer (b->fp, newpos, NULL, FILE_BEGIN) == 0xffffffff)
+ {
+ log_error ("SetFilePointer failed on handle %p: ec=%d\n",
+ b->fp, (int) GetLastError ());
+ return -1;
+ }
+#else
+ if (lseek (b->fp, newpos, SEEK_SET) == (off_t) - 1)
+ {
+ log_error ("can't lseek: %s\n", strerror (errno));
+ return -1;
+ }
+#endif
+#endif
+ }
+ a->d.len = 0; /* discard buffer */
+ a->d.start = 0;
+ a->nbytes = 0;
+ a->nlimit = 0;
+ a->nofast &= ~1;
+ a->ntotal = newpos;
+ a->error = 0;
+ /* remove filters, but the last */
+ if (a->chain)
+ log_debug ("pop_filter called in iobuf_seek - please report\n");
+ while (a->chain)
+ pop_filter (a, a->filter, NULL);
+
+ return 0;
+}
+
+
+
+
+
+
+/****************
+ * Retrieve the real filename
+ */
+const char *
+iobuf_get_real_fname (iobuf_t a)
+{
+ if (a->real_fname)
+ return a->real_fname;
+
+ /* the old solution */
+ for (; a; a = a->chain)
+ if (!a->chain && a->filter == file_filter)
+ {
+ file_filter_ctx_t *b = a->filter_ov;
+ return b->print_only_name ? NULL : b->fname;
+ }
+
+ return NULL;
+}
+
+
+/****************
+ * Retrieve the filename
+ */
+const char *
+iobuf_get_fname (iobuf_t a)
+{
+ for (; a; a = a->chain)
+ if (!a->chain && a->filter == file_filter)
+ {
+ file_filter_ctx_t *b = a->filter_ov;
+ return b->fname;
+ }
+
+ return NULL;
+}
+
+/****************
+ * Start the block write mode, see rfc1991.new for details.
+ * A value of 0 for N stops this mode (flushes and writes
+ * the end marker)
+ */
+void
+iobuf_set_block_mode (iobuf_t a, size_t n)
+{
+ block_filter_ctx_t *ctx = xcalloc (1, sizeof *ctx);
+
+ assert (a->use == 1 || a->use == 2);
+ ctx->use = a->use;
+ if (!n)
+ {
+ if (a->use == 1)
+ log_debug ("pop_filter called in set_block_mode - please report\n");
+ pop_filter (a, block_filter, NULL);
+ }
+ else
+ {
+ ctx->size = n; /* only needed for use 2 */
+ iobuf_push_filter (a, block_filter, ctx);
+ }
+}
+
+/****************
+ * enable partial block mode as described in the OpenPGP draft.
+ * LEN is the first length byte on read, but ignored on writes.
+ */
+void
+iobuf_set_partial_block_mode (iobuf_t a, size_t len)
+{
+ block_filter_ctx_t *ctx = xcalloc (1, sizeof *ctx);
+
+ assert (a->use == 1 || a->use == 2);
+ ctx->use = a->use;
+ if (!len)
+ {
+ if (a->use == 1)
+ log_debug ("pop_filter called in set_partial_block_mode"
+ " - please report\n");
+ pop_filter (a, block_filter, NULL);
+ }
+ else
+ {
+ ctx->partial = 1;
+ ctx->size = 0;
+ ctx->first_c = len;
+ iobuf_push_filter (a, block_filter, ctx);
+ }
+}
+
+
+/****************
+ * Checks whether the stream is in block mode
+ * Note: This does not work if other filters are pushed on the stream.
+ */
+int
+iobuf_in_block_mode (iobuf_t a)
+{
+ if (a && a->filter == block_filter)
+ return 1; /* yes */
+ return 0; /* no */
+}
+
+
+/****************
+ * Same as fgets() but if the buffer is too short a larger one will
+ * be allocated up to some limit *max_length.
+ * A line is considered a byte stream ending in a LF.
+ * Returns the length of the line. EOF is indicated by a line of
+ * length zero. The last LF may be missing due to an EOF.
+ * is max_length is zero on return, the line has been truncated.
+ *
+ * Note: The buffer is allocated with enough space to append a CR,LF,EOL
+ */
+unsigned int
+iobuf_read_line (iobuf_t a, byte ** addr_of_buffer,
+ unsigned *length_of_buffer, unsigned *max_length)
+{
+ int c;
+ char *buffer = *addr_of_buffer;
+ unsigned length = *length_of_buffer;
+ unsigned nbytes = 0;
+ unsigned maxlen = *max_length;
+ char *p;
+
+ if (!buffer)
+ { /* must allocate a new buffer */
+ length = 256;
+ buffer = xmalloc (length);
+ *addr_of_buffer = buffer;
+ *length_of_buffer = length;
+ }
+
+ length -= 3; /* reserve 3 bytes (cr,lf,eol) */
+ p = buffer;
+ while ((c = iobuf_get (a)) != -1)
+ {
+ if (nbytes == length)
+ { /* increase the buffer */
+ if (length > maxlen)
+ { /* this is out limit */
+ /* skip the rest of the line */
+ while (c != '\n' && (c = iobuf_get (a)) != -1)
+ ;
+ *p++ = '\n'; /* always append a LF (we have reserved space) */
+ nbytes++;
+ *max_length = 0; /* indicate truncation */
+ break;
+ }
+ length += 3; /* correct for the reserved byte */
+ length += length < 1024 ? 256 : 1024;
+ buffer = xrealloc (buffer, length);
+ *addr_of_buffer = buffer;
+ *length_of_buffer = length;
+ length -= 3; /* and reserve again */
+ p = buffer + nbytes;
+ }
+ *p++ = c;
+ nbytes++;
+ if (c == '\n')
+ break;
+ }
+ *p = 0; /* make sure the line is a string */
+
+ return nbytes;
+}
+
+/* This is the non iobuf specific function */
+int
+iobuf_translate_file_handle (int fd, int for_write)
+{
+#ifdef __MINGW32__
+ {
+ int x;
+
+ if (fd <= 2)
+ return fd; /* do not do this for error, stdin, stdout, stderr */
+
+ x = _open_osfhandle (fd, for_write ? 1 : 0);
+ if (x == -1)
+ log_error ("failed to translate osfhandle %p\n", (void *) fd);
+ else
+ {
+ /*log_info ("_open_osfhandle %p yields %d%s\n",
+ (void*)fd, x, for_write? " for writing":"" ); */
+ fd = x;
+ }
+ }
+#endif
+ return fd;
+}
+
+static int
+translate_file_handle (int fd, int for_write)
+{
+#ifdef __MINGW32__
+#ifdef FILE_FILTER_USES_STDIO
+ fd = iobuf_translate_file_handle (fd, for_write);
+#else
+ {
+ int x;
+
+ if (fd == 0)
+ x = (int) GetStdHandle (STD_INPUT_HANDLE);
+ else if (fd == 1)
+ x = (int) GetStdHandle (STD_OUTPUT_HANDLE);
+ else if (fd == 2)
+ x = (int) GetStdHandle (STD_ERROR_HANDLE);
+ else
+ x = fd;
+
+ if (x == -1)
+ log_debug ("GetStdHandle(%d) failed: ec=%d\n",
+ fd, (int) GetLastError ());
+
+ fd = x;
+ }
+#endif
+#endif
+ return fd;
+}
diff --git a/common/iobuf.h b/common/iobuf.h
new file mode 100644
index 000000000..0af94e22d
--- /dev/null
+++ b/common/iobuf.h
@@ -0,0 +1,170 @@
+/* iobuf.h - I/O buffer
+ * Copyright (C) 1998, 1999, 2000, 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GNUPG.
+ *
+ * GNUPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef GNUPG_COMMON_IOBUF_H
+#define GNUPG_COMMON_IOBUF_H
+
+#include "../include/types.h" /* fixme: should be moved elsewhere. */
+
+
+#define DBG_IOBUF iobuf_debug_mode
+
+
+#define IOBUFCTRL_INIT 1
+#define IOBUFCTRL_FREE 2
+#define IOBUFCTRL_UNDERFLOW 3
+#define IOBUFCTRL_FLUSH 4
+#define IOBUFCTRL_DESC 5
+#define IOBUFCTRL_CANCEL 6
+#define IOBUFCTRL_USER 16
+
+typedef struct iobuf_struct *iobuf_t;
+
+/* fixme: we should hide most of this stuff */
+struct iobuf_struct
+{
+ int use; /* 1 input , 2 output, 3 temp */
+ off_t nlimit;
+ off_t nbytes; /* used together with nlimit */
+ off_t ntotal; /* total bytes read (position of stream) */
+ int nofast; /* used by the iobuf_get() */
+ void *directfp;
+ struct
+ {
+ size_t size; /* allocated size */
+ size_t start; /* number of invalid bytes at the begin of the buffer */
+ size_t len; /* currently filled to this size */
+ byte *buf;
+ }
+ d;
+ int filter_eof;
+ int error;
+ int (*filter) (void *opaque, int control,
+ iobuf_t chain, byte * buf, size_t * len);
+ void *filter_ov; /* value for opaque */
+ int filter_ov_owner;
+ char *real_fname;
+ iobuf_t chain; /* next iobuf used for i/o if any
+ (passed to filter) */
+ int no, subno;
+ const char *desc;
+ void *opaque; /* can be used to hold any information
+ this value is copied to all
+ instances */
+ struct
+ {
+ size_t size; /* allocated size */
+ size_t start; /* number of invalid bytes at the
+ begin of the buffer */
+ size_t len; /* currently filled to this size */
+ byte *buf;
+ }
+ unget;
+};
+
+#ifndef EXTERN_UNLESS_MAIN_MODULE
+#if defined (__riscos__) && !defined (INCLUDED_BY_MAIN_MODULE)
+#define EXTERN_UNLESS_MAIN_MODULE extern
+#else
+#define EXTERN_UNLESS_MAIN_MODULE
+#endif
+#endif
+EXTERN_UNLESS_MAIN_MODULE int iobuf_debug_mode;
+
+void iobuf_enable_special_filenames (int yes);
+iobuf_t iobuf_alloc (int use, size_t bufsize);
+iobuf_t iobuf_temp (void);
+iobuf_t iobuf_temp_with_content (const char *buffer, size_t length);
+iobuf_t iobuf_open (const char *fname);
+iobuf_t iobuf_fdopen (int fd, const char *mode);
+iobuf_t iobuf_sockopen (int fd, const char *mode);
+iobuf_t iobuf_create (const char *fname);
+iobuf_t iobuf_append (const char *fname);
+iobuf_t iobuf_openrw (const char *fname);
+int iobuf_ioctl (iobuf_t a, int cmd, int intval, void *ptrval);
+int iobuf_close (iobuf_t iobuf);
+int iobuf_cancel (iobuf_t iobuf);
+
+int iobuf_push_filter (iobuf_t a, int (*f) (void *opaque, int control,
+ iobuf_t chain, byte * buf,
+ size_t * len), void *ov);
+int iobuf_push_filter2 (iobuf_t a,
+ int (*f) (void *opaque, int control, iobuf_t chain,
+ byte * buf, size_t * len), void *ov,
+ int rel_ov);
+int iobuf_flush (iobuf_t a);
+void iobuf_clear_eof (iobuf_t a);
+#define iobuf_set_error(a) do { (a)->error = 1; } while(0)
+#define iobuf_error(a) ((a)->error)
+
+void iobuf_set_limit (iobuf_t a, off_t nlimit);
+
+off_t iobuf_tell (iobuf_t a);
+int iobuf_seek (iobuf_t a, off_t newpos);
+
+int iobuf_readbyte (iobuf_t a);
+int iobuf_read (iobuf_t a, byte * buf, unsigned buflen);
+unsigned iobuf_read_line (iobuf_t a, byte ** addr_of_buffer,
+ unsigned *length_of_buffer, unsigned *max_length);
+int iobuf_peek (iobuf_t a, byte * buf, unsigned buflen);
+int iobuf_writebyte (iobuf_t a, unsigned c);
+int iobuf_write (iobuf_t a, byte * buf, unsigned buflen);
+int iobuf_writestr (iobuf_t a, const char *buf);
+
+void iobuf_flush_temp (iobuf_t temp);
+int iobuf_write_temp (iobuf_t a, iobuf_t temp);
+size_t iobuf_temp_to_buffer (iobuf_t a, byte * buffer, size_t buflen);
+void iobuf_unget_and_close_temp (iobuf_t a, iobuf_t temp);
+
+off_t iobuf_get_filelength (iobuf_t a);
+#define IOBUF_FILELENGTH_LIMIT 0xffffffff
+const char *iobuf_get_real_fname (iobuf_t a);
+const char *iobuf_get_fname (iobuf_t a);
+
+void iobuf_set_block_mode (iobuf_t a, size_t n);
+void iobuf_set_partial_block_mode (iobuf_t a, size_t len);
+int iobuf_in_block_mode (iobuf_t a);
+
+int iobuf_translate_file_handle (int fd, int for_write);
+
+
+/* get a byte form the iobuf; must check for eof prior to this function
+ * this function returns values in the range 0 .. 255 or -1 to indicate EOF
+ * iobuf_get_noeof() does not return -1 to indicate EOF, but masks the
+ * returned value to be in the range 0 ..255.
+ */
+#define iobuf_get(a) \
+ ( ((a)->nofast || (a)->d.start >= (a)->d.len )? \
+ iobuf_readbyte((a)) : ( (a)->nbytes++, (a)->d.buf[(a)->d.start++] ) )
+#define iobuf_get_noeof(a) (iobuf_get((a))&0xff)
+
+/* write a byte to the iobuf and return true on write error
+ * This macro does only write the low order byte
+ */
+#define iobuf_put(a,c) iobuf_writebyte(a,c)
+
+#define iobuf_where(a) "[don't know]"
+#define iobuf_id(a) ((a)->no)
+
+#define iobuf_get_temp_buffer(a) ( (a)->d.buf )
+#define iobuf_get_temp_length(a) ( (a)->d.len )
+#define iobuf_is_temp(a) ( (a)->use == 3 )
+
+#endif /*GNUPG_COMMON_IOBUF_H*/
diff --git a/common/maperror.c b/common/maperror.c
new file mode 100644
index 000000000..13657bece
--- /dev/null
+++ b/common/maperror.c
@@ -0,0 +1,157 @@
+/* maperror.c - Error mapping
+ * Copyright (C) 2001, 2002 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#include <ksba.h>
+#include <assuan.h>
+
+#include "util.h"
+#include "errors.h"
+
+/* Note: we might want to wrap this in a macro to get our hands on
+ the line and file where the error occured */
+int
+map_ksba_err (int err)
+{
+ switch (err)
+ {
+ case -1:
+ case 0:
+ break;
+
+ case KSBA_Out_Of_Core: err = GPG_ERR_ENOMEM; break;
+ case KSBA_Invalid_Value: err = GPG_ERR_INV_VALUE; break;
+ case KSBA_Not_Implemented: err = GPG_ERR_NOT_IMPLEMENTED; break;
+ case KSBA_Conflict: err = GPG_ERR_CONFLICT; break;
+ case KSBA_Read_Error: err = GPG_ERR_EIO; break;
+ case KSBA_Write_Error: err = GPG_ERR_EIO; break;
+ case KSBA_No_Data: err = GPG_ERR_NO_DATA; break;
+ case KSBA_Bug: err = GPG_ERR_BUG; break;
+ case KSBA_Unsupported_Algorithm: err = GPG_ERR_UNSUPPORTED_ALGORITHM; break;
+ case KSBA_Invalid_Index: err = GPG_ERR_INV_INDEX; break;
+ case KSBA_Invalid_Sexp: err = GPG_ERR_INV_SEXP; break;
+ case KSBA_Unknown_Sexp: err = GPG_ERR_UNKNOWN_SEXP; break;
+
+ default:
+ err = GPG_ERR_GENERAL;
+ break;
+ }
+ return err;
+}
+
+
+int
+map_gcry_err (int err)
+{
+ return err;
+}
+
+int
+map_kbx_err (int err)
+{
+ return err;
+}
+
+/* Map Assuan error code ERR to an GPG_ERR_ code. We need to
+ distinguish between genuine (and legacy) Assuan error codes and
+ application error codes shared with all GnuPG modules. The rule is
+ simple: All errors with a gpg_err_source of UNKNOWN are genuine
+ Assuan codes all others are passed verbatim through. */
+gpg_error_t
+map_assuan_err (int err)
+{
+ gpg_err_code_t ec;
+
+ if (gpg_err_source (err))
+ return err;
+
+ switch (err)
+ {
+ case -1: ec = GPG_ERR_EOF; break;
+ case 0: ec = 0; break;
+
+ case ASSUAN_Canceled: ec = GPG_ERR_CANCELED; break;
+ case ASSUAN_Invalid_Index: ec = GPG_ERR_INV_INDEX; break;
+
+ case ASSUAN_Not_Implemented: ec = GPG_ERR_NOT_IMPLEMENTED; break;
+ case ASSUAN_Server_Fault: ec = GPG_ERR_ASSUAN_SERVER_FAULT; break;
+ case ASSUAN_No_Public_Key: ec = GPG_ERR_NO_PUBKEY; break;
+ case ASSUAN_No_Secret_Key: ec = GPG_ERR_NO_SECKEY; break;
+
+ case ASSUAN_Cert_Revoked: ec = GPG_ERR_CERT_REVOKED; break;
+ case ASSUAN_No_CRL_For_Cert: ec = GPG_ERR_NO_CRL_KNOWN; break;
+ case ASSUAN_CRL_Too_Old: ec = GPG_ERR_CRL_TOO_OLD; break;
+
+ case ASSUAN_Not_Trusted: ec = GPG_ERR_NOT_TRUSTED; break;
+
+ case ASSUAN_Card_Error: ec = GPG_ERR_CARD; break;
+ case ASSUAN_Invalid_Card: ec = GPG_ERR_INV_CARD; break;
+ case ASSUAN_No_PKCS15_App: ec = GPG_ERR_NO_PKCS15_APP; break;
+ case ASSUAN_Card_Not_Present: ec= GPG_ERR_CARD_NOT_PRESENT; break;
+ case ASSUAN_Not_Confirmed: ec = GPG_ERR_NOT_CONFIRMED; break;
+ case ASSUAN_Invalid_Id: ec = GPG_ERR_INV_ID; break;
+
+ default:
+ ec = err < 100? GPG_ERR_ASSUAN_SERVER_FAULT : GPG_ERR_ASSUAN;
+ break;
+ }
+ return gpg_err_make (GPG_ERR_SOURCE_UNKNOWN, ec);
+}
+
+/* Map GPG_xERR_xx error codes to Assuan status codes */
+int
+map_to_assuan_status (int rc)
+{
+ gpg_err_code_t ec = gpg_err_code (rc);
+ gpg_err_source_t es = gpg_err_source (rc);
+
+ if (!rc)
+ return 0;
+ if (!es)
+ {
+ es = GPG_ERR_SOURCE_USER_4; /* This should not happen, but we
+ need to make sure to pass a new
+ Assuan errorcode along. */
+ log_debug ("map_to_assuan_status called with no error source\n");
+ }
+
+ if (ec == -1)
+ ec = GPG_ERR_NO_DATA; /* That used to be ASSUAN_No_Data_Available. */
+
+ return gpg_err_make (es, ec);
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/common/membuf.c b/common/membuf.c
new file mode 100644
index 000000000..69e4ab908
--- /dev/null
+++ b/common/membuf.c
@@ -0,0 +1,89 @@
+/* membuf.c - A simple implementation of a dynamic buffer
+ * Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "membuf.h"
+
+#include "util.h"
+
+
+/* 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. */
+
+void
+init_membuf (membuf_t *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 = errno;
+}
+
+
+void
+put_membuf (membuf_t *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 = errno;
+ return;
+ }
+ mb->buf = p;
+ }
+ memcpy (mb->buf + mb->len, buf, len);
+ mb->len += len;
+}
+
+
+void *
+get_membuf (membuf_t *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 = ENOMEM; /* hack to make sure it won't get reused. */
+ return p;
+}
diff --git a/common/membuf.h b/common/membuf.h
new file mode 100644
index 000000000..c199363cc
--- /dev/null
+++ b/common/membuf.h
@@ -0,0 +1,41 @@
+/* membuf.h - A simple implementation of a dynamic buffer
+ * Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef GNUPG_COMMON_MEMBUF_H
+#define GNUPG_COMMON_MEMBUF_H
+
+/* The definition of the structure is private, we only need it here,
+ so it can be allocated on the stack. */
+struct private_membuf_s {
+ size_t len;
+ size_t size;
+ char *buf;
+ int out_of_core;
+};
+
+typedef struct private_membuf_s membuf_t;
+
+
+void init_membuf (membuf_t *mb, int initiallen);
+void put_membuf (membuf_t *mb, const void *buf, size_t len);
+void *get_membuf (membuf_t *mb, size_t *len);
+
+
+#endif /*GNUPG_COMMON_MEMBUF_H*/
diff --git a/common/miscellaneous.c b/common/miscellaneous.c
new file mode 100644
index 000000000..bdb12c574
--- /dev/null
+++ b/common/miscellaneous.c
@@ -0,0 +1,126 @@
+/* miscellaneous.c - Stuff not fitting elsewhere
+ * Copyright (C) 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "util.h"
+#include "iobuf.h"
+
+/* Decide whether the filename is stdout or a real filename and return
+ * an appropriate string. */
+const char *
+print_fname_stdout (const char *s)
+{
+ if( !s || (*s == '-' && !s[1]) )
+ return "[stdout]";
+ return s;
+}
+
+
+/* Decide whether the filename is stdin or a real filename and return
+ * an appropriate string. */
+const char *
+print_fname_stdin (const char *s)
+{
+ if( !s || (*s == '-' && !s[1]) )
+ return "[stdin]";
+ return s;
+}
+
+void
+print_string( FILE *fp, const byte *p, size_t n, int delim )
+{
+ print_sanitized_buffer (fp, p, n, delim);
+}
+
+void
+print_utf8_string2 ( FILE *fp, const byte *p, size_t n, int delim )
+{
+ print_sanitized_utf8_buffer (fp, p, n, delim);
+}
+
+void
+print_utf8_string( FILE *fp, const byte *p, size_t n )
+{
+ print_utf8_string2 (fp, p, n, 0);
+}
+
+char *
+make_printable_string( const byte *p, size_t n, int delim )
+{
+ return sanitize_buffer (p, n, delim);
+}
+
+
+/*
+ * Check if the file is compressed.
+ */
+int
+is_file_compressed (const char *s, int *ret_rc)
+{
+ iobuf_t a;
+ byte buf[4];
+ int i, rc = 0;
+
+ struct magic_compress_s {
+ size_t len;
+ byte magic[4];
+ } magic[] = {
+ { 3, { 0x42, 0x5a, 0x68, 0x00 } }, /* bzip2 */
+ { 3, { 0x1f, 0x8b, 0x08, 0x00 } }, /* gzip */
+ { 4, { 0x50, 0x4b, 0x03, 0x04 } }, /* (pk)zip */
+ };
+
+ if ( !s || (*s == '-' && !s[1]) || !ret_rc )
+ return 0; /* We can't check stdin or no file was given */
+
+ a = iobuf_open( s );
+ if ( a == NULL ) {
+ *ret_rc = gpg_error_from_errno (errno);
+ return 0;
+ }
+
+ if ( iobuf_get_filelength( a ) < 4 ) {
+ *ret_rc = 0;
+ goto leave;
+ }
+
+ if ( iobuf_read( a, buf, 4 ) == -1 ) {
+ *ret_rc = a->error;
+ goto leave;
+ }
+
+ for ( i = 0; i < DIM( magic ); i++ ) {
+ if ( !memcmp( buf, magic[i].magic, magic[i].len ) ) {
+ *ret_rc = 0;
+ rc = 1;
+ break;
+ }
+ }
+
+leave:
+ iobuf_close( a );
+ return rc;
+}
+
+
+
diff --git a/common/simple-pwquery.c b/common/simple-pwquery.c
new file mode 100644
index 000000000..afdc4e2a4
--- /dev/null
+++ b/common/simple-pwquery.c
@@ -0,0 +1,486 @@
+/* simple-pwquery.c - A simple password query cleint for gpg-agent
+ * Copyright (C) 2002 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+/* This module is intended as a standalone client implementation to
+ gpg-agent's GET_PASSPHRASE command. In particular it does not use
+ the Assuan library and can only cope with an already running
+ gpg-agent. Some stuff is configurable in the header file. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#define SIMPLE_PWQUERY_IMPLEMENTATION 1
+#include "simple-pwquery.h"
+
+#if defined(SPWQ_USE_LOGGING) && !defined(HAVE_JNLIB_LOGGING)
+# undef SPWQ_USE_LOGGING
+#endif
+
+#ifndef _
+#define _(a) (a)
+#endif
+
+#if !defined (hexdigitp) && !defined (xtoi_2)
+#define digitp(p) (*(p) >= '0' && *(p) <= '9')
+#define hexdigitp(a) (digitp (a) \
+ || (*(a) >= 'A' && *(a) <= 'F') \
+ || (*(a) >= 'a' && *(a) <= 'f'))
+#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
+ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
+#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
+#endif
+
+
+/* Write NBYTES of BUF to file descriptor FD. */
+static int
+writen (int fd, const void *buf, size_t nbytes)
+{
+ size_t nleft = nbytes;
+ int nwritten;
+
+ while (nleft > 0)
+ {
+ nwritten = write( fd, buf, nleft );
+ if (nwritten < 0)
+ {
+ if (errno == EINTR)
+ nwritten = 0;
+ else {
+#ifdef SPWQ_USE_LOGGING
+ log_error ("write failed: %s\n", strerror (errno));
+#endif
+ return SPWQ_IO_ERROR;
+ }
+ }
+ nleft -= nwritten;
+ buf = (const char*)buf + nwritten;
+ }
+
+ return 0;
+}
+
+
+/* Read an entire line and return number of bytes read. */
+static int
+readline (int fd, char *buf, size_t buflen)
+{
+ size_t nleft = buflen;
+ char *p;
+ int nread = 0;
+
+ while (nleft > 0)
+ {
+ int n = read (fd, buf, nleft);
+ if (n < 0)
+ {
+ if (errno == EINTR)
+ continue;
+ return -(SPWQ_IO_ERROR);
+ }
+ else if (!n)
+ {
+ return -(SPWQ_PROTOCOL_ERROR); /* incomplete line */
+ }
+ p = buf;
+ nleft -= n;
+ buf += n;
+ nread += n;
+
+ for (; n && *p != '\n'; n--, p++)
+ ;
+ if (n)
+ {
+ break; /* at least one full line available - that's enough.
+ This function is just a simple implementation, so
+ it is okay to forget about pending bytes */
+ }
+ }
+
+ return nread;
+}
+
+
+/* Send an option to the agent */
+static int
+agent_send_option (int fd, const char *name, const char *value)
+{
+ char buf[200];
+ int nread;
+ char *line;
+ int i;
+
+ line = spwq_malloc (7 + strlen (name) + 1 + strlen (value) + 2);
+ if (!line)
+ return SPWQ_OUT_OF_CORE;
+ strcpy (stpcpy (stpcpy (stpcpy (
+ stpcpy (line, "OPTION "), name), "="), value), "\n");
+ i = writen (fd, line, strlen (line));
+ spwq_free (line);
+ if (i)
+ return i;
+
+ /* get response */
+ nread = readline (fd, buf, DIM(buf)-1);
+ if (nread < 0)
+ return -nread;
+ if (nread < 3)
+ return SPWQ_PROTOCOL_ERROR;
+
+ if (buf[0] == 'O' && buf[1] == 'K' && (buf[2] == ' ' || buf[2] == '\n'))
+ return 0; /* okay */
+
+ return SPWQ_ERR_RESPONSE;
+}
+
+
+/* Send all available options to the agent. */
+static int
+agent_send_all_options (int fd)
+{
+ char *dft_display = NULL;
+ char *dft_ttyname = NULL;
+ char *dft_ttytype = NULL;
+ int rc = 0;
+
+ dft_display = getenv ("DISPLAY");
+ if (dft_display)
+ {
+ if ((rc = agent_send_option (fd, "display", dft_display)))
+ return rc;
+ }
+
+ dft_ttyname = getenv ("GPG_TTY");
+ if ((!dft_ttyname || !*dft_ttyname) && ttyname (0))
+ dft_ttyname = ttyname (0);
+ if (dft_ttyname && *dft_ttyname)
+ {
+ if ((rc=agent_send_option (fd, "ttyname", dft_ttyname)))
+ return rc;
+ }
+
+ dft_ttytype = getenv ("TERM");
+ if (dft_ttyname && dft_ttytype)
+ {
+ if ((rc = agent_send_option (fd, "ttytype", dft_ttytype)))
+ return rc;
+ }
+
+#if defined(HAVE_SETLOCALE)
+ {
+ char *old_lc = NULL;
+ char *dft_lc = NULL;
+
+#if defined(LC_CTYPE)
+ old_lc = setlocale (LC_CTYPE, NULL);
+ if (old_lc)
+ {
+ char *p = spwq_malloc (strlen (old_lc)+1);
+ if (!p)
+ return SPWQ_OUT_OF_CORE;
+ strcpy (p, old_lc);
+ old_lc = p;
+ }
+ dft_lc = setlocale (LC_CTYPE, "");
+ if (dft_ttyname && dft_lc)
+ rc = agent_send_option (fd, "lc-ctype", dft_lc);
+ if (old_lc)
+ {
+ setlocale (LC_CTYPE, old_lc);
+ spwq_free (old_lc);
+ }
+ if (rc)
+ return rc;
+#endif
+
+#if defined(LC_MESSAGES)
+ old_lc = setlocale (LC_MESSAGES, NULL);
+ if (old_lc)
+ {
+ char *p = spwq_malloc (strlen (old_lc)+1);
+ if (!p)
+ return SPWQ_OUT_OF_CORE;
+ strcpy (p, old_lc);
+ old_lc = p;
+ }
+ dft_lc = setlocale (LC_MESSAGES, "");
+ if (dft_ttyname && dft_lc)
+ rc = agent_send_option (fd, "lc-messages", dft_lc);
+ if (old_lc)
+ {
+ setlocale (LC_MESSAGES, old_lc);
+ spwq_free (old_lc);
+ }
+ if (rc)
+ return rc;
+#endif
+ }
+#endif /*HAVE_SETLOCALE*/
+
+ return 0;
+}
+
+
+
+/* Try to open a connection to the agent, send all options and return
+ the file descriptor for the connection. Return -1 in case of
+ error. */
+static int
+agent_open (int *rfd)
+{
+ int rc;
+ int fd;
+ char *infostr, *p;
+ struct sockaddr_un client_addr;
+ size_t len;
+ int prot;
+ char line[200];
+ int nread;
+
+ *rfd = -1;
+ infostr = getenv ( "GPG_AGENT_INFO" );
+ if ( !infostr )
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error (_("gpg-agent is not available in this session\n"));
+#endif
+ return SPWQ_NO_AGENT;
+ }
+
+ if ( !(p = strchr ( infostr, ':')) || p == infostr
+ || (p-infostr)+1 >= sizeof client_addr.sun_path )
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error ( _("malformed GPG_AGENT_INFO environment variable\n"));
+#endif
+ return SPWQ_NO_AGENT;
+ }
+ *p++ = 0;
+
+ while (*p && *p != ':')
+ p++;
+ prot = *p? atoi (p+1) : 0;
+ if ( prot != 1)
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error (_("gpg-agent protocol version %d is not supported\n"),prot);
+#endif
+ return SPWQ_PROTOCOL_ERROR;
+ }
+
+ if( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 )
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error ("can't create socket: %s\n", strerror(errno) );
+#endif
+ return SPWQ_SYS_ERROR;
+ }
+
+ memset (&client_addr, 0, sizeof client_addr);
+ client_addr.sun_family = AF_UNIX;
+ strcpy (client_addr.sun_path, infostr);
+ len = (offsetof (struct sockaddr_un, sun_path)
+ + strlen(client_addr.sun_path) + 1);
+
+ if (connect (fd, (struct sockaddr*)&client_addr, len ) == -1)
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error ( _("can't connect to `%s': %s\n"), infostr, strerror (errno));
+#endif
+ close (fd );
+ return SPWQ_IO_ERROR;
+ }
+
+ nread = readline (fd, line, DIM(line));
+ if (nread < 3 || !(line[0] == 'O' && line[1] == 'K'
+ && (line[2] == '\n' || line[2] == ' ')) )
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error ( _("communication problem with gpg-agent\n"));
+#endif
+ close (fd );
+ return SPWQ_PROTOCOL_ERROR;
+ }
+
+ rc = agent_send_all_options (fd);
+ if (rc)
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error (_("problem setting the gpg-agent options\n"));
+#endif
+ close (fd);
+ return rc;
+ }
+
+ *rfd = fd;
+ return 0;
+}
+
+
+/* Copy text to BUFFER and escape as required. Return a poiinter to
+ the end of the new buffer. NOte that BUFFER must be large enough
+ to keep the entire text; allocataing it 3 times the size of TEXT
+ is sufficient. */
+static char *
+copy_and_escape (char *buffer, const char *text)
+{
+ int i;
+ char *p = buffer;
+
+ for (i=0; text[i]; i++)
+ {
+ if (text[i] < ' ' || text[i] == '+')
+ {
+ sprintf (p, "%%%02X", text[i]);
+ p += 3;
+ }
+ else if (text[i] == ' ')
+ *p++ = '+';
+ else
+ *p++ = text[i];
+ }
+ return p;
+}
+
+
+/* Ask the gpg-agent for a passphrase and present the user with a
+ DESCRIPTION, a PROMPT and optiaonlly with a TRYAGAIN extra text.
+ If a CACHEID is not NULL it is used to locate the passphrase in in
+ the cache and store it under this ID. If ERRORCODE is not NULL it
+ should point a variable receiving an errorcode; thsi errocode might
+ be 0 if the user canceled the operation. The function returns NULL
+ to indicate an error. */
+char *
+simple_pwquery (const char *cacheid,
+ const char *tryagain,
+ const char *prompt,
+ const char *description,
+ int *errorcode)
+{
+ int fd = -1;
+ int nread;
+ char *result = NULL;
+ char *pw = NULL;
+ char *p;
+ int rc, i;
+
+ rc = agent_open (&fd);
+ if (rc)
+ goto leave;
+
+ if (!cacheid)
+ cacheid = "X";
+ if (!tryagain)
+ tryagain = "X";
+ if (!prompt)
+ prompt = "X";
+ if (!description)
+ description = "X";
+
+ {
+ char *line;
+ /* We allocate 3 times the needed space so that there is enough
+ space for escaping. */
+ line = spwq_malloc (15
+ + 3*strlen (cacheid) + 1
+ + 3*strlen (tryagain) + 1
+ + 3*strlen (prompt) + 1
+ + 3*strlen (description) + 1
+ + 2);
+ if (!line)
+ {
+ rc = SPWQ_OUT_OF_CORE;
+ goto leave;
+ }
+ strcpy (line, "GET_PASSPHRASE ");
+ p = line+15;
+ p = copy_and_escape (p, cacheid);
+ *p++ = ' ';
+ p = copy_and_escape (p, tryagain);
+ *p++ = ' ';
+ p = copy_and_escape (p, prompt);
+ *p++ = ' ';
+ p = copy_and_escape (p, description);
+ *p++ = '\n';
+ rc = writen (fd, line, p - line);
+ spwq_free (line);
+ if (rc)
+ goto leave;
+ }
+
+ /* get response */
+ pw = spwq_secure_malloc (500);
+ nread = readline (fd, pw, 499);
+ if (nread < 0)
+ {
+ rc = -nread;
+ goto leave;
+ }
+ if (nread < 3)
+ {
+ rc = SPWQ_PROTOCOL_ERROR;
+ goto leave;
+ }
+
+ if (pw[0] == 'O' && pw[1] == 'K' && pw[2] == ' ')
+ { /* we got a passphrase - convert it back from hex */
+ size_t pwlen = 0;
+
+ for (i=3; i < nread && hexdigitp (pw+i); i+=2)
+ pw[pwlen++] = xtoi_2 (pw+i);
+ pw[pwlen] = 0; /* make a C String */
+ result = pw;
+ pw = NULL;
+ }
+ else if (nread > 7 && !memcmp (pw, "ERR 111", 7)
+ && (pw[7] == ' ' || pw[7] == '\n') )
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_info (_("canceled by user\n") );
+#endif
+ *errorcode = 0; /* canceled */
+ }
+ else
+ {
+#ifdef SPWQ_USE_LOGGING
+ log_error (_("problem with the agent\n"));
+#endif
+ rc = SPWQ_ERR_RESPONSE;
+ }
+
+ leave:
+ if (errorcode)
+ *errorcode = rc;
+ if (fd != -1)
+ close (fd);
+ if (pw)
+ spwq_free (pw);
+ return result;
+}
diff --git a/common/simple-pwquery.h b/common/simple-pwquery.h
new file mode 100644
index 000000000..5947c42b5
--- /dev/null
+++ b/common/simple-pwquery.h
@@ -0,0 +1,69 @@
+/* simple-pwquery.c - A simple password query cleint for gpg-agent
+ * Copyright (C) 2002 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef SIMPLE_PWQUERY_H
+#define SIMPLE_PWQUERY_H
+
+#ifdef SIMPLE_PWQUERY_IMPLEMENTATION /* Begin configuration stuff. */
+
+/* Include whatever files you need. */
+#include <gcrypt.h>
+#include "../jnlib/logging.h"
+
+/* Try to write error message using the standard log mechanism. The
+ current implementation requires that the HAVE_JNLIB_LOGGING is also
+ defined. */
+#define SPWQ_USE_LOGGING 1
+
+/* Memory allocation functions used by the implementation. Note, that
+ the returned value is expected to be freed with
+ spwq_secure_free. */
+#define spwq_malloc(a) gcry_malloc (a)
+#define spwq_free(a) gcry_free (a)
+#define spwq_secure_malloc(a) gcry_malloc_secure (a)
+#define spwq_secure_free(a) gcry_free (a)
+
+
+#endif /*SIMPLE_PWQUERY_IMPLEMENTATION*/ /* End configuration stuff. */
+
+
+/* Ask the gpg-agent for a passphrase and present the user with a
+ DESCRIPTION, a PROMPT and optiaonlly with a TRYAGAIN extra text.
+ If a CACHEID is not NULL it is used to locate the passphrase in in
+ the cache and store it under this ID. If ERRORCODE is not NULL it
+ should point a variable receiving an errorcode; this errocode might
+ be 0 if the user canceled the operation. The function returns NULL
+ to indicate an error. */
+char *simple_pwquery (const char *cacheid,
+ const char *tryagain,
+ const char *prompt,
+ const char *description,
+ int *errorcode);
+
+
+#define SPWQ_OUT_OF_CORE 1
+#define SPWQ_IO_ERROR 2
+#define SPWQ_PROTOCOL_ERROR 3
+#define SPWQ_ERR_RESPONSE 4
+#define SPWQ_NO_AGENT 5
+#define SPWQ_SYS_ERROR 6
+#define SPWQ_GENERAL_ERROR 7
+
+#endif /*SIMPLE_PWQUERY_H*/
diff --git a/common/ttyio.c b/common/ttyio.c
new file mode 100644
index 000000000..fd748009e
--- /dev/null
+++ b/common/ttyio.c
@@ -0,0 +1,508 @@
+/* ttyio.c - tty i/O functions
+ * Copyright (C) 1998,1999,2000,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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#ifdef HAVE_TCGETATTR
+#include <termios.h>
+#else
+#ifdef HAVE_TERMIO_H
+/* simulate termios with termio */
+#include <termio.h>
+#define termios termio
+#define tcsetattr ioctl
+#define TCSAFLUSH TCSETAF
+#define tcgetattr(A,B) ioctl(A,TCGETA,B)
+#define HAVE_TCGETATTR
+#endif
+#endif
+#ifdef __MINGW32__ /* use the odd Win32 functions */
+#include <windows.h>
+#ifdef HAVE_TCGETATTR
+#error mingw32 and termios
+#endif
+#endif
+#include <errno.h>
+#include <ctype.h>
+#include "util.h"
+#include "memory.h"
+#include "ttyio.h"
+
+#define CONTROL_D ('D' - 'A' + 1)
+
+#ifdef __MINGW32__ /* use the odd Win32 functions */
+static struct {
+ HANDLE in, out;
+} con;
+#define DEF_INPMODE (ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT \
+ |ENABLE_PROCESSED_INPUT )
+#define HID_INPMODE (ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT )
+#define DEF_OUTMODE (ENABLE_WRAP_AT_EOL_OUTPUT|ENABLE_PROCESSED_OUTPUT)
+
+#else /* yeah, we have a real OS */
+static FILE *ttyfp = NULL;
+#endif
+
+static int initialized;
+static int last_prompt_len;
+static int batchmode;
+static int no_terminal;
+
+#ifdef HAVE_TCGETATTR
+ static struct termios termsave;
+ static int restore_termios;
+#endif
+
+
+
+/* This is a wrapper around ttyname so that we can use it even when
+ the standard streams are redirected. It figures the name out the
+ first time and returns it in a statically allocated buffer. */
+const char *
+tty_get_ttyname (void)
+{
+ static char *name;
+
+ /* On a GNU system ctermid() always return /dev/tty, so this does
+ not make much sense - however if it is ever changed we do the
+ Right Thing now. */
+#ifdef HAVE_CTERMID
+ static int got_name;
+
+ if (!got_name)
+ {
+ const char *s;
+ s = ctermid (NULL);
+ if (s)
+ name = strdup (s);
+ got_name = 1;
+ }
+#endif
+ /* Assume the staandrd tty on memory error or when tehre is no
+ certmid. */
+ return name? name : "/dev/tty";
+}
+
+
+
+#ifdef HAVE_TCGETATTR
+static void
+cleanup(void)
+{
+ if( restore_termios ) {
+ restore_termios = 0; /* do it prios in case it is interrupted again */
+ if( tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave) )
+ log_error("tcsetattr() failed: %s\n", strerror(errno) );
+ }
+}
+#endif
+
+static void
+init_ttyfp(void)
+{
+ if( initialized )
+ return;
+
+#if defined(__MINGW32__)
+ {
+ SECURITY_ATTRIBUTES sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.nLength = sizeof(sa);
+ sa.bInheritHandle = TRUE;
+ con.out = CreateFileA( "CONOUT$", GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ &sa, OPEN_EXISTING, 0, 0 );
+ if( con.out == INVALID_HANDLE_VALUE )
+ log_fatal("open(CONOUT$) failed: rc=%d", (int)GetLastError() );
+ memset(&sa, 0, sizeof(sa));
+ sa.nLength = sizeof(sa);
+ sa.bInheritHandle = TRUE;
+ con.in = CreateFileA( "CONIN$", GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ &sa, OPEN_EXISTING, 0, 0 );
+ if( con.in == INVALID_HANDLE_VALUE )
+ log_fatal("open(CONIN$) failed: rc=%d", (int)GetLastError() );
+ }
+ SetConsoleMode(con.in, DEF_INPMODE );
+ SetConsoleMode(con.out, DEF_OUTMODE );
+
+#elif defined(__EMX__)
+ ttyfp = stdout; /* Fixme: replace by the real functions: see wklib */
+#else
+ ttyfp = batchmode? stderr : fopen (tty_get_ttyname (), "r+");
+ if( !ttyfp ) {
+ log_error("cannot open `%s': %s\n", tty_get_ttyname (),
+ strerror(errno) );
+ exit(2);
+ }
+#endif
+#ifdef HAVE_TCGETATTR
+ atexit( cleanup );
+#endif
+ initialized = 1;
+}
+
+
+int
+tty_batchmode( int onoff )
+{
+ int old = batchmode;
+ if( onoff != -1 )
+ batchmode = onoff;
+ return old;
+}
+
+int
+tty_no_terminal(int onoff)
+{
+ int old = no_terminal;
+ no_terminal = onoff ? 1 : 0;
+ return old;
+}
+
+void
+tty_printf( const char *fmt, ... )
+{
+ va_list arg_ptr;
+
+ if (no_terminal)
+ return;
+
+ if( !initialized )
+ init_ttyfp();
+
+ va_start( arg_ptr, fmt ) ;
+#ifdef __MINGW32__
+ {
+ char *buf = NULL;
+ int n;
+ DWORD nwritten;
+
+ n = vasprintf(&buf, fmt, arg_ptr);
+ if( !buf )
+ log_bug("vasprintf() failed\n");
+
+ if( !WriteConsoleA( con.out, buf, n, &nwritten, NULL ) )
+ log_fatal("WriteConsole failed: rc=%d", (int)GetLastError() );
+ if( n != nwritten )
+ log_fatal("WriteConsole failed: %d != %d\n", n, (int)nwritten );
+ last_prompt_len += n;
+ xfree (buf);
+ }
+#else
+ last_prompt_len += vfprintf(ttyfp,fmt,arg_ptr) ;
+ fflush(ttyfp);
+#endif
+ va_end(arg_ptr);
+}
+
+
+/****************
+ * Print a string, but filter all control characters out.
+ */
+void
+tty_print_string( byte *p, size_t n )
+{
+ if (no_terminal)
+ return;
+
+ if( !initialized )
+ init_ttyfp();
+
+#ifdef __MINGW32__
+ /* not so effective, change it if you want */
+ for( ; n; n--, p++ )
+ if( iscntrl( *p ) ) {
+ if( *p == '\n' )
+ tty_printf("\\n");
+ else if( !*p )
+ tty_printf("\\0");
+ else
+ tty_printf("\\x%02x", *p);
+ }
+ else
+ tty_printf("%c", *p);
+#else
+ for( ; n; n--, p++ )
+ if( iscntrl( *p ) ) {
+ putc('\\', ttyfp);
+ if( *p == '\n' )
+ putc('n', ttyfp);
+ else if( !*p )
+ putc('0', ttyfp);
+ else
+ fprintf(ttyfp, "x%02x", *p );
+ }
+ else
+ putc(*p, ttyfp);
+#endif
+}
+
+void
+tty_print_utf8_string2( byte *p, size_t n, size_t max_n )
+{
+ size_t i;
+ char *buf;
+
+ if (no_terminal)
+ return;
+
+ /* we can handle plain ascii simpler, so check for it first */
+ for(i=0; i < n; i++ ) {
+ if( p[i] & 0x80 )
+ break;
+ }
+ if( i < n ) {
+ buf = utf8_to_native( p, n, 0 );
+ if( max_n && (strlen( buf ) > max_n )) {
+ buf[max_n] = 0;
+ }
+ /*(utf8 conversion already does the control character quoting)*/
+ tty_printf("%s", buf );
+ xfree( buf );
+ }
+ else {
+ if( max_n && (n > max_n) ) {
+ n = max_n;
+ }
+ tty_print_string( p, n );
+ }
+}
+
+void
+tty_print_utf8_string( byte *p, size_t n )
+{
+ tty_print_utf8_string2( p, n, 0 );
+}
+
+
+static char *
+do_get( const char *prompt, int hidden )
+{
+ char *buf;
+#ifndef __riscos__
+ byte cbuf[1];
+#endif
+ int c, n, i;
+
+ if( batchmode ) {
+ log_error("Sorry, we are in batchmode - can't get input\n");
+ exit(2);
+ }
+
+ if (no_terminal) {
+ log_error("Sorry, no terminal at all requested - can't get input\n");
+ exit(2);
+ }
+
+ if( !initialized )
+ init_ttyfp();
+
+ last_prompt_len = 0;
+ tty_printf( "%s", prompt );
+ buf = xmalloc((n=50));
+ i = 0;
+
+#ifdef __MINGW32__ /* windoze version */
+ if( hidden )
+ SetConsoleMode(con.in, HID_INPMODE );
+
+ for(;;) {
+ DWORD nread;
+
+ if( !ReadConsoleA( con.in, cbuf, 1, &nread, NULL ) )
+ log_fatal("ReadConsole failed: rc=%d", (int)GetLastError() );
+ if( !nread )
+ continue;
+ if( *cbuf == '\n' )
+ break;
+
+ if( !hidden )
+ last_prompt_len++;
+ c = *cbuf;
+ if( c == '\t' )
+ c = ' ';
+ else if( c > 0xa0 )
+ ; /* we don't allow 0xa0, as this is a protected blank which may
+ * confuse the user */
+ else if( iscntrl(c) )
+ continue;
+ if( !(i < n-1) ) {
+ n += 50;
+ buf = xrealloc (buf, n);
+ }
+ buf[i++] = c;
+ }
+
+ if( hidden )
+ SetConsoleMode(con.in, DEF_INPMODE );
+
+#elif defined(__riscos__)
+ do {
+ c = riscos_getchar();
+ if (c == 0xa || c == 0xd) { /* Return || Enter */
+ c = (int) '\n';
+ } else if (c == 0x8 || c == 0x7f) { /* Backspace || Delete */
+ if (i>0) {
+ i--;
+ if (!hidden) {
+ last_prompt_len--;
+ fputc(8, ttyfp);
+ fputc(32, ttyfp);
+ fputc(8, ttyfp);
+ fflush(ttyfp);
+ }
+ } else {
+ fputc(7, ttyfp);
+ fflush(ttyfp);
+ }
+ continue;
+ } else if (c == (int) '\t') { /* Tab */
+ c = ' ';
+ } else if (c > 0xa0) {
+ ; /* we don't allow 0xa0, as this is a protected blank which may
+ * confuse the user */
+ } else if (iscntrl(c)) {
+ continue;
+ }
+ if(!(i < n-1)) {
+ n += 50;
+ buf = xrealloc (buf, n);
+ }
+ buf[i++] = c;
+ if (!hidden) {
+ last_prompt_len++;
+ fputc(c, ttyfp);
+ fflush(ttyfp);
+ }
+ } while (c != '\n');
+ i = (i>0) ? i-1 : 0;
+#else /* unix version */
+ if( hidden ) {
+#ifdef HAVE_TCGETATTR
+ struct termios term;
+
+ if( tcgetattr(fileno(ttyfp), &termsave) )
+ log_fatal("tcgetattr() failed: %s\n", strerror(errno) );
+ restore_termios = 1;
+ term = termsave;
+ term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
+ if( tcsetattr( fileno(ttyfp), TCSAFLUSH, &term ) )
+ log_fatal("tcsetattr() failed: %s\n", strerror(errno) );
+#endif
+ }
+
+ /* fixme: How can we avoid that the \n is echoed w/o disabling
+ * canonical mode - w/o this kill_prompt can't work */
+ while( read(fileno(ttyfp), cbuf, 1) == 1 && *cbuf != '\n' ) {
+ if( !hidden )
+ last_prompt_len++;
+ c = *cbuf;
+ if( c == CONTROL_D )
+ log_info("control d found\n");
+ if( c == '\t' )
+ c = ' ';
+ else if( c > 0xa0 )
+ ; /* we don't allow 0xa0, as this is a protected blank which may
+ * confuse the user */
+ else if( iscntrl(c) )
+ continue;
+ if( !(i < n-1) ) {
+ n += 50;
+ buf = xrealloc (buf, n );
+ }
+ buf[i++] = c;
+ }
+ if( *cbuf != '\n' ) {
+ buf[0] = CONTROL_D;
+ i = 1;
+ }
+
+
+ if( hidden ) {
+#ifdef HAVE_TCGETATTR
+ if( tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave) )
+ log_error("tcsetattr() failed: %s\n", strerror(errno) );
+ restore_termios = 0;
+#endif
+ }
+#endif /* end unix version */
+ buf[i] = 0;
+ return buf;
+}
+
+
+char *
+tty_get( const char *prompt )
+{
+ return do_get( prompt, 0 );
+}
+
+char *
+tty_get_hidden( const char *prompt )
+{
+ return do_get( prompt, 1 );
+}
+
+
+void
+tty_kill_prompt()
+{
+ if ( no_terminal )
+ return;
+
+ if( !initialized )
+ init_ttyfp();
+
+ if( batchmode )
+ last_prompt_len = 0;
+ if( !last_prompt_len )
+ return;
+#ifdef __MINGW32__
+ tty_printf("\r%*s\r", last_prompt_len, "");
+#else
+ {
+ int i;
+ putc('\r', ttyfp);
+ for(i=0; i < last_prompt_len; i ++ )
+ putc(' ', ttyfp);
+ putc('\r', ttyfp);
+ fflush(ttyfp);
+ }
+#endif
+ last_prompt_len = 0;
+}
+
+
+int
+tty_get_answer_is_yes( const char *prompt )
+{
+ int yes;
+ char *p = tty_get( prompt );
+ tty_kill_prompt();
+ yes = answer_is_yes(p);
+ xfree(p);
+ return yes;
+}
diff --git a/common/ttyio.h b/common/ttyio.h
new file mode 100644
index 000000000..b3ca7dcaf
--- /dev/null
+++ b/common/ttyio.h
@@ -0,0 +1,40 @@
+/* ttyio.h
+ * Copyright (C) 1998, 1999, 2000, 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GNUPG.
+ *
+ * GNUPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+#ifndef GNUPG_COMMON_TTYIO_H
+#define GNUPG_COMMON_TTYIO_H
+
+const char *tty_get_ttyname (void);
+int tty_batchmode (int onoff);
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )
+void tty_printf (const char *fmt, ... ) __attribute__ ((format (printf,1,2)));
+#else
+void tty_printf (const char *fmt, ... );
+#endif
+void tty_print_string (unsigned char *p, size_t n);
+void tty_print_utf8_string (unsigned char *p, size_t n);
+void tty_print_utf8_string2 (unsigned char *p, size_t n, size_t max_n);
+char *tty_get (const char *prompt);
+char *tty_get_hidden (const char *prompt);
+void tty_kill_prompt (void);
+int tty_get_answer_is_yes (const char *prompt);
+int tty_no_terminal (int onoff);
+
+
+#endif /*GNUPG_COMMON_TTYIO_H*/
diff --git a/common/util.h b/common/util.h
new file mode 100644
index 000000000..045851481
--- /dev/null
+++ b/common/util.h
@@ -0,0 +1,120 @@
+/* util.h - Utility functions for Gnupg
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef GNUPG_COMMON_UTIL_H
+#define GNUPG_COMMON_UTIL_H
+
+#include <gcrypt.h> /* We need this for the memory function protos. */
+#include <time.h> /* We need time_t. */
+#include <gpg-error.h> /* we need gpg-error_t. */
+
+/* to pass hash functions to libksba we need to cast it */
+#define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write)
+
+/* get all the stuff from jnlib */
+#include "../jnlib/logging.h"
+#include "../jnlib/argparse.h"
+#include "../jnlib/stringhelp.h"
+#include "../jnlib/mischelp.h"
+#include "../jnlib/strlist.h"
+#include "../jnlib/dotlock.h"
+#include "../jnlib/utf8conv.h"
+
+/* handy malloc macros - use only them */
+#define xtrymalloc(a) gcry_malloc ((a))
+#define xtrycalloc(a,b) gcry_calloc ((a),(b))
+#define xtryrealloc(a,b) gcry_realloc ((a),(b))
+#define xtrystrdup(a) gcry_strdup ((a))
+#define xfree(a) gcry_free ((a))
+
+#define xmalloc(a) gcry_xmalloc ((a))
+#define xmalloc_secure(a) gcry_xmalloc_secure ((a))
+#define xcalloc(a,b) gcry_xcalloc ((a),(b))
+#define xcalloc_secure(a,b) gcry_xcalloc_secure ((a),(b))
+#define xrealloc(a,b) gcry_xrealloc ((a),(b))
+#define xstrdup(a) gcry_xstrdup ((a))
+
+/*-- maperror.c --*/
+int map_ksba_err (int err);
+int map_gcry_err (int err);
+int map_kbx_err (int err);
+gpg_error_t map_assuan_err (int err);
+int map_to_assuan_status (int rc);
+
+/*-- gettime.c --*/
+time_t gnupg_get_time (void);
+void gnupg_set_time (time_t newtime, int freeze);
+int gnupg_faked_time_p (void);
+u32 make_timestamp (void);
+u32 scan_isodatestr (const char *string);
+u32 add_days_to_timestamp (u32 stamp, u16 days);
+const char *strtimevalue (u32 stamp);
+const char *strtimestamp (u32 stamp); /* GMT */
+const char *asctimestamp (u32 stamp); /* localized */
+
+/*-- signal.c --*/
+void gnupg_init_signals (int mode, void (*fast_cleanup)(void));
+void gnupg_pause_on_sigusr (int which);
+void gnupg_block_all_signals (void);
+void gnupg_unblock_all_signals (void);
+
+/*-- yesno.c --*/
+int answer_is_yes (const char *s);
+int answer_is_yes_no_default (const char *s, int def_answer);
+int answer_is_yes_no_quit (const char *s);
+
+
+/*-- miscellaneous.c --*/
+const char *print_fname_stdout (const char *s);
+const char *print_fname_stdin (const char *s);
+void print_string (FILE *fp, const byte *p, size_t n, int delim);
+void print_utf8_string2 ( FILE *fp, const byte *p, size_t n, int delim);
+void print_utf8_string (FILE *fp, const byte *p, size_t n);
+char *make_printable_string (const byte *p, size_t n, int delim);
+
+int is_file_compressed (const char *s, int *ret_rc);
+
+
+/*-- replacement functions from funcname.c --*/
+#if !HAVE_VASPRINTF
+#include <stdarg.h>
+int vasprintf (char **result, const char *format, va_list *args);
+int asprintf (char **result, const char *format, ...);
+#endif
+
+
+
+/*-- some macros to replace ctype ones and avoid locale problems --*/
+#define spacep(p) (*(p) == ' ' || *(p) == '\t')
+#define digitp(p) (*(p) >= '0' && *(p) <= '9')
+#define hexdigitp(a) (digitp (a) \
+ || (*(a) >= 'A' && *(a) <= 'F') \
+ || (*(a) >= 'a' && *(a) <= 'f'))
+/* the atoi macros assume that the buffer has only valid digits */
+#define atoi_1(p) (*(p) - '0' )
+#define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1))
+#define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2))
+#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
+ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
+#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
+
+
+
+#endif /*GNUPG_COMMON_UTIL_H*/
diff --git a/common/yesno.c b/common/yesno.c
new file mode 100644
index 000000000..2a96b4e5d
--- /dev/null
+++ b/common/yesno.c
@@ -0,0 +1,96 @@
+/* yesno.c - Yes/No questions
+ * Copyright (C) 1998, 1999, 2000, 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "i18n.h"
+#include "util.h"
+
+int
+answer_is_yes_no_default( const char *s, int def_answer )
+{
+ const char *long_yes = _("yes");
+ const char *short_yes = _("yY");
+ const char *long_no = _("no");
+ const char *short_no = _("nN");
+
+ /* Note: we have to use the local dependent strcasecmp here */
+ if( !strcasecmp(s, long_yes ) )
+ return 1;
+ if( *s && strchr( short_yes, *s ) && !s[1] )
+ return 1;
+ /* test for no strings to catch ambiguities for the next test */
+ if( !strcasecmp(s, long_no ) )
+ return 0;
+ if( *s && strchr( short_no, *s ) && !s[1] )
+ return 0;
+ /* test for the english version (for those who are used to type yes) */
+ if( !ascii_strcasecmp(s, "yes" ) )
+ return 1;
+ if( *s && strchr( "yY", *s ) && !s[1] )
+ return 1;
+ return def_answer;
+}
+
+int
+answer_is_yes( const char *s )
+{
+ return answer_is_yes_no_default(s,0);
+}
+
+/****************
+ * Return 1 for yes, -1 for quit, or 0 for no
+ */
+int
+answer_is_yes_no_quit( const char *s )
+{
+ const char *long_yes = _("yes");
+ const char *long_no = _("no");
+ const char *long_quit = _("quit");
+ const char *short_yes = _("yY");
+ const char *short_no = _("nN");
+ const char *short_quit = _("qQ");
+
+ /* Note: We have to use the locale dependent strcasecmp */
+ if( !strcasecmp(s, long_no ) )
+ return 0;
+ if( !strcasecmp(s, long_yes ) )
+ return 1;
+ if( !strcasecmp(s, long_quit ) )
+ return -1;
+ if( *s && strchr( short_no, *s ) && !s[1] )
+ return 0;
+ if( *s && strchr( short_yes, *s ) && !s[1] )
+ return 1;
+ if( *s && strchr( short_quit, *s ) && !s[1] )
+ return -1;
+ /* but not here */
+ if( !ascii_strcasecmp(s, "yes" ) )
+ return 1;
+ if( !ascii_strcasecmp(s, "quit" ) )
+ return -1;
+ if( *s && strchr( "yY", *s ) && !s[1] )
+ return 1;
+ if( *s && strchr( "qQ", *s ) && !s[1] )
+ return -1;
+ return 0;
+}
diff --git a/kbx/Makefile.am b/kbx/Makefile.am
new file mode 100644
index 000000000..4f0c40043
--- /dev/null
+++ b/kbx/Makefile.am
@@ -0,0 +1,52 @@
+# Keybox Makefile
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+## Process this file with automake to produce Makefile.in
+
+localedir = $(datadir)/locale
+INCLUDES = -I../intl -DLOCALEDIR=\"$(localedir)\"
+
+EXTRA_DIST = mkerrors
+AM_CPPFLAGS = -I$(top_srcdir)/common -I$(top_srcdir)/intl \
+ $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS)
+
+noinst_LIBRARIES = libkeybox.a
+bin_PROGRAMS = kbxutil
+
+common_sources = \
+ keybox.h keybox-defs.h keybox-search-desc.h \
+ keybox-util.c \
+ keybox-init.c \
+ keybox-blob.c \
+ keybox-file.c \
+ keybox-search.c \
+ keybox-update.c \
+ keybox-dump.c
+
+
+libkeybox_a_SOURCES = $(common_sources)
+
+kbxutil_SOURCES = kbxutil.c $(common_sources)
+kbxutil_LDADD = ../jnlib/libjnlib.a $(KSBA_LIBS) $(LIBGCRYPT_LIBS) \
+ -lgpg-error @INTLLIBS@
+
+
+
+
+
diff --git a/kbx/kbxutil.c b/kbx/kbxutil.c
new file mode 100644
index 000000000..abca4faa9
--- /dev/null
+++ b/kbx/kbxutil.c
@@ -0,0 +1,339 @@
+/* kbxutil.c - The Keybox utility
+ * Copyright (C) 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#include "../jnlib/logging.h"
+#include "../jnlib/argparse.h"
+#include "../jnlib/stringhelp.h"
+#include "../common/i18n.h"
+#include "keybox-defs.h"
+
+#include <gcrypt.h>
+
+
+enum cmd_and_opt_values {
+ aNull = 0,
+ oArmor = 'a',
+ oDryRun = 'n',
+ oOutput = 'o',
+ oQuiet = 'q',
+ oVerbose = 'v',
+
+ aNoSuchCmd = 500, /* force other values not to be a letter */
+ aFindByFpr,
+ aFindByKid,
+ aFindByUid,
+
+ oDebug,
+ oDebugAll,
+
+ oNoArmor,
+
+
+ aTest
+};
+
+
+static ARGPARSE_OPTS opts[] = {
+ { 300, NULL, 0, N_("@Commands:\n ") },
+
+ { aFindByFpr, "find-by-fpr", 0, "|FPR| find key using it's fingerprnt" },
+ { aFindByKid, "find-by-kid", 0, "|KID| find key using it's keyid" },
+ { aFindByUid, "find-by-uid", 0, "|NAME| find key by user name" },
+
+ { 301, NULL, 0, N_("@\nOptions:\n ") },
+
+ { oArmor, "armor", 0, N_("create ascii armored output")},
+ { oArmor, "armour", 0, "@" },
+ { oOutput, "output", 2, N_("use as output file")},
+ { oVerbose, "verbose", 0, N_("verbose") },
+ { oQuiet, "quiet", 0, N_("be somewhat more quiet") },
+ { oDryRun, "dry-run", 0, N_("do not make any changes") },
+
+ { oDebug, "debug" ,4|16, N_("set debugging flags")},
+ { oDebugAll, "debug-all" ,0, N_("enable full debugging")},
+
+ {0} /* end of list */
+};
+
+
+void myexit (int rc);
+
+int keybox_errors_seen = 0;
+
+
+static const char *
+my_strusage( int level )
+{
+ const char *p;
+ switch( level ) {
+ case 11: p = "kbxutil (GnuPG)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p =
+ _("Please report bugs to " PACKAGE_BUGREPORT ".\n");
+ break;
+ case 1:
+ case 40: p =
+ _("Usage: kbxutil [options] [files] (-h for help)");
+ break;
+ case 41: p =
+ _("Syntax: kbxutil [options] [files]\n"
+ "list, export, import Keybox data\n");
+ break;
+
+
+ default: p = NULL;
+ }
+ return p;
+}
+
+
+static void
+i18n_init(void)
+{
+ #ifdef USE_SIMPLE_GETTEXT
+ set_gettext_file( PACKAGE );
+ #else
+ #ifdef ENABLE_NLS
+ #ifdef HAVE_LC_MESSAGES
+ setlocale( LC_TIME, "" );
+ setlocale( LC_MESSAGES, "" );
+ #else
+ setlocale( LC_ALL, "" );
+ #endif
+ bindtextdomain( PACKAGE, LOCALEDIR );
+ textdomain( PACKAGE );
+ #endif
+ #endif
+}
+
+
+/* static void */
+/* wrong_args( const char *text ) */
+/* { */
+/* log_error("usage: kbxutil %s\n", text); */
+/* myexit ( 1 ); */
+/* } */
+
+
+#if 0
+static int
+hextobyte( const byte *s )
+{
+ int c;
+
+ if( *s >= '0' && *s <= '9' )
+ c = 16 * (*s - '0');
+ else if( *s >= 'A' && *s <= 'F' )
+ c = 16 * (10 + *s - 'A');
+ else if( *s >= 'a' && *s <= 'f' )
+ c = 16 * (10 + *s - 'a');
+ else
+ return -1;
+ s++;
+ if( *s >= '0' && *s <= '9' )
+ c += *s - '0';
+ else if( *s >= 'A' && *s <= 'F' )
+ c += 10 + *s - 'A';
+ else if( *s >= 'a' && *s <= 'f' )
+ c += 10 + *s - 'a';
+ else
+ return -1;
+ return c;
+}
+#endif
+
+#if 0
+static char *
+format_fingerprint ( const char *s )
+{
+ int i, c;
+ byte fpr[20];
+
+ for (i=0; i < 20 && *s; ) {
+ if ( *s == ' ' || *s == '\t' ) {
+ s++;
+ continue;
+ }
+ c = hextobyte(s);
+ if (c == -1) {
+ return NULL;
+ }
+ fpr[i++] = c;
+ s += 2;
+ }
+ return gcry_xstrdup ( fpr );
+}
+#endif
+
+#if 0
+static int
+format_keyid ( const char *s, u32 *kid )
+{
+ char helpbuf[9];
+ switch ( strlen ( s ) ) {
+ case 8:
+ kid[0] = 0;
+ kid[1] = strtoul( s, NULL, 16 );
+ return 10;
+
+ case 16:
+ mem2str( helpbuf, s, 9 );
+ kid[0] = strtoul( helpbuf, NULL, 16 );
+ kid[1] = strtoul( s+8, NULL, 16 );
+ return 11;
+ }
+ return 0; /* error */
+}
+#endif
+
+
+int
+main( int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ enum cmd_and_opt_values cmd = 0;
+
+ set_strusage( my_strusage );
+ /*log_set_name("kbxutil"); fixme */
+#if 0
+ /* check that the libraries are suitable. Do it here because
+ * the option parse may need services of the library */
+ if ( !gcry_check_version ( "1.1.4" ) )
+ {
+ log_fatal(_("libgcrypt is too old (need %s, have %s)\n"),
+ "1.1.4", gcry_check_version(NULL) );
+ }
+#endif
+
+ /*create_dotlock(NULL); register locking cleanup */
+ i18n_init();
+
+ /* We need to use the gcry malloc function because jnlib does use them */
+ keybox_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
+ ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free );
+
+
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1; /* do not remove the args */
+ while (arg_parse( &pargs, opts) )
+ {
+ switch (pargs.r_opt)
+ {
+ case oVerbose:
+ /*opt.verbose++;*/
+ /*gcry_control( GCRYCTL_SET_VERBOSITY, (int)opt.verbose );*/
+ break;
+ case oDebug:
+ /*opt.debug |= pargs.r.ret_ulong; */
+ break;
+ case oDebugAll:
+ /*opt.debug = ~0;*/
+ break;
+
+ case aFindByFpr:
+ case aFindByKid:
+ case aFindByUid:
+ cmd = pargs.r_opt;
+ break;
+
+ default:
+ pargs.err = 2;
+ break;
+ }
+ }
+ if (log_get_errorcount(0) )
+ myexit(2);
+
+ if (!cmd)
+ { /* default is to list a KBX file */
+ if (!argc)
+ _keybox_dump_file (NULL, stdout);
+ else
+ {
+ for (; argc; argc--, argv++)
+ _keybox_dump_file (*argv, stdout);
+ }
+ }
+#if 0
+ else if ( cmd == aFindByFpr ) {
+ char *fpr;
+ if ( argc != 2 )
+ wrong_args ("kbxfile foingerprint");
+ fpr = format_fingerprint ( argv[1] );
+ if ( !fpr )
+ log_error ("invalid formatted fingerprint\n");
+ else {
+ kbxfile_search_by_fpr ( argv[0], fpr );
+ gcry_free ( fpr );
+ }
+ }
+ else if ( cmd == aFindByKid ) {
+ u32 kid[2];
+ int mode;
+
+ if ( argc != 2 )
+ wrong_args ("kbxfile short-or-long-keyid");
+ mode = format_keyid ( argv[1], kid );
+ if ( !mode )
+ log_error ("invalid formatted keyID\n");
+ else {
+ kbxfile_search_by_kid ( argv[0], kid, mode );
+ }
+ }
+ else if ( cmd == aFindByUid ) {
+ if ( argc != 2 )
+ wrong_args ("kbxfile userID");
+ kbxfile_search_by_uid ( argv[0], argv[1] );
+ }
+#endif
+ else
+ log_error ("unsupported action\n");
+
+ myexit(0);
+ return 8; /*NEVER REACHED*/
+}
+
+
+void
+myexit( int rc )
+{
+ /* 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 ); */
+ rc = rc? rc : log_get_errorcount(0)? 2 :
+ keybox_errors_seen? 1 : 0;
+ exit(rc );
+}
+
+
diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c
new file mode 100644
index 000000000..5ad1d2610
--- /dev/null
+++ b/kbx/keybox-blob.c
@@ -0,0 +1,1008 @@
+/* keybox-blob.c - KBX Blob handling
+ * Copyright (C) 2000, 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+
+/* The keybox data formats
+
+The KeyBox uses an augmented OpenPGP/X.509 key format. This makes
+random access to a keyblock/Certificate easier and also gives the
+opportunity to store additional information (e.g. the fingerprint)
+along with the key. All integers are stored in network byte order,
+offsets are counted from the beginning of the Blob.
+
+The first record of a plain KBX file has a special format:
+
+ u32 length of the first record
+ byte Blob type (1)
+ byte version number (1)
+ byte reserved
+ byte reserved
+ u32 magic 'KBXf'
+ byte pgp_marginals used for validity calculation of this file
+ byte pgp_completes ditto.
+ byte pgp_cert_depth ditto.
+
+The OpenPGP and X.509 blob are verry similiar, things which are
+X.509 specific are noted like [X.509: xxx]
+
+ u32 length of this blob (including these 4 bytes)
+ byte Blob type (2) [X509: 3]
+ byte version number of this blob type (1)
+ u16 Blob flags
+ bit 0 = contains secret key material
+ bit 1 = ephemeral blob (e.g. used while quering external resources)
+
+ u32 offset to the OpenPGP keyblock or X509 DER encoded certificate
+ u32 and its length
+ u16 number of keys (at least 1!) [X509: always 1]
+ u16 size of additional key information
+ n times:
+ b20 The keys fingerprint
+ (fingerprints are always 20 bytes, MD5 left padded with zeroes)
+ u32 offset to the n-th key's keyID (a keyID is always 8 byte)
+ or 0 if not known which is the case opnly for X509.
+ u16 special key flags
+ bit 0 =
+ u16 reserved
+ u16 size of serialnumber(may be zero)
+ n u16 (see above) bytes of serial number
+ u16 number of user IDs
+ u16 size of additional user ID information
+ n times:
+ u32 offset to the n-th user ID
+ u32 length of this user ID.
+ u16 special user ID flags.
+ bit 0 =
+ byte validity
+ byte reserved
+ [For X509, the first user ID is the ISsuer, the second the subject
+ and the others are subjectAltNames]
+ u16 number of signatures
+ u16 size of signature information (4)
+ u32 expiration time of signature with some special values:
+ 0x00000000 = not checked
+ 0x00000001 = missing key
+ 0x00000002 = bad signature
+ 0x10000000 = valid and expires at some date in 1978.
+ 0xffffffff = valid and does not expire
+ u8 assigned ownertrust [X509: no used]
+ u8 all_validity [X509: no used]
+ u16 reserved
+ u32 recheck_after
+ u32 Newest timestamp in the keyblock (useful for KS syncronsiation?)
+ u32 Blob created at
+ u32 size of reserved space (not including this field)
+ reserved space
+
+ Here we might want to put other data
+
+ Here comes the keyblock
+
+ maybe we put a signature here later.
+
+ b16 MD5 checksum (useful for KS syncronisation), we might also want to use
+ a mac here.
+ b4 resevered
+
+*/
+
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "keybox-defs.h"
+#include <gcrypt.h>
+
+#ifdef KEYBOX_WITH_OPENPGP
+/* include stuff to parse the packets */
+#endif
+#ifdef KEYBOX_WITH_X509
+#include <ksba.h>
+#endif
+
+
+
+/* special values of the signature status */
+#define SF_NONE(a) ( !(a) )
+#define SF_NOKEY(a) ((a) & (1<<0))
+#define SF_BAD(a) ((a) & (1<<1))
+#define SF_VALID(a) ((a) & (1<<29))
+
+
+struct membuf {
+ size_t len;
+ size_t size;
+ char *buf;
+ int out_of_core;
+};
+
+
+/* #if MAX_FINGERPRINT_LEN < 20 */
+/* #error fingerprints are 20 bytes */
+/* #endif */
+
+struct keyboxblob_key {
+ char fpr[20];
+ u32 off_kid;
+ ulong off_kid_addr;
+ u16 flags;
+};
+struct keyboxblob_uid {
+ ulong off_addr;
+ char *name; /* used only with x509 */
+ u32 len;
+ u16 flags;
+ byte validity;
+};
+
+struct keyid_list {
+ struct keyid_list *next;
+ int seqno;
+ byte kid[8];
+};
+
+struct fixup_list {
+ struct fixup_list *next;
+ u32 off;
+ u32 val;
+};
+
+
+struct keyboxblob {
+ byte *blob;
+ size_t bloblen;
+ off_t fileoffset;
+
+ /* stuff used only by keybox_create_blob */
+ unsigned char *serialbuf;
+ const unsigned char *serial;
+ size_t seriallen;
+ int nkeys;
+ struct keyboxblob_key *keys;
+ int nuids;
+ struct keyboxblob_uid *uids;
+ int nsigs;
+ u32 *sigs;
+ struct fixup_list *fixups;
+ int fixup_out_of_core;
+
+ struct keyid_list *temp_kids;
+ struct membuf bufbuf; /* temporary store for the blob */
+ struct membuf *buf;
+};
+
+
+
+/* A simple implemnation 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;
+}
+
+
+static void
+put8 (struct membuf *mb, byte a )
+{
+ put_membuf (mb, &a, 1);
+}
+
+static void
+put16 (struct membuf *mb, u16 a )
+{
+ unsigned char tmp[2];
+ tmp[0] = a>>8;
+ tmp[1] = a;
+ put_membuf (mb, tmp, 2);
+}
+
+static void
+put32 (struct membuf *mb, u32 a )
+{
+ unsigned char tmp[4];
+ tmp[0] = a>>24;
+ tmp[1] = a>>16;
+ tmp[2] = a>>8;
+ tmp[3] = a;
+ put_membuf (mb, tmp, 4);
+}
+
+
+/* Store a value in the fixup list */
+static void
+add_fixup (KEYBOXBLOB blob, u32 off, u32 val)
+{
+ struct fixup_list *fl;
+
+ if (blob->fixup_out_of_core)
+ return;
+
+ fl = xtrycalloc(1, sizeof *fl);
+ if (!fl)
+ blob->fixup_out_of_core = 1;
+ else
+ {
+ fl->off = off;
+ fl->val = val;
+ fl->next = blob->fixups;
+ blob->fixups = fl;
+ }
+}
+
+
+/*
+ Some wrappers
+*/
+
+static u32
+make_timestamp (void)
+{
+ return time(NULL);
+}
+
+
+
+#ifdef KEYBOX_WITH_OPENPGP
+/*
+ OpenPGP specific stuff
+*/
+
+
+/*
+ We must store the keyid at some place because we can't calculate the
+ offset yet. This is only used for v3 keyIDs. Function returns an
+ index value for later fixup or -1 for out of core. The value must be
+ a non-zero value */
+static int
+pgp_temp_store_kid (KEYBOXBLOB blob, PKT_public_key *pk)
+{
+ struct keyid_list *k, *r;
+
+ k = xtrymalloc (sizeof *k);
+ if (!k)
+ return -1;
+ k->kid[0] = pk->keyid[0] >> 24 ;
+ k->kid[1] = pk->keyid[0] >> 16 ;
+ k->kid[2] = pk->keyid[0] >> 8 ;
+ k->kid[3] = pk->keyid[0] ;
+ k->kid[4] = pk->keyid[0] >> 24 ;
+ k->kid[5] = pk->keyid[0] >> 16 ;
+ k->kid[6] = pk->keyid[0] >> 8 ;
+ k->kid[7] = pk->keyid[0] ;
+ k->seqno = 0;
+ k->next = blob->temp_kids;
+ blob->temp_kids = k;
+ for (r=k; r; r = r->next)
+ k->seqno++;
+
+ return k->seqno;
+}
+
+static int
+pgp_create_key_part (KEYBOXBLOB blob, KBNODE keyblock)
+{
+ KBNODE node;
+ size_t fprlen;
+ int n;
+
+ for (n=0, node = keyblock; node; node = node->next)
+ {
+ if ( node->pkt->pkttype == PKT_PUBLIC_KEY
+ || node->pkt->pkttype == PKT_PUBLIC_SUBKEY )
+ {
+ PKT_public_key *pk = node->pkt->pkt.public_key;
+ char tmp[20];
+
+ fingerprint_from_pk (pk, tmp , &fprlen);
+ memcpy (blob->keys[n].fpr, tmp, 20);
+ if ( fprlen != 20 ) /*v3 fpr - shift right and fill with zeroes*/
+ {
+ assert (fprlen == 16);
+ memmove (blob->keys[n].fpr+4, blob->keys[n].fpr, 16);
+ memset (blob->keys[n].fpr, 0, 4);
+ blob->keys[n].off_kid = pgp_temp_store_kid (blob, pk);
+ }
+ else
+ {
+ blob->keys[n].off_kid = 0; /* will be fixed up later */
+ }
+ blob->keys[n].flags = 0;
+ n++;
+ }
+ else if ( node->pkt->pkttype == PKT_SECRET_KEY
+ || node->pkt->pkttype == PKT_SECRET_SUBKEY )
+ {
+ never_reached (); /* actually not yet implemented */
+ }
+ }
+ assert (n == blob->nkeys);
+ return 0;
+}
+
+static int
+pgp_create_uid_part (KEYBOXBLOB blob, KBNODE keyblock)
+{
+ KBNODE node;
+ int n;
+
+ for (n=0, node = keyblock; node; node = node->next)
+ {
+ if (node->pkt->pkttype == PKT_USER_ID)
+ {
+ PKT_user_id *u = node->pkt->pkt.user_id;
+
+ blob->uids[n].len = u->len;
+ blob->uids[n].flags = 0;
+ blob->uids[n].validity = 0;
+ n++;
+ }
+ }
+ assert (n == blob->nuids);
+ return 0;
+}
+
+static int
+pgp_create_sig_part (KEYBOXBLOB blob, KBNODE keyblock)
+{
+ KBNODE node;
+ int n;
+
+ for (n=0, node = keyblock; node; node = node->next)
+ {
+ if (node->pkt->pkttype == PKT_SIGNATURE)
+ {
+ PKT_signature *sig = node->pkt->pkt.signature;
+
+ blob->sigs[n] = 0; /* FIXME: check the signature here */
+ n++;
+ }
+ }
+ assert( n == blob->nsigs );
+ return 0;
+}
+
+static int
+pgp_create_blob_keyblock (KEYBOXBLOB blob, KBNODE keyblock)
+{
+ struct membuf *a = blob->buf;
+ KBNODE node;
+ int rc;
+ int n;
+ u32 kbstart = a->len;
+
+ add_fixup (blob, kbstart);
+
+ for (n = 0, node = keyblock; node; node = node->next)
+ {
+ rc = build_packet ( a, node->pkt );
+ if ( rc ) {
+ gpg_log_error ("build_packet(%d) for keyboxblob failed: %s\n",
+ node->pkt->pkttype, gpg_errstr(rc) );
+ return GPGERR_WRITE_FILE;
+ }
+ if ( node->pkt->pkttype == PKT_USER_ID )
+ {
+ PKT_user_id *u = node->pkt->pkt.user_id;
+ /* build_packet has set the offset of the name into u ;
+ * now we can do the fixup */
+ add_fixup (blob, blob->uids[n].off_addr, u->stored_at);
+ n++;
+ }
+ }
+ assert (n == blob->nuids);
+
+ add_fixup (blob, a->len - kbstart);
+ return 0;
+}
+
+#endif /*KEYBOX_WITH_OPENPGP*/
+
+
+#ifdef KEYBOX_WITH_X509
+/*
+ X.509 specific stuff
+ */
+
+/* Write the raw certificate out */
+static int
+x509_create_blob_cert (KEYBOXBLOB blob, KsbaCert cert)
+{
+ struct membuf *a = blob->buf;
+ const unsigned char *image;
+ size_t length;
+ u32 kbstart = a->len;
+
+ /* Store our offset for later fixup */
+ add_fixup (blob, 8, kbstart);
+
+ image = ksba_cert_get_image (cert, &length);
+ if (!image)
+ return gpg_error (GPG_ERR_GENERAL);
+ put_membuf (a, image, length);
+
+ add_fixup (blob, 12, a->len - kbstart);
+ return 0;
+}
+
+#endif /*KEYBOX_WITH_X509*/
+
+/* Write a stored keyID out to the buffer */
+static void
+write_stored_kid (KEYBOXBLOB blob, int seqno)
+{
+ struct keyid_list *r;
+
+ for ( r = blob->temp_kids; r; r = r->next )
+ {
+ if (r->seqno == seqno )
+ {
+ put_membuf (blob->buf, r->kid, 8);
+ return;
+ }
+ }
+ never_reached ();
+}
+
+/* Release a list of key IDs */
+static void
+release_kid_list (struct keyid_list *kl)
+{
+ struct keyid_list *r, *r2;
+
+ for ( r = kl; r; r = r2 )
+ {
+ r2 = r->next;
+ xfree (r);
+ }
+}
+
+
+
+static int
+create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral)
+{
+ struct membuf *a = blob->buf;
+ int i;
+
+ put32 ( a, 0 ); /* blob length, needs fixup */
+ put8 ( a, blobtype);
+ put8 ( a, 1 ); /* blob type version */
+ put16 ( a, as_ephemeral? 2:0 ); /* blob flags */
+
+ put32 ( a, 0 ); /* offset to the raw data, needs fixup */
+ put32 ( a, 0 ); /* length of the raw data, needs fixup */
+
+ put16 ( a, blob->nkeys );
+ put16 ( a, 20 + 4 + 2 + 2 ); /* size of key info */
+ for ( i=0; i < blob->nkeys; i++ )
+ {
+ put_membuf (a, blob->keys[i].fpr, 20);
+ blob->keys[i].off_kid_addr = a->len;
+ put32 ( a, 0 ); /* offset to keyid, fixed up later */
+ put16 ( a, blob->keys[i].flags );
+ put16 ( a, 0 ); /* reserved */
+ }
+
+ put16 (a, blob->seriallen); /*fixme: check that it fits into 16 bits*/
+ if (blob->serial)
+ put_membuf (a, blob->serial, blob->seriallen);
+
+ put16 ( a, blob->nuids );
+ put16 ( a, 4 + 4 + 2 + 1 + 1 ); /* size of uid info */
+ for (i=0; i < blob->nuids; i++)
+ {
+ blob->uids[i].off_addr = a->len;
+ put32 ( a, 0 ); /* offset to userid, fixed up later */
+ put32 ( a, blob->uids[i].len );
+ put16 ( a, blob->uids[i].flags );
+ put8 ( a, 0 ); /* validity */
+ put8 ( a, 0 ); /* reserved */
+ }
+
+ put16 ( a, blob->nsigs );
+ put16 ( a, 4 ); /* size of sig info */
+ for (i=0; i < blob->nsigs; i++)
+ {
+ put32 ( a, blob->sigs[i]);
+ }
+
+ put8 ( a, 0 ); /* assigned ownertrust */
+ put8 ( a, 0 ); /* validity of all user IDs */
+ put16 ( a, 0 ); /* reserved */
+ put32 ( a, 0 ); /* time of next recheck */
+ put32 ( a, 0 ); /* newest timestamp (none) */
+ put32 ( a, make_timestamp() ); /* creation time */
+ put32 ( a, 0 ); /* size of reserved space */
+ /* reserved space (which is currently of size 0) */
+
+ /* space where we write keyIDs and and other stuff so that the
+ pointers can actually point to somewhere */
+ if (blobtype == BLOBTYPE_PGP)
+ {
+ /* We need to store the keyids for all pgp v3 keys because those key
+ IDs are not part of the fingerprint. While we are doing that, we
+ fixup all the keyID offsets */
+ for (i=0; i < blob->nkeys; i++ )
+ {
+ if (blob->keys[i].off_kid)
+ { /* this is a v3 one */
+ add_fixup (blob, blob->keys[i].off_kid_addr, a->len);
+ write_stored_kid (blob, blob->keys[i].off_kid);
+ }
+ else
+ { /* the better v4 key IDs - just store an offset 8 bytes back */
+ add_fixup (blob, blob->keys[i].off_kid_addr,
+ blob->keys[i].off_kid_addr - 8);
+ }
+ }
+ }
+
+ if (blobtype == BLOBTYPE_X509)
+ {
+ /* We don't want to point to ASN.1 encoded UserIDs (DNs) but to
+ the utf-8 string represenation of them */
+ for (i=0; i < blob->nuids; i++ )
+ {
+ if (blob->uids[i].name)
+ { /* this is a v3 one */
+ add_fixup (blob, blob->uids[i].off_addr, a->len);
+ put_membuf (blob->buf, blob->uids[i].name, blob->uids[i].len);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+
+static int
+create_blob_trailer (KEYBOXBLOB blob)
+{
+ return 0;
+}
+
+
+static int
+create_blob_finish (KEYBOXBLOB blob)
+{
+ struct membuf *a = blob->buf;
+ byte *p;
+ char *pp;
+ int i;
+ size_t n;
+
+ /* write a placeholder for the checksum */
+ for (i = 0; i < 16; i++ )
+ put32 (a, 0); /* Hmmm: why put32() ?? */
+
+ /* get the memory area */
+ p = get_membuf (a, &n);
+ if (!p)
+ return gpg_error (GPG_ERR_ENOMEM);
+ assert (n >= 20);
+
+ /* fixup the length */
+ add_fixup (blob, 0, n);
+
+ /* do the fixups */
+ if (blob->fixup_out_of_core)
+ return gpg_error (GPG_ERR_ENOMEM);
+
+ {
+ struct fixup_list *fl;
+ for (fl = blob->fixups; fl; fl = fl->next)
+ {
+ assert (fl->off+4 <= n);
+ p[fl->off+0] = fl->val >> 24;
+ p[fl->off+1] = fl->val >> 16;
+ p[fl->off+2] = fl->val >> 8;
+ p[fl->off+3] = fl->val;
+ }
+ }
+
+ /* calculate and store the MD5 checksum */
+ gcry_md_hash_buffer (GCRY_MD_MD5, p + n - 16, p, n - 16);
+
+ pp = xtrymalloc (n);
+ if ( !pp )
+ return gpg_error (gpg_err_code_from_errno (errno));
+ memcpy (pp , p, n);
+ blob->blob = pp;
+ blob->bloblen = n;
+
+ return 0;
+}
+
+
+#ifdef KEYBOX_WITH_OPENPGP
+
+int
+_keybox_create_pgp_blob (KEYBOXBLOB *r_blob, KBNODE keyblock, int as_ephemeral)
+{
+ int rc = 0;
+ KBNODE node;
+ KEYBOXBLOB blob;
+
+ *r_blob = NULL;
+ blob = xtrycalloc (1, sizeof *blob);
+ if (!blob)
+ return gpg_error (gpg_err_code_from_errno (errno));
+
+ /* fixme: Do some sanity checks on the keyblock */
+
+ /* count userids and keys so that we can allocate the arrays */
+ for (node = keyblock; node; node = node->next)
+ {
+ switch (node->pkt->pkttype)
+ {
+ case PKT_PUBLIC_KEY:
+ case PKT_SECRET_KEY:
+ case PKT_PUBLIC_SUBKEY:
+ case PKT_SECRET_SUBKEY: blob->nkeys++; break;
+ case PKT_USER_ID: blob->nuids++; break;
+ case PKT_SIGNATURE: blob->nsigs++; break;
+ default: break;
+ }
+ }
+
+ blob->keys = xtrycalloc (blob->nkeys, sizeof *blob->keys );
+ blob->uids = xtrycalloc (blob->nuids, sizeof *blob->uids );
+ blob->sigs = xtrycalloc (blob->nsigs, sizeof *blob->sigs );
+ if (!blob->keys || !blob->uids || !blob->sigs)
+ {
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ goto leave;
+ }
+
+ rc = pgp_create_key_part ( blob, keyblock );
+ if (rc)
+ goto leave;
+ rc = pgp_create_uid_part ( blob, keyblock );
+ if (rc)
+ goto leave;
+ rc = pgp_create_sig_part ( blob, keyblock );
+ if (rc)
+ goto leave;
+
+ init_membuf (&blob->bufbuf, 1024);
+ blob->buf = &blob->bufbuf;
+ rc = create_blob_header (blob, BLOBTYPE_OPENPGP, as_ephemeral);
+ if (rc)
+ goto leave;
+ rc = pgp_create_blob_keyblock (blob, keyblock);
+ if (rc)
+ goto leave;
+ rc = create_blob_trailer (blob);
+ if (rc)
+ goto leave;
+ rc = create_blob_finish ( blob );
+ if (rc)
+ goto leave;
+
+
+ leave:
+ release_kid_list (blob->temp_kids);
+ blob->temp_kids = NULL;
+ if (rc)
+ {
+ keybox_release_blob (blob);
+ *r_blob = NULL;
+ }
+ else
+ {
+ *r_blob = blob;
+ }
+ return rc;
+}
+#endif /*KEYBOX_WITH_OPENPGP*/
+
+#ifdef KEYBOX_WITH_X509
+
+/* return an allocated string with the email address extracted from a
+ DN */
+static char *
+x509_email_kludge (const char *name)
+{
+ const unsigned char *p;
+ unsigned char *buf;
+ int n;
+
+ if (strncmp (name, "1.2.840.113549.1.9.1=#", 22))
+ return NULL;
+ /* 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 */
+ name += 22;
+ for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++)
+ ;
+ if (*p != '#' || !n)
+ return NULL;
+ buf = xtrymalloc (n+3);
+ if (!buf)
+ return NULL; /* oops, out of core */
+ *buf = '<';
+ for (n=1, p=name; *p != '#'; p +=2, n++)
+ buf[n] = xtoi_2 (p);
+ buf[n++] = '>';
+ buf[n] = 0;
+ return buf;
+}
+
+
+
+/* Note: We should move calculation of the digest into libksba and
+ remove that parameter */
+int
+_keybox_create_x509_blob (KEYBOXBLOB *r_blob, KsbaCert cert,
+ unsigned char *sha1_digest, int as_ephemeral)
+{
+ int i, rc = 0;
+ KEYBOXBLOB blob;
+ unsigned char *p;
+ unsigned char **names = NULL;
+ size_t max_names;
+
+ *r_blob = NULL;
+ blob = xtrycalloc (1, sizeof *blob);
+ if( !blob )
+ return gpg_error (gpg_err_code_from_errno (errno));
+
+ p = ksba_cert_get_serial (cert);
+ if (p)
+ {
+ size_t n, len;
+ n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+ if (n < 2)
+ {
+ xfree (p);
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+ blob->serialbuf = p;
+ p++; n--; /* skip '(' */
+ for (len=0; n && *p && *p != ':' && digitp (p); n--, p++)
+ len = len*10 + atoi_1 (p);
+ if (*p != ':')
+ {
+ xfree (blob->serialbuf);
+ blob->serialbuf = NULL;
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+ p++;
+ blob->serial = p;
+ blob->seriallen = len;
+ }
+
+ blob->nkeys = 1;
+
+ /* create list of names */
+ blob->nuids = 0;
+ max_names = 100;
+ names = xtrymalloc (max_names * sizeof *names);
+ if (!names)
+ {
+ rc = gpg_error (gpg_err_code_from_errno (errno));
+ goto leave;
+ }
+ p = ksba_cert_get_issuer (cert, 0);
+ if (!p)
+ {
+ rc = gpg_error (GPG_ERR_MISSING_VALUE);
+ goto leave;
+ }
+ names[blob->nuids++] = p;
+ for (i=0; (p = ksba_cert_get_subject (cert, i)); i++)
+ {
+
+ if (blob->nuids >= max_names)
+ {
+ unsigned char **tmp;
+
+ max_names += 100;
+ tmp = xtryrealloc (names, max_names * sizeof *names);
+ if (!tmp)
+ {
+ rc = gpg_error (gpg_err_code_from_errno (errno));
+ goto leave;
+ }
+ }
+ names[blob->nuids++] = p;
+ if (!i && (p=x509_email_kludge (p)))
+ names[blob->nuids++] = p; /* due to !i we don't need to check bounds*/
+ }
+
+ /* space for signature information */
+ blob->nsigs = 1;
+
+ blob->keys = xtrycalloc (blob->nkeys, sizeof *blob->keys );
+ blob->uids = xtrycalloc (blob->nuids, sizeof *blob->uids );
+ blob->sigs = xtrycalloc (blob->nsigs, sizeof *blob->sigs );
+ if (!blob->keys || !blob->uids || !blob->sigs)
+ {
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ goto leave;
+ }
+
+ memcpy (blob->keys[0].fpr, sha1_digest, 20);
+ blob->keys[0].off_kid = 0; /* We don't have keyids */
+ blob->keys[0].flags = 0;
+
+ /* issuer and subject names */
+ for (i=0; i < blob->nuids; i++)
+ {
+ blob->uids[i].name = names[i];
+ blob->uids[i].len = strlen(names[i]);
+ names[i] = NULL;
+ blob->uids[i].flags = 0;
+ blob->uids[i].validity = 0;
+ }
+ xfree (names);
+ names = NULL;
+
+ /* signatures */
+ blob->sigs[0] = 0; /* not yet checked */
+
+ /* Create a temporary buffer for further processing */
+ init_membuf (&blob->bufbuf, 1024);
+ blob->buf = &blob->bufbuf;
+ /* write out what we already have */
+ rc = create_blob_header (blob, BLOBTYPE_X509, as_ephemeral);
+ if (rc)
+ goto leave;
+ rc = x509_create_blob_cert (blob, cert);
+ if (rc)
+ goto leave;
+ rc = create_blob_trailer (blob);
+ if (rc)
+ goto leave;
+ rc = create_blob_finish ( blob );
+ if (rc)
+ goto leave;
+
+
+ leave:
+ release_kid_list (blob->temp_kids);
+ blob->temp_kids = NULL;
+ if (blob && names)
+ {
+ for (i=0; i < blob->nuids; i++)
+ xfree (names[i]);
+ }
+ xfree (names);
+ if (rc)
+ {
+ _keybox_release_blob (blob);
+ *r_blob = NULL;
+ }
+ else
+ {
+ *r_blob = blob;
+ }
+ return rc;
+}
+#endif /*KEYBOX_WITH_X509*/
+
+
+
+int
+_keybox_new_blob (KEYBOXBLOB *r_blob, char *image, size_t imagelen, off_t off)
+{
+ KEYBOXBLOB blob;
+
+ *r_blob = NULL;
+ blob = xtrycalloc (1, sizeof *blob);
+ if (!blob)
+ return gpg_error (gpg_err_code_from_errno (errno));
+
+ blob->blob = image;
+ blob->bloblen = imagelen;
+ blob->fileoffset = off;
+ *r_blob = blob;
+ return 0;
+}
+
+void
+_keybox_release_blob (KEYBOXBLOB blob)
+{
+ int i;
+ if (!blob)
+ return;
+ /* hmmm: release membuf here?*/
+ xfree (blob->keys );
+ xfree (blob->serialbuf);
+ for (i=0; i < blob->nuids; i++)
+ xfree (blob->uids[i].name);
+ xfree (blob->uids );
+ xfree (blob->sigs );
+ xfree (blob->blob );
+ xfree (blob );
+}
+
+
+
+const char *
+_keybox_get_blob_image ( KEYBOXBLOB blob, size_t *n )
+{
+ *n = blob->bloblen;
+ return blob->blob;
+}
+
+off_t
+_keybox_get_blob_fileoffset (KEYBOXBLOB blob)
+{
+ return blob->fileoffset;
+}
+
diff --git a/scd/ChangeLog b/scd/ChangeLog
new file mode 100644
index 000000000..ad4b0518c
--- /dev/null
+++ b/scd/ChangeLog
@@ -0,0 +1,242 @@
+2003-07-31 Werner Koch <wk@gnupg.org>
+
+ * Makefile.am (scdaemon_LDADD): Added INTLLIBS.
+
+2003-07-28 Werner Koch <wk@gnupg.org>
+
+ * app-openpgp.c (do_setattr): Change implementation. Allow all
+ useful DOs.
+
+2003-07-27 Werner Koch <wk@gnupg.org>
+
+ Adjusted for gcry_mpi_print and gcry_mpi_scan API change.
+
+2003-07-24 Werner Koch <wk@gnupg.org>
+
+ * app-openpgp.c (do_learn_status): Print more status information.
+ (app_select_openpgp): Store the card version.
+ (store_fpr): Add argument card_version and fix DOs for old cards.
+ (app_openpgp_storekey): Likewise.
+
+2003-07-23 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_pkauth): New.
+ (cmd_setdata): Check whether data was given at all to avoid
+ passing 0 to malloc.
+
+ * app.c (app_auth): New.
+ * app-openpgp.c (do_auth): New.
+
+2003-07-22 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_passwd): New.
+ * app.c (app_change_pin): New.
+ * app-openpgp.c (do_change_pin): New.
+ * iso7816.c (iso7816_reset_retry_counter): Implemented.
+
+ * sc-investigate.c (main): New option --gen-random.
+ * iso7816.c (iso7816_get_challenge): Don't create APDUs with a
+ length larger than 255.
+
+2003-07-17 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_random): New command RANDOM.
+
+ * iso7816.c (map_sw): New. Use it in this file to return
+ meaningful error messages. Changed all public fucntions to return
+ a gpg_error_t.
+ (iso7816_change_reference_data): New.
+ * apdu.c (apdu_open_reader): Use faked status words for soem
+ system errors.
+
+2003-07-16 Werner Koch <wk@gnupg.org>
+
+ * apdu.c (apdu_send_simple): Use apdu_send_le so that we can
+ specify not to send Le as it should be.
+
+2003-07-15 Werner Koch <wk@gnupg.org>
+
+ * Makefile.am: Add sc-copykeys program.
+ * sc-copykeys.c: New.
+ * app-openpgp.c (app_openpgp_storekey): New.
+ (app_openpgp_cardinfo): New.
+ (count_bits): New.
+ (store_fpr): And use it here to get the actual length in bit.
+
+2003-07-03 Werner Koch <wk@gnupg.org>
+
+ * app-openpgp.c (do_setattr): Add setting of the URL.
+ (app_select_openpgp): Dump card data only in very verbose mode.
+ (do_decipher): New.
+
+2003-07-02 Werner Koch <wk@gnupg.org>
+
+ * app-openpgp.c (get_sig_counter): New.
+ (do_sign): Print the signature counter and enable the PIN callback.
+ (do_genkey): Implement the PIN callback.
+
+2003-07-01 Werner Koch <wk@gnupg.org>
+
+ * app-openpgp.c (store_fpr): Fixed fingerprint calculation.
+
+2003-06-26 Werner Koch <wk@gnupg.org>
+
+ * app-openpgp.c (find_tlv): Fixed length header parsing.
+
+ * app.c (app_genkey): New.
+ * command.c (cmd_genkey): New.
+
+2003-06-25 Werner Koch <wk@gnupg.org>
+
+ * command.c (percent_plus_unescape): New.
+ (cmd_setattr): New.
+
+2003-06-24 Werner Koch <wk@gnupg.org>
+
+ * command.c (send_status_info): New.
+
+ * app-openpgp.c (app_select_openpgp): Replace SLOT arg by APP arg
+ and setup the function pointers in APP on success. Changed callers.
+ * app.c: New.
+ * app-common.h: New.
+ * scdaemon.h (APP): New type to handle applications.
+ (server_control_s): Add an APP context field.
+
+ * command.c (cmd_serialno): Handle applications.
+ (cmd_pksign): Ditto.
+ (cmd_pkdecrypt): Ditto.
+ (reset_notify): Ditto.
+ (cmd_learn): For now return error for application contexts.
+ (cmd_readcert): Ditto.
+ (cmd_readkey): Ditto.
+
+2003-06-04 Werner Koch <wk@gnupg.org>
+
+ * card.c (map_sc_err): Renamed gpg_make_err to gpg_err_make.
+
+ 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.
+
+ * scdaemon.h: Include gpg-error.h and errno.h
+ * card.c (map_sc_err): Use unknown for the error source.
+ * Makefile.am: Link with libgpg-error
+
+2003-05-14 Werner Koch <wk@gnupg.org>
+
+ * atr.c, atr.h: New.
+ * sc-investigate.c: Dump the ATR in a human readable format.
+
+2003-05-08 Werner Koch <wk@gnupg.org>
+
+ * scdaemon.h (DBG_CARD_IO_VALUE): New.
+
+ * sc-investigate.c: New.
+ * scdaemon.c (main): Removed --print-atr option.
+
+ * iso7816.c, iso7816.h, app-openpgp.c: New.
+
+2003-04-29 Werner Koch <wk@gnupg.org>
+
+ * scdaemon.c: New options --print-atr and --reader-port
+ * apdu.c, apdu.h: New
+
+ * card.c, card-p15.c, card-dinsig.c: Allow build without OpenSC.
+
+ * Makefile.am (LDFLAGS): Removed.
+
+ * command.c (register_commands): Adjusted for new Assuan semantics.
+
+2002-08-21 Werner Koch <wk@gnupg.org>
+
+ * scdaemon.c (main): New option --daemon so that the program is
+ not accidently started in the background.
+
+2002-08-16 Werner Koch <wk@gnupg.org>
+
+ * scdaemon.c: Include i18n.h.
+
+ * card-common.h (struct p15_private_s): Forward declaration. Add
+ it to card_ctx_s.
+ * card.c (card_close): Make sure private data is released.
+ (card_enum_certs): New.
+ * card-p15.c (p15_release_private_data): New.
+ (init_private_data): New to work around an OpenSC weirdness.
+ (p15_enum_keypairs): Do an OpenSC get_objects only once.
+ (p15_enum_certs): New.
+ (card_p15_bind): Bind new function.
+ * command.c (cmd_learn): Return information about the certificates.
+
+2002-08-09 Werner Koch <wk@gnupg.org>
+
+ * card.c (card_get_serial_and_stamp): Use the tokeinfo serial
+ number as a fallback. Add a special prefix for serial numbers.
+
+2002-07-30 Werner Koch <wk@gnupg.org>
+
+ Changes to cope with OpenSC 0.7.0:
+
+ * card.c: Removed the check for the packed opensc version.
+ Changed include file names of opensc.
+ (map_sc_err): Adjusted error codes for new opensc version.
+ * card-p15.c: Changed include filename of opensc.
+ * card-dinsig.c: Ditto.
+
+ * card-p15.c (p15_decipher): Add flags argument to OpenSC call.
+
+2002-07-24 Werner Koch <wk@gnupg.org>
+
+ * card.c (find_simple_tlv, find_iccsn): New.
+ (card_get_serial_and_stamp): Improved serial number parser.
+
+2002-06-27 Werner Koch <wk@gnupg.org>
+
+ * scdaemon.c (main): Use GNUPG_DEFAULT_HOMEDIR constant.
+
+2002-06-15 Werner Koch <wk@gnupg.org>
+
+ * card-dinsig.c: Documented some stuff from the DIN norm.
+
+2002-04-15 Werner Koch <wk@gnupg.org>
+
+ * command.c (cmd_pksign, cmd_pkdecrypt): Use a copy of the key ID.
+
+2002-04-12 Werner Koch <wk@gnupg.org>
+
+ * scdaemon.c: New option --debug-sc N.
+ * card.c (card_open): set it here.
+
+ * card-p15.c (p15_prepare_key): Factored out common code from ...
+ (p15_sign, p15_decipher): here and made the decryption work the
+ regular way.
+
+2002-04-10 Werner Koch <wk@gnupg.org>
+
+ * card.c (card_open): Return immediately when no reader is available.
+
+2002-03-27 Werner Koch <wk@gnupg.org>
+
+ * card.c (card_open, card_close): Adjusted for changes in OpenSC.
+
+2002-03-10 Werner Koch <wk@gnupg.org>
+
+ * card-p15.c, card-dinsig.c, card-common.h: New.
+ * card.c: Factored most code out to the new modules, so that we
+ can better support different types of card applications.
+
+2002-01-26 Werner Koch <wk@gnupg.org>
+
+ * scdaemon.c scdaemon.h, command.c: New. Based on the code from
+ the gpg-agent.
+
+ Copyright 2002 Free Software Foundation, Inc.
+
+ This file is free software; as a special exception the author gives
+ unlimited permission to copy and/or distribute it, with or without
+ modifications, as long as this notice is preserved.
+
+ This file is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/scd/Makefile.am b/scd/Makefile.am
new file mode 100644
index 000000000..0771beb60
--- /dev/null
+++ b/scd/Makefile.am
@@ -0,0 +1,72 @@
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+## Process this file with automake to produce Makefile.in
+
+localedir = $(datadir)/locale
+INCLUDES = -I../intl -DLOCALEDIR=\"$(localedir)\"
+
+bin_PROGRAMS = scdaemon sc-investigate sc-copykeys
+
+AM_CPPFLAGS = -I$(top_srcdir)/common $(OPENSC_CFLAGS) $(LIBGCRYPT_CFLAGS) \
+ $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS)
+
+scdaemon_SOURCES = \
+ scdaemon.c scdaemon.h \
+ command.c card.c \
+ card-common.h \
+ card-p15.c card-dinsig.c \
+ apdu.c apdu.h \
+ iso7816.c iso7816.h \
+ app.c app-common.h \
+ app-openpgp.c
+
+scdaemon_LDADD = ../jnlib/libjnlib.a ../common/libcommon.a \
+ $(OPENSC_LIBS) $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) \
+ -lgpg-error @INTLLIBS@ -ldl
+
+sc_investigate_SOURCES = \
+ sc-investigate.c scdaemon.h \
+ apdu.c apdu.h \
+ iso7816.c iso7816.h \
+ app.c app-common.h \
+ app-openpgp.c \
+ atr.c atr.h
+
+sc_investigate_LDADD = \
+ ../jnlib/libjnlib.a ../common/libcommon.a \
+ $(LIBGCRYPT_LIBS) @INTLLIBS@ -lgpg-error -ldl
+
+
+sc_copykeys_SOURCES = \
+ sc-copykeys.c scdaemon.h \
+ apdu.c apdu.h \
+ iso7816.c iso7816.h \
+ app.c app-common.h \
+ app-openpgp.c \
+ atr.c atr.h
+
+sc_copykeys_LDADD = \
+ ../jnlib/libjnlib.a ../common/libcommon.a \
+ ../common/libsimple-pwquery.a \
+ $(LIBGCRYPT_LIBS) -lgpg-error @INTLLIBS@ -ldl
+
+
+
+
+
diff --git a/scd/apdu.c b/scd/apdu.c
new file mode 100644
index 000000000..6fec584b9
--- /dev/null
+++ b/scd/apdu.c
@@ -0,0 +1,558 @@
+/* apdu.c - ISO 7816 APDU functions and low level I/O
+ * Copyright (C) 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <assert.h>
+
+#include "scdaemon.h"
+#include "apdu.h"
+
+#define HAVE_CTAPI 1
+
+#define MAX_READER 4 /* Number of readers we support concurrently. */
+#define CARD_CONNECT_TIMEOUT 1 /* Number of seconds to wait for
+ insertion of the card (1 = don't wait). */
+
+
+
+/* A global table to keep track of active readers. */
+static struct {
+ int used; /* True if slot is used. */
+ unsigned short port; /* port number0 = unused, 1 - dev/tty */
+ int status;
+ unsigned char atr[33];
+ size_t atrlen;
+} reader_table[MAX_READER];
+
+
+/* ct API function pointer. */
+static char (*CT_init) (unsigned short ctn, unsigned short Pn);
+static char (*CT_data) (unsigned short ctn, unsigned char *dad,
+ unsigned char *sad, unsigned short lc,
+ unsigned char *cmd, unsigned short *lr,
+ unsigned char *rsp);
+static char (*CT_close) (unsigned short ctn);
+
+
+
+
+
+/*
+ Helper
+ */
+
+
+/* Find an unused reader slot for PORT and put it into the reader
+ table. Return -1 on error or the index into the reader table. */
+static int
+new_reader_slot (int port)
+{
+ int i, reader = -1;
+
+ if (port < 0 || port > 0xffff)
+ {
+ log_error ("new_reader_slot: invalid port %d requested\n", port);
+ return -1;
+ }
+
+ for (i=0; i < MAX_READER; i++)
+ {
+ if (reader_table[i].used && reader_table[i].port == port)
+ {
+ log_error ("new_reader_slot: requested port %d already in use\n",
+ reader);
+ return -1;
+ }
+ else if (!reader_table[i].used && reader == -1)
+ reader = i;
+ }
+ if (reader == -1)
+ {
+ log_error ("new_reader_slot: out of slots\n");
+ return -1;
+ }
+ reader_table[reader].used = 1;
+ reader_table[reader].port = port;
+ return reader;
+}
+
+
+static void
+dump_reader_status (int reader)
+{
+ log_info ("reader %d: %s\n", reader,
+ reader_table[reader].status == 1? "Processor ICC present" :
+ reader_table[reader].status == 0? "Memory ICC present" :
+ "ICC not present" );
+
+ if (reader_table[reader].status != -1)
+ {
+ log_info ("reader %d: ATR=", reader);
+ log_printhex ("", reader_table[reader].atr,
+ reader_table[reader].atrlen);
+ }
+}
+
+
+
+#ifdef HAVE_CTAPI
+/*
+ ct API Interface
+ */
+
+static const char *
+ct_error_string (int err)
+{
+ switch (err)
+ {
+ case 0: return "okay";
+ case -1: return "invalid data";
+ case -8: return "ct error";
+ case -10: return "transmission error";
+ case -11: return "memory allocation error";
+ case -128: return "HTSI error";
+ default: return "unknown CT-API error";
+ }
+}
+
+/* Wait for the card in READER and activate it. Return -1 on error or
+ 0 on success. */
+static int
+ct_activate_card (int reader)
+{
+ int rc, count;
+
+ for (count = 0; count < CARD_CONNECT_TIMEOUT; count++)
+ {
+ unsigned char dad[1], sad[1], cmd[11], buf[256];
+ unsigned short buflen;
+
+ if (count)
+ sleep (1); /* FIXME: we should use a more reliable timer. */
+
+ /* Check whether card has been inserted. */
+ dad[0] = 1; /* Destination address: CT. */
+ sad[0] = 2; /* Source address: Host. */
+
+ cmd[0] = 0x20; /* Class byte. */
+ cmd[1] = 0x13; /* Request status. */
+ cmd[2] = 0x00; /* From kernel. */
+ cmd[3] = 0x80; /* Return card's DO. */
+ cmd[4] = 0x00;
+
+ buflen = DIM(buf);
+
+ rc = CT_data (reader, dad, sad, 5, cmd, &buflen, buf);
+ if (rc || buflen < 2 || buf[buflen-2] != 0x90)
+ {
+ log_error ("ct_activate_card: can't get status of reader %d: %s\n",
+ reader, ct_error_string (rc));
+ return -1;
+ }
+
+ if (buf[0] == 0x05)
+ { /* Connected, now activate the card. */
+ dad[0] = 1; /* Destination address: CT. */
+ sad[0] = 2; /* Source address: Host. */
+
+ cmd[0] = 0x20; /* Class byte. */
+ cmd[1] = 0x12; /* Request ICC. */
+ cmd[2] = 0x01; /* From first interface. */
+ cmd[3] = 0x01; /* Return card's ATR. */
+ cmd[4] = 0x00;
+
+ buflen = DIM(buf);
+
+ rc = CT_data (reader, dad, sad, 5, cmd, &buflen, buf);
+ if (rc || buflen < 2 || buf[buflen-2] != 0x90)
+ {
+ log_error ("ct_activate_card(%d): activation failed: %s\n",
+ reader, ct_error_string (rc));
+ return -1;
+ }
+
+ /* Store the type and the ATR. */
+ if (buflen - 2 > DIM (reader_table[0].atr))
+ {
+ log_error ("ct_activate_card(%d): ATR too long\n", reader);
+ return -1;
+ }
+
+ reader_table[reader].status = buf[buflen - 1];
+ memcpy (reader_table[reader].atr, buf, buflen - 2);
+ reader_table[reader].atrlen = buflen - 2;
+ return 0;
+ }
+
+ }
+
+ log_info ("ct_activate_card(%d): timeout waiting for card\n", reader);
+ return -1;
+}
+
+
+/* Open a reader and return an internal handle for it. PORT is a
+ non-negative value with the port number of the reader. USB readers
+ do have port numbers starting at 32769. */
+static int
+open_ct_reader (int port)
+{
+ int rc, reader;
+
+ reader = new_reader_slot (port);
+ if (reader == -1)
+ return reader;
+
+ rc = CT_init (reader, (unsigned short)port);
+ if (rc)
+ {
+ log_error ("apdu_open_ct_reader failed on port %d: %s\n",
+ port, ct_error_string (rc));
+ reader_table[reader].used = 0;
+ return -1;
+ }
+
+ rc = ct_activate_card (reader);
+ if (rc)
+ {
+ reader_table[reader].used = 0;
+ return -1;
+ }
+
+ dump_reader_status (reader);
+ return reader;
+}
+
+
+/* Actually send the APDU of length APDULEN to SLOT and return a
+ maximum of *BUFLEN data in BUFFER, the actual retruned size will be
+ set to BUFLEN. Returns: CT API error code. */
+static int
+ct_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
+ unsigned char *buffer, size_t *buflen)
+{
+ int rc;
+ unsigned char dad[1], sad[1];
+ unsigned short ctbuflen;
+
+ dad[0] = 0; /* Destination address: Card. */
+ sad[0] = 2; /* Source address: Host. */
+ ctbuflen = *buflen;
+ if (DBG_CARD_IO)
+ log_printhex (" CT_data:", apdu, apdulen);
+ rc = CT_data (slot, dad, sad, apdulen, apdu, &ctbuflen, buffer);
+ *buflen = ctbuflen;
+
+ /* FIXME: map the errorcodes to GNUPG ones, so that they can be
+ shared between CTAPI and PCSC. */
+ return rc;
+}
+
+
+#endif /*HAVE_CTAPI*/
+
+
+#ifdef HAVE_PCSC
+/*
+ PC/SC Interface
+ */
+
+
+#endif /*HAVE_PCSC*/
+
+
+/*
+ Driver Access
+ */
+
+/* Open the reader and return an internal slot number or -1 on
+ error. */
+int
+apdu_open_reader (int port)
+{
+ static int ct_api_loaded;
+
+ if (!ct_api_loaded)
+ {
+ void *handle;
+
+ handle = dlopen ("libtowitoko.so", RTLD_LAZY);
+ if (!handle)
+ {
+ log_error ("apdu_open_reader: failed to open driver: %s",
+ dlerror ());
+ return -1;
+ }
+ CT_init = dlsym (handle, "CT_init");
+ CT_data = dlsym (handle, "CT_data");
+ CT_close = dlsym (handle, "CT_close");
+ if (!CT_init || !CT_data || !CT_close)
+ {
+ log_error ("apdu_open_reader: invalid driver\n");
+ dlclose (handle);
+ return -1;
+ }
+ ct_api_loaded = 1;
+ }
+ return open_ct_reader (port);
+}
+
+
+unsigned char *
+apdu_get_atr (int slot, size_t *atrlen)
+{
+ char *buf;
+
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ return NULL;
+
+ buf = xtrymalloc (reader_table[slot].atrlen);
+ if (!buf)
+ return NULL;
+ memcpy (buf, reader_table[slot].atr, reader_table[slot].atrlen);
+ *atrlen = reader_table[slot].atrlen;
+ return buf;
+}
+
+
+static const char *
+error_string (int slot, int rc)
+{
+#ifdef HAVE_CTAPI
+ return ct_error_string (rc);
+#elif defined(HAVE_PCSC)
+ return "?";
+#else
+ return "?";
+#endif
+}
+
+
+/* Dispatcher for the actual send_apdu fucntion. */
+static int
+send_apdu (int slot, unsigned char *apdu, size_t apdulen,
+ unsigned char *buffer, size_t *buflen)
+{
+#ifdef HAVE_CTAPI
+ return ct_send_apdu (slot, apdu, apdulen, buffer, buflen);
+#elif defined(HAVE_PCSC)
+ return SW_HOST_NO_DRIVER;
+#else
+ return SW_HOST_NO_DRIVER;
+#endif
+}
+
+/* Send an APDU to the card in SLOT. The APDU is created from all
+ given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1
+ for LC won't sent this field and the data field; in this case DATA
+ must also be passed as NULL. The return value is the status word
+ or -1 for an invalid SLOT or other non card related error. If
+ RETBUF is not NULL, it will receive an allocated buffer with the
+ returned data. The length of that data will be put into
+ *RETBUFLEN. The caller is reponsible for releasing the buffer even
+ in case of errors. */
+int
+apdu_send_le(int slot, int class, int ins, int p0, int p1,
+ int lc, const char *data, int le,
+ unsigned char **retbuf, size_t *retbuflen)
+{
+ unsigned char result[256+10]; /* 10 extra in case of bugs in the driver. */
+ size_t resultlen = 256;
+ unsigned char apdu[5+256+1];
+ size_t apdulen;
+ int rc, sw;
+
+ if (DBG_CARD_IO)
+ log_debug ("send apdu: c=%02X i=%02X p0=%02X p1=%02X lc=%d le=%d\n",
+ class, ins, p0, p1, lc, le);
+
+ if (lc != -1 && (lc > 255 || lc < 0))
+ return SW_WRONG_LENGTH;
+ if (le != -1 && (le > 256 || le < 1))
+ return SW_WRONG_LENGTH;
+ if ((!data && lc != -1) || (data && lc == -1))
+ return SW_HOST_INV_VALUE;
+
+ apdulen = 0;
+ apdu[apdulen++] = class;
+ apdu[apdulen++] = ins;
+ apdu[apdulen++] = p0;
+ apdu[apdulen++] = p1;
+ if (lc != -1)
+ {
+ apdu[apdulen++] = lc;
+ memcpy (apdu+apdulen, data, lc);
+ apdulen += lc;
+ }
+ if (le != -1)
+ apdu[apdulen++] = le; /* Truncation is okay becuase 0 means 256. */
+ assert (sizeof (apdu) >= apdulen);
+ /* As safeguard don't pass any garbage from the stack to the driver. */
+ memset (apdu+apdulen, 0, sizeof (apdu) - apdulen);
+ rc = send_apdu (slot, apdu, apdulen, result, &resultlen);
+ if (rc || resultlen < 2)
+ {
+ log_error ("apdu_send_simple(%d) failed: %s\n",
+ slot, error_string (slot, rc));
+ return SW_HOST_INCOMPLETE_CARD_RESPONSE;
+ }
+ sw = (result[resultlen-2] << 8) | result[resultlen-1];
+ /* store away the returned data but strip the statusword. */
+ resultlen -= 2;
+ if (DBG_CARD_IO)
+ {
+ log_debug (" response: sw=%04X datalen=%d\n", sw, resultlen);
+ if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA))
+ log_printhex (" dump: ", result, resultlen);
+ }
+
+ if (sw == SW_SUCCESS)
+ {
+ if (retbuf)
+ {
+ *retbuf = xtrymalloc (resultlen? resultlen : 1);
+ if (!*retbuf)
+ return SW_HOST_OUT_OF_CORE;
+ *retbuflen = resultlen;
+ memcpy (*retbuf, result, resultlen);
+ }
+ }
+ else if ((sw & 0xff00) == SW_MORE_DATA)
+ {
+ unsigned char *p = NULL, *tmp;
+ size_t bufsize = 4096;
+
+ /* It is likely that we need to return much more data, so we
+ start off with a large buffer. */
+ if (retbuf)
+ {
+ *retbuf = p = xtrymalloc (bufsize);
+ if (!*retbuf)
+ return SW_HOST_OUT_OF_CORE;
+ assert (resultlen < bufsize);
+ memcpy (p, result, resultlen);
+ p += resultlen;
+ }
+
+ do
+ {
+ int len = (sw & 0x00ff);
+
+ log_debug ("apdu_send_simple(%d): %d more bytes available\n",
+ slot, len);
+ apdulen = 0;
+ apdu[apdulen++] = class;
+ apdu[apdulen++] = 0xC0;
+ apdu[apdulen++] = 0;
+ apdu[apdulen++] = 0;
+ apdu[apdulen++] = 64; /* that is 256 bytes for Le */
+ memset (apdu+apdulen, 0, sizeof (apdu) - apdulen);
+ rc = send_apdu (slot, apdu, apdulen, result, &resultlen);
+ if (rc || resultlen < 2)
+ {
+ log_error ("apdu_send_simple(%d) for get response failed: %s\n",
+ slot, error_string (slot, rc));
+ return SW_HOST_INCOMPLETE_CARD_RESPONSE;
+ }
+ sw = (result[resultlen-2] << 8) | result[resultlen-1];
+ resultlen -= 2;
+ if (DBG_CARD_IO)
+ {
+ log_debug (" more: sw=%04X datalen=%d\n", sw, resultlen);
+ if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA))
+ log_printhex (" dump: ", result, resultlen);
+ }
+
+ if ((sw & 0xff00) == SW_MORE_DATA || sw == SW_SUCCESS)
+ {
+ if (retbuf)
+ {
+ if (p - *retbuf + resultlen > bufsize)
+ {
+ bufsize += resultlen > 4096? resultlen: 4096;
+ tmp = xtryrealloc (*retbuf, bufsize);
+ if (!tmp)
+ return SW_HOST_OUT_OF_CORE;
+ p = tmp + (p - *retbuf);
+ *retbuf = tmp;
+ }
+ memcpy (p, result, resultlen);
+ p += resultlen;
+ }
+ }
+ else
+ log_info ("apdu_send_simple(%d) "
+ "got unexpected status %04X from get response\n",
+ slot, sw);
+ }
+ while ((sw & 0xff00) == SW_MORE_DATA);
+
+ if (retbuf)
+ {
+ *retbuflen = p - *retbuf;
+ tmp = xtryrealloc (*retbuf, *retbuflen);
+ if (tmp)
+ *retbuf = tmp;
+ }
+ }
+ if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS)
+ log_printhex (" dump: ", *retbuf, *retbuflen);
+
+ return sw;
+}
+
+/* Send an APDU to the card in SLOT. The APDU is created from all
+ given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
+ LC won't sent this field and the data field; in this case DATA must
+ also be passed as NULL. The return value is the status word or -1
+ for an invalid SLOT or other non card related error. If RETBUF is
+ not NULL, it will receive an allocated buffer with the returned
+ data. The length of that data will be put into *RETBUFLEN. The
+ caller is reponsible for releasing the buffer even in case of
+ errors. */
+int
+apdu_send (int slot, int class, int ins, int p0, int p1,
+ int lc, const char *data, unsigned char **retbuf, size_t *retbuflen)
+{
+ return apdu_send_le (slot, class, ins, p0, p1, lc, data, 256,
+ retbuf, retbuflen);
+}
+
+/* Send an APDU to the card in SLOT. The APDU is created from all
+ given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
+ LC won't sent this field and the data field; in this case DATA must
+ also be passed as NULL. The return value is the status word or -1
+ for an invalid SLOT or other non card related error. No data will be
+ returned. */
+int
+apdu_send_simple (int slot, int class, int ins, int p0, int p1,
+ int lc, const char *data)
+{
+ return apdu_send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL);
+}
+
+
+
+
diff --git a/scd/apdu.h b/scd/apdu.h
new file mode 100644
index 000000000..44166a3fe
--- /dev/null
+++ b/scd/apdu.h
@@ -0,0 +1,73 @@
+/* apdu.h - ISO 7816 APDU functions and low level I/O
+ * Copyright (C) 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef APDU_H
+#define APDU_H
+
+/* ISO 7816 values for the statusword are defined here because they
+ should not be visible to the users of the actual ISO command
+ API. */
+enum {
+ SW_MORE_DATA = 0x6100, /* Note: that the low byte must be
+ masked of.*/
+ SW_EEPROM_FAILURE = 0x6581,
+ SW_WRONG_LENGTH = 0x6700,
+ SW_CHV_WRONG = 0x6982,
+ SW_CHV_BLOCKED = 0x6983,
+ SW_USE_CONDITIONS = 0x6985,
+ SW_NOT_SUPPORTED = 0x6a81,
+ SW_BAD_PARAMETER = 0x6a80, /* (in the data field) */
+ SW_REF_NOT_FOUND = 0x6a88,
+ SW_BAD_P0_P1 = 0x6b00,
+ SW_INS_NOT_SUP = 0x6d00,
+ SW_CLA_NOT_SUP = 0x6e00,
+ SW_SUCCESS = 0x9000,
+
+ /* The follwoing statuswords are no real ones but used to map host
+ OS errors into status words. A status word is 16 bit so that
+ those values can't be issued by a card. */
+ SW_HOST_OUT_OF_CORE = 0x10001, /* No way yet to differentiate
+ between errnos on a failed malloc. */
+ SW_HOST_INV_VALUE = 0x10002,
+ SW_HOST_INCOMPLETE_CARD_RESPONSE = 0x10003,
+};
+
+
+
+/* Note , that apdu_open_reader returns no status word but -1 on error. */
+int apdu_open_reader (int port);
+unsigned char *apdu_get_atr (int slot, size_t *atrlen);
+
+
+/* The apdu send functions do return status words. */
+int apdu_send_simple (int slot, int class, int ins, int p0, int p1,
+ int lc, const char *data);
+int apdu_send (int slot, int class, int ins, int p0, int p1,
+ int lc, const char *data,
+ unsigned char **retbuf, size_t *retbuflen);
+int apdu_send_le (int slot, int class, int ins, int p0, int p1,
+ int lc, const char *data, int le,
+ unsigned char **retbuf, size_t *retbuflen);
+
+
+#endif /*APDU_H*/
+
+
+
diff --git a/scd/app-common.h b/scd/app-common.h
new file mode 100644
index 000000000..282f82715
--- /dev/null
+++ b/scd/app-common.h
@@ -0,0 +1,128 @@
+/* app-common.h - Common declarations for all card applications
+ * Copyright (C) 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef GNUPG_SCD_APP_COMMON_H
+#define GNUPG_SCD_APP_COMMON_H
+
+struct app_ctx_s {
+ int initialized; /* The application has been initialied and the
+ function pointers may be used. Note that for
+ unsupported operations the particular
+ function pointer is set to NULL */
+ int slot; /* Used reader. */
+ unsigned char *serialno; /* Serialnumber in raw form, allocated. */
+ size_t serialnolen; /* Length in octets of serialnumber. */
+ unsigned int card_version;
+ int did_chv1;
+ int did_chv2;
+ int did_chv3;
+ struct {
+ int (*learn_status) (APP app, CTRL ctrl);
+ int (*setattr) (APP app, const char *name,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const unsigned char *value, size_t valuelen);
+ int (*sign) (APP app,
+ const char *keyidstr, int hashalgo,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen );
+ int (*auth) (APP app, const char *keyidstr,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen);
+ int (*decipher) (APP app, const char *keyidstr,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen);
+ int (*genkey) (APP app, CTRL ctrl,
+ const char *keynostr, unsigned int flags,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg);
+ int (*change_pin) (APP app, CTRL ctrl,
+ const char *chvnostr, int reset_mode,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg);
+ } fnc;
+
+
+};
+
+/*-- app.c --*/
+APP select_application (void);
+int app_get_serial_and_stamp (APP app, char **serial, time_t *stamp);
+int app_write_learn_status (APP app, CTRL ctrl);
+int app_setattr (APP app, const char *name,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const unsigned char *value, size_t valuelen);
+int app_sign (APP app, const char *keyidstr, int hashalgo,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen );
+int app_auth (APP app, const char *keyidstr,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen);
+int app_decipher (APP app, const char *keyidstr,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen );
+int app_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg);
+int app_get_challenge (APP app, size_t nbytes, unsigned char *buffer);
+int app_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg);
+
+
+/*-- app-openpgp.c --*/
+int app_select_openpgp (APP app, unsigned char **sn, size_t *snlen);
+
+int app_openpgp_cardinfo (APP app,
+ char **serialno,
+ char **disp_name,
+ char **pubkey_url,
+ unsigned char **fpr1,
+ unsigned char **fpr2,
+ unsigned char **fpr3);
+int app_openpgp_storekey (APP app, int keyno,
+ unsigned char *template, size_t template_len,
+ time_t created_at,
+ const unsigned char *m, size_t mlen,
+ const unsigned char *e, size_t elen,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg);
+int app_openpgp_readkey (APP app, int keyno,
+ unsigned char **m, size_t *mlen,
+ unsigned char **e, size_t *elen);
+
+
+#endif /*GNUPG_SCD_APP_COMMON_H*/
+
+
+
diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c
new file mode 100644
index 000000000..09a19699d
--- /dev/null
+++ b/scd/app-openpgp.c
@@ -0,0 +1,1482 @@
+/* app-openpgp.c - The OpenPGP card application.
+ * Copyright (C) 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "scdaemon.h"
+#include "app-common.h"
+#include "iso7816.h"
+
+
+
+static struct {
+ int tag;
+ int constructed;
+ int get_from; /* Constructed DO with this DO or 0 for direct access. */
+ int binary;
+ char *desc;
+} data_objects[] = {
+ { 0x005E, 0, 0, 1, "Login Data" },
+ { 0x5F50, 0, 0, 0, "URL" },
+ { 0x0065, 1, 0, 1, "Cardholder Related Data"},
+ { 0x005B, 0, 0x65, 0, "Name" },
+ { 0x5F2D, 0, 0x65, 0, "Language preferences" },
+ { 0x5F35, 0, 0x65, 0, "Sex" },
+ { 0x006E, 1, 0, 1, "Application Related Data" },
+ { 0x004F, 0, 0x6E, 1, "AID" },
+ { 0x0073, 1, 0, 1, "Discretionary Data Objects" },
+ { 0x0047, 0, 0x6E, 1, "Card Capabilities" },
+ { 0x00C0, 0, 0x6E, 1, "Extended Card Capabilities" },
+ { 0x00C1, 0, 0x6E, 1, "Algorithm Attributes Signature" },
+ { 0x00C2, 0, 0x6E, 1, "Algorithm Attributes Decryption" },
+ { 0x00C3, 0, 0x6E, 1, "Algorithm Attributes Authentication" },
+ { 0x00C4, 0, 0x6E, 1, "CHV Status Bytes" },
+ { 0x00C5, 0, 0x6E, 1, "Fingerprints" },
+ { 0x00C6, 0, 0x6E, 1, "CA Fingerprints" },
+ { 0x007A, 1, 0, 1, "Security Support Template" },
+ { 0x0093, 0, 0x7A, 1, "Digital Signature Counter" },
+ { 0 }
+};
+
+
+static unsigned long get_sig_counter (APP app);
+
+
+/* Locate a TLV encoded data object in BUFFER of LENGTH and
+ return a pointer to value as well as its length in NBYTES. Return
+ NULL if it was not found. Note, that the function does not check
+ whether the value fits into the provided buffer.
+
+ FIXME: Move this to an extra file, it is mostly duplicated from card.c.
+*/
+static const unsigned char *
+find_tlv (const unsigned char *buffer, size_t length,
+ int tag, size_t *nbytes, int nestlevel)
+{
+ const unsigned char *s = buffer;
+ size_t n = length;
+ size_t len;
+ int this_tag;
+ int composite;
+
+ for (;;)
+ {
+ buffer = s;
+ if (n < 2)
+ return NULL; /* buffer definitely too short for tag and length. */
+ if (!*s || *s == 0xff)
+ { /* Skip optional filler between TLV objects. */
+ s++;
+ n--;
+ continue;
+ }
+ composite = !!(*s & 0x20);
+ if ((*s & 0x1f) == 0x1f)
+ { /* more tag bytes to follow */
+ s++;
+ n--;
+ if (n < 2)
+ return NULL; /* buffer definitely too short for tag and length. */
+ if ((*s & 0x1f) == 0x1f)
+ return NULL; /* We support only up to 2 bytes. */
+ this_tag = (s[-1] << 8) | (s[0] & 0x7f);
+ }
+ else
+ this_tag = s[0];
+ len = s[1];
+ s += 2; n -= 2;
+ if (len < 0x80)
+ ;
+ else if (len == 0x81)
+ { /* One byte length follows. */
+ if (!n)
+ return NULL; /* we expected 1 more bytes with the length. */
+ len = s[0];
+ s++; n--;
+ }
+ else if (len == 0x82)
+ { /* Two byte length follows. */
+ if (n < 2)
+ return NULL; /* we expected 2 more bytes with the length. */
+ len = (s[0] << 8) | s[1];
+ s += 2; n -= 2;
+ }
+ else
+ return NULL; /* APDU limit is 65535, thus it does not make
+ sense to assume longer length fields. */
+
+ if (composite && nestlevel < 100)
+ { /* Dive into this composite DO after checking for too deep
+ nesting. */
+ const unsigned char *tmp_s;
+ size_t tmp_len;
+
+ tmp_s = find_tlv (s, len, tag, &tmp_len, nestlevel+1);
+ if (tmp_s)
+ {
+ *nbytes = tmp_len;
+ return tmp_s;
+ }
+ }
+
+ if (this_tag == tag)
+ {
+ *nbytes = len;
+ return s;
+ }
+ if (len > n)
+ return NULL; /* buffer too short to skip to the next tag. */
+ s += len; n -= len;
+ }
+}
+
+
+/* Get the DO identified by TAG from the card in SLOT and return a
+ buffer with its content in RESULT and NBYTES. The return value is
+ NULL if not found or a pointer which must be used to release the
+ buffer holding value. */
+static void *
+get_one_do (int slot, int tag, unsigned char **result, size_t *nbytes)
+{
+ int rc, i;
+ unsigned char *buffer;
+ size_t buflen;
+ unsigned char *value;
+ size_t valuelen;
+
+ *result = NULL;
+ *nbytes = 0;
+ for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++)
+ ;
+
+ value = NULL;
+ rc = -1;
+ if (data_objects[i].tag && data_objects[i].get_from)
+ {
+ rc = iso7816_get_data (slot, data_objects[i].get_from,
+ &buffer, &buflen);
+ if (!rc)
+ {
+ const unsigned char *s;
+
+ s = find_tlv (buffer, buflen, tag, &valuelen, 0);
+ if (!s)
+ value = NULL; /* not found */
+ else if (valuelen > buflen - (s - buffer))
+ {
+ log_error ("warning: constructed DO too short\n");
+ value = NULL;
+ xfree (buffer); buffer = NULL;
+ }
+ else
+ value = buffer + (s - buffer);
+ }
+ }
+
+ if (!value) /* Not in a constructed DO, try simple. */
+ {
+ rc = iso7816_get_data (slot, tag, &buffer, &buflen);
+ if (!rc)
+ {
+ value = buffer;
+ valuelen = buflen;
+ }
+ }
+
+ if (!rc)
+ {
+ *nbytes = valuelen;
+ *result = value;
+ return buffer;
+ }
+ return NULL;
+}
+
+#if 0 /* not used */
+static void
+dump_one_do (int slot, int tag)
+{
+ int rc, i;
+ unsigned char *buffer;
+ size_t buflen;
+ const char *desc;
+ int binary;
+ const unsigned char *value;
+ size_t valuelen;
+
+ for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++)
+ ;
+ desc = data_objects[i].tag? data_objects[i].desc : "?";
+ binary = data_objects[i].tag? data_objects[i].binary : 1;
+
+ value = NULL;
+ rc = -1;
+ if (data_objects[i].tag && data_objects[i].get_from)
+ {
+ rc = iso7816_get_data (slot, data_objects[i].get_from,
+ &buffer, &buflen);
+ if (!rc)
+ {
+ value = find_tlv (buffer, buflen, tag, &valuelen, 0);
+ if (!value)
+ ; /* not found */
+ else if (valuelen > buflen - (value - buffer))
+ {
+ log_error ("warning: constructed DO too short\n");
+ value = NULL;
+ xfree (buffer); buffer = NULL;
+ }
+ }
+ }
+
+ if (!value) /* Not in a constructed DO, try simple. */
+ {
+ rc = iso7816_get_data (slot, tag, &buffer, &buflen);
+ if (!rc)
+ {
+ value = buffer;
+ valuelen = buflen;
+ }
+ }
+ if (rc == 0x6a88)
+ log_info ("DO `%s' not available\n", desc);
+ else if (rc)
+ log_info ("DO `%s' not available (rc=%04X)\n", desc, rc);
+ else
+ {
+ if (binary)
+ {
+ log_info ("DO `%s': ", desc);
+ log_printhex ("", value, valuelen);
+ }
+ else
+ log_info ("DO `%s': `%.*s'\n",
+ desc, (int)valuelen, value); /* FIXME: sanitize */
+ xfree (buffer);
+ }
+}
+#endif /*not used*/
+
+
+static void
+dump_all_do (int slot)
+{
+ int rc, i, j;
+ unsigned char *buffer;
+ size_t buflen;
+
+ for (i=0; data_objects[i].tag; i++)
+ {
+ if (data_objects[i].get_from)
+ continue;
+
+ rc = iso7816_get_data (slot, data_objects[i].tag, &buffer, &buflen);
+ if (rc == 0x6a88)
+ ;
+ else if (rc)
+ log_info ("DO `%s' not available (rc=%04X)\n",
+ data_objects[i].desc, rc);
+ else
+ {
+ if (data_objects[i].binary)
+ {
+ log_info ("DO `%s': ", data_objects[i].desc);
+ log_printhex ("", buffer, buflen);
+ }
+ else
+ log_info ("DO `%s': `%.*s'\n",
+ data_objects[i].desc,
+ (int)buflen, buffer); /* FIXME: sanitize */
+ }
+
+ if (data_objects[i].constructed)
+ {
+ for (j=0; data_objects[j].tag; j++)
+ {
+ const unsigned char *value;
+ size_t valuelen;
+
+ if (j==i || data_objects[i].tag != data_objects[j].get_from)
+ continue;
+ value = find_tlv (buffer, buflen,
+ data_objects[j].tag, &valuelen, 0);
+ if (!value)
+ ; /* not found */
+ else if (valuelen > buflen - (value - buffer))
+ log_error ("warning: constructed DO too short\n");
+ else
+ {
+ if (data_objects[j].binary)
+ {
+ log_info ("DO `%s': ", data_objects[j].desc);
+ log_printhex ("", value, valuelen);
+ }
+ else
+ log_info ("DO `%s': `%.*s'\n",
+ data_objects[j].desc,
+ (int)valuelen, value); /* FIXME: sanitize */
+ }
+ }
+ }
+ xfree (buffer); buffer = NULL;
+ }
+}
+
+
+/* Count the number of bits, assuming the A represents an unsigned big
+ integer of length LEN bytes. */
+static unsigned int
+count_bits (const unsigned char *a, size_t len)
+{
+ unsigned int n = len * 8;
+ int i;
+
+ for (; len && !*a; len--, a++, n -=8)
+ ;
+ if (len)
+ {
+ for (i=7; i && !(*a & (1<<i)); i--)
+ n--;
+ }
+ return n;
+}
+
+/* Note, that FPR must be at least 20 bytes. */
+static int
+store_fpr (int slot, int keynumber, u32 timestamp,
+ const unsigned char *m, size_t mlen,
+ const unsigned char *e, size_t elen,
+ unsigned char *fpr, unsigned int card_version)
+{
+ unsigned int n, nbits;
+ unsigned char *buffer, *p;
+ int rc;
+
+ for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */
+ ;
+ for (; elen && !*e; elen--, e++) /* strip leading zeroes */
+ ;
+
+ n = 6 + 2 + mlen + 2 + elen;
+ p = buffer = xtrymalloc (3 + n);
+ if (!buffer)
+ return out_of_core ();
+
+ *p++ = 0x99; /* ctb */
+ *p++ = n >> 8; /* 2 byte length header */
+ *p++ = n;
+ *p++ = 4; /* key packet version */
+ *p++ = timestamp >> 24;
+ *p++ = timestamp >> 16;
+ *p++ = timestamp >> 8;
+ *p++ = timestamp;
+ *p++ = 1; /* RSA */
+ nbits = count_bits (m, mlen);
+ *p++ = nbits >> 8;
+ *p++ = nbits;
+ memcpy (p, m, mlen); p += mlen;
+ nbits = count_bits (e, elen);
+ *p++ = nbits >> 8;
+ *p++ = nbits;
+ memcpy (p, e, elen); p += elen;
+
+ log_printhex ("fprbuf:", buffer, n+3);
+ gcry_md_hash_buffer (GCRY_MD_SHA1, fpr, buffer, n+3);
+
+ xfree (buffer);
+
+ rc = iso7816_put_data (slot, (card_version > 0x0007? 0xC7 : 0xC6)
+ + keynumber, fpr, 20);
+ if (rc)
+ log_error ("failed to store the fingerprint: rc=%04X\n", rc);
+
+ return rc;
+}
+
+
+static void
+send_fpr_if_not_null (CTRL ctrl, const char *keyword,
+ int number, const unsigned char *fpr)
+{
+ int i;
+ char buf[41];
+ char numbuf[25];
+
+ for (i=0; i < 20 && !fpr[i]; i++)
+ ;
+ if (i==20)
+ return; /* All zero. */
+ for (i=0; i< 20; i++)
+ sprintf (buf+2*i, "%02X", fpr[i]);
+ if (number == -1)
+ *numbuf = 0; /* Don't print the key number */
+ else
+ sprintf (numbuf, "%d", number);
+ send_status_info (ctrl, keyword,
+ numbuf, (size_t)strlen(numbuf),
+ buf, (size_t)strlen (buf), NULL, 0);
+}
+
+static void
+send_key_data (CTRL ctrl, const char *name,
+ const unsigned char *a, size_t alen)
+{
+ char *p, *buf = xmalloc (alen*2+1);
+
+ for (p=buf; alen; a++, alen--, p += 2)
+ sprintf (p, "%02X", *a);
+
+ send_status_info (ctrl, "KEY-DATA",
+ name, (size_t)strlen(name),
+ buf, (size_t)strlen (buf),
+ NULL, 0);
+ xfree (buf);
+}
+
+
+
+static int
+do_learn_status (APP app, CTRL ctrl)
+{
+ void *relptr;
+ unsigned char *value;
+ size_t valuelen;
+ int i;
+
+ relptr = get_one_do (app->slot, 0x005B, &value, &valuelen);
+ if (relptr)
+ {
+ send_status_info (ctrl, "DISP-NAME", value, valuelen, NULL, 0);
+ xfree (relptr);
+ }
+ relptr = get_one_do (app->slot, 0x5F2D, &value, &valuelen);
+ if (relptr)
+ {
+ send_status_info (ctrl, "DISP-LANG", value, valuelen, NULL, 0);
+ xfree (relptr);
+ }
+ relptr = get_one_do (app->slot, 0x5F35, &value, &valuelen);
+ if (relptr)
+ {
+ send_status_info (ctrl, "DISP-SEX", value, valuelen, NULL, 0);
+ xfree (relptr);
+ }
+ relptr = get_one_do (app->slot, 0x5F50, &value, &valuelen);
+ if (relptr)
+ {
+ send_status_info (ctrl, "PUBKEY-URL", value, valuelen, NULL, 0);
+ xfree (relptr);
+ }
+ relptr = get_one_do (app->slot, 0x005E, &value, &valuelen);
+ if (relptr)
+ {
+ send_status_info (ctrl, "LOGIN-DATA", value, valuelen, NULL, 0);
+ xfree (relptr);
+ }
+
+ relptr = get_one_do (app->slot, 0x00C5, &value, &valuelen);
+ if (relptr && valuelen >= 60)
+ {
+ for (i=0; i < 3; i++)
+ send_fpr_if_not_null (ctrl, "KEY-FPR", i+1, value+i*20);
+ }
+ xfree (relptr);
+ relptr = get_one_do (app->slot, 0x00C6, &value, &valuelen);
+ if (relptr && valuelen >= 60)
+ {
+ for (i=0; i < 3; i++)
+ send_fpr_if_not_null (ctrl, "CA-FPR", i+1, value+i*20);
+ }
+ xfree (relptr);
+ relptr = get_one_do (app->slot, 0x00C4, &value, &valuelen);
+ if (relptr)
+ {
+ char numbuf[7*23];
+
+ for (i=0,*numbuf=0; i < valuelen && i < 7; i++)
+ sprintf (numbuf+strlen (numbuf), " %d", value[i]);
+ send_status_info (ctrl, "CHV-STATUS", numbuf, strlen (numbuf), NULL, 0);
+ xfree (relptr);
+ }
+
+ {
+ unsigned long ul = get_sig_counter (app);
+ char numbuf[23];
+
+ sprintf (numbuf, "%lu", ul);
+ send_status_info (ctrl, "SIG-COUNTER", numbuf, strlen (numbuf), NULL, 0);
+ }
+ return 0;
+}
+
+
+/* Handle the SETATTR operation. All arguments are already basically
+ checked. */
+static int
+do_setattr (APP app, const char *name,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const unsigned char *value, size_t valuelen)
+{
+ gpg_error_t rc;
+ int idx;
+ static struct {
+ const char *name;
+ int tag;
+ } table[] = {
+ { "DISP-NAME", 0x005B },
+ { "LOGIN-DATA", 0x005E },
+ { "DISP-LANG", 0x5F2D },
+ { "DISP-SEX", 0x5F35 },
+ { "PUBKEY-URL", 0x5F50 },
+ { "CHV-STATUS-1", 0x00C4 },
+ { "CA-FPR-1", 0x00CA },
+ { "CA-FPR-2", 0x00CB },
+ { "CA-FPR-3", 0x00CC },
+ { NULL, 0 }
+ };
+
+
+ for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++)
+ ;
+ if (!table[idx].name)
+ return gpg_error (GPG_ERR_INV_NAME);
+
+ if (!app->did_chv3)
+ {
+ char *pinvalue;
+
+ rc = pincb (pincb_arg, "Admin PIN (CHV3)",
+ &pinvalue);
+/* pinvalue = xstrdup ("12345678"); */
+/* rc = 0; */
+ if (rc)
+ {
+ log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+
+ rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV3 failed\n");
+ rc = gpg_error (GPG_ERR_GENERAL);
+ return rc;
+ }
+ app->did_chv3 = 1;
+ }
+
+ rc = iso7816_put_data (app->slot, table[idx].tag, value, valuelen);
+ if (rc)
+ log_error ("failed to set `%s': %s\n", table[idx].name, gpg_strerror (rc));
+ /* FIXME: If this fails we should *once* try again after
+ doing a verify command, so that in case of a problem with
+ tracking the verify operation we have a fallback. */
+
+ return rc;
+}
+
+/* Handle the PASSWD command. */
+static int
+do_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg)
+{
+ int rc = 0;
+ int chvno = atoi (chvnostr);
+ char *pinvalue;
+
+ if (reset_mode && chvno == 3)
+ {
+ rc = gpg_error (GPG_ERR_INV_ID);
+ goto leave;
+ }
+ else if (reset_mode || chvno == 3)
+ {
+ rc = pincb (pincb_arg, "Admin PIN", &pinvalue);
+ if (rc)
+ {
+ log_error ("error getting PIN: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV3 failed: rc=%04X\n", rc);
+ goto leave;
+ }
+ }
+ else if (chvno == 1)
+ {
+ rc = pincb (pincb_arg, "Signature PIN", &pinvalue);
+ if (rc)
+ {
+ log_error ("error getting PIN: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV1 failed: rc=%04X\n", rc);
+ goto leave;
+ }
+ }
+ else if (chvno == 2)
+ {
+ rc = pincb (pincb_arg, "Decryption PIN", &pinvalue);
+ if (rc)
+ {
+ log_error ("error getting PIN: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV2 failed: rc=%04X\n", rc);
+ goto leave;
+ }
+ }
+ else
+ {
+ rc = gpg_error (GPG_ERR_INV_ID);
+ goto leave;
+ }
+
+
+ rc = pincb (pincb_arg, chvno == 1? "New Signature PIN" :
+ chvno == 2? "New Decryption PIN" :
+ chvno == 3? "New Admin PIN" : "?", &pinvalue);
+ if (rc)
+ {
+ log_error ("error getting new PIN: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ if (reset_mode)
+ rc = iso7816_reset_retry_counter (app->slot, 0x80 + chvno,
+ pinvalue, strlen (pinvalue));
+ else
+ rc = iso7816_change_reference_data (app->slot, 0x80 + chvno,
+ NULL, 0,
+ pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+
+
+ leave:
+ return rc;
+}
+
+
+
+/* Handle the GENKEY command. */
+static int
+do_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg)
+{
+ int rc;
+ int i;
+ char numbuf[30];
+ unsigned char fprbuf[20];
+ const unsigned char *fpr;
+ const unsigned char *keydata, *m, *e;
+ unsigned char *buffer;
+ size_t buflen, keydatalen, n, mlen, elen;
+ time_t created_at;
+ int keyno = atoi (keynostr);
+ int force = (flags & 1);
+ time_t start_at;
+
+ if (keyno < 1 || keyno > 3)
+ return gpg_error (GPG_ERR_INV_ID);
+ keyno--;
+
+ rc = iso7816_get_data (app->slot, 0x006E, &buffer, &buflen);
+ if (rc)
+ {
+ log_error ("error reading application data\n");
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+ fpr = find_tlv (buffer, buflen, 0x00C5, &n, 0);
+ if (!fpr || n != 60)
+ {
+ rc = gpg_error (GPG_ERR_GENERAL);
+ log_error ("error reading fingerprint DO\n");
+ goto leave;
+ }
+ fpr += 20*keyno;
+ for (i=0; i < 20 && !fpr[i]; i++)
+ ;
+ if (i!=20 && !force)
+ {
+ rc = gpg_error (GPG_ERR_EEXIST);
+ log_error ("key already exists\n");
+ goto leave;
+ }
+ else if (i!=20)
+ log_info ("existing key will be replaced\n");
+ else
+ log_info ("generating new key\n");
+
+ {
+ char *pinvalue;
+ rc = pincb (pincb_arg, "Admin PIN", &pinvalue);
+ if (rc)
+ {
+ log_error ("error getting PIN: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+ rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ }
+ if (rc)
+ {
+ log_error ("verify CHV3 failed: rc=%04X\n", rc);
+ goto leave;
+ }
+
+ xfree (buffer); buffer = NULL;
+#if 1
+ log_info ("please wait while key is being generated ...\n");
+ start_at = time (NULL);
+ rc = iso7816_generate_keypair
+#else
+#warning key generation temporary replaced by reading an existing key.
+ rc = iso7816_read_public_key
+#endif
+ (app->slot,
+ keyno == 0? "\xB6" :
+ keyno == 1? "\xB8" : "\xA4",
+ 2,
+ &buffer, &buflen);
+ if (rc)
+ {
+ rc = gpg_error (GPG_ERR_CARD);
+ log_error ("generating key failed\n");
+ goto leave;
+ }
+ log_info ("key generation completed (%d seconds)\n",
+ (int)(time (NULL) - start_at));
+ keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen, 0);
+ if (!keydata)
+ {
+ rc = gpg_error (GPG_ERR_CARD);
+ log_error ("response does not contain the public key data\n");
+ goto leave;
+ }
+
+ m = find_tlv (keydata, keydatalen, 0x0081, &mlen, 0);
+ if (!m)
+ {
+ rc = gpg_error (GPG_ERR_CARD);
+ log_error ("response does not contain the RSA modulus\n");
+ goto leave;
+ }
+/* log_printhex ("RSA n:", m, mlen); */
+ send_key_data (ctrl, "n", m, mlen);
+
+ e = find_tlv (keydata, keydatalen, 0x0082, &elen, 0);
+ if (!e)
+ {
+ rc = gpg_error (GPG_ERR_CARD);
+ log_error ("response does not contain the RSA public exponent\n");
+ goto leave;
+ }
+/* log_printhex ("RSA e:", e, elen); */
+ send_key_data (ctrl, "e", e, elen);
+
+ created_at = gnupg_get_time ();
+ sprintf (numbuf, "%lu", (unsigned long)created_at);
+ send_status_info (ctrl, "KEY-CREATED-AT",
+ numbuf, (size_t)strlen(numbuf), NULL, 0);
+
+ rc = store_fpr (app->slot, keyno, (u32)created_at,
+ m, mlen, e, elen, fprbuf, app->card_version);
+ if (rc)
+ goto leave;
+ send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf);
+
+
+ leave:
+ xfree (buffer);
+ return rc;
+}
+
+
+static unsigned long
+get_sig_counter (APP app)
+{
+ void *relptr;
+ unsigned char *value;
+ size_t valuelen;
+ unsigned long ul;
+
+ relptr = get_one_do (app->slot, 0x0093, &value, &valuelen);
+ if (!relptr)
+ return 0;
+ if (valuelen == 3 )
+ ul = (value[0] << 16) | (value[1] << 8) | value[2];
+ else
+ {
+ log_error ("invalid structure of OpenPGP card (DO 0x93)\n");
+ ul = 0;
+ }
+ xfree (relptr);
+ return ul;
+}
+
+static int
+compare_fingerprint (APP app, int keyno, unsigned char *sha1fpr)
+{
+ const unsigned char *fpr;
+ unsigned char *buffer;
+ size_t buflen, n;
+ int rc, i;
+
+ assert (keyno >= 1 && keyno <= 3);
+
+ rc = iso7816_get_data (app->slot, 0x006E, &buffer, &buflen);
+ if (rc)
+ {
+ log_error ("error reading application data\n");
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+ fpr = find_tlv (buffer, buflen, 0x00C5, &n, 0);
+ if (!fpr || n != 60)
+ {
+ xfree (buffer);
+ log_error ("error reading fingerprint DO\n");
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+ fpr += (keyno-1)*20;
+ for (i=0; i < 20; i++)
+ if (sha1fpr[i] != fpr[i])
+ {
+ xfree (buffer);
+ return gpg_error (GPG_ERR_WRONG_SECKEY);
+ }
+ xfree (buffer);
+ return 0;
+}
+
+
+
+/* Compute a digital signature on INDATA which is expected to be the
+ raw message digest. For this application the KEYIDSTR consists of
+ the serialnumber and the fingerprint delimited by a slash.
+
+ Note that this fucntion may return the error code
+ GPG_ERR_WRONG_CARD to indicate that the card currently present does
+ not match the one required for the requested action (e.g. the
+ serial number does not match). */
+static int
+do_sign (APP app, const char *keyidstr, int hashalgo,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen )
+{
+ static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */
+ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
+ 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
+ static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
+ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
+ 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
+ int rc;
+ unsigned char data[35];
+ unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */
+ const char *s;
+ int n;
+ const char *fpr = NULL;
+ unsigned long sigcount;
+
+ if (!keyidstr || !*keyidstr)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (indatalen != 20)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ /* Check whether an OpenPGP card of any version has been requested. */
+ if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (n != 32)
+ return gpg_error (GPG_ERR_INV_ID);
+ else if (!*s)
+ ; /* no fingerprint given: we allow this for now. */
+ else if (*s == '/')
+ fpr = s + 1;
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=keyidstr, n=0; n < 16; s += 2, n++)
+ tmp_sn[n] = xtoi_2 (s);
+
+ if (app->serialnolen != 16)
+ return gpg_error (GPG_ERR_INV_CARD);
+ if (memcmp (app->serialno, tmp_sn, 16))
+ return gpg_error (GPG_ERR_WRONG_CARD);
+
+ /* If a fingerprint has been specified check it against the one on
+ the card. This is allows for a meaningful error message in case
+ the key on the card has been replaced but the shadow information
+ known to gpg was not updated. If there is no fingerprint, gpg
+ will detect a bogus signature anyway due to the
+ verify-after-signing feature. */
+ if (fpr)
+ {
+ for (s=fpr, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (n != 40)
+ return gpg_error (GPG_ERR_INV_ID);
+ else if (!*s)
+ ; /* okay */
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=fpr, n=0; n < 20; s += 2, n++)
+ tmp_sn[n] = xtoi_2 (s);
+ rc = compare_fingerprint (app, 1, tmp_sn);
+ if (rc)
+ return rc;
+ }
+
+ if (hashalgo == GCRY_MD_SHA1)
+ memcpy (data, sha1_prefix, 15);
+ else if (hashalgo == GCRY_MD_RMD160)
+ memcpy (data, rmd160_prefix, 15);
+ else
+ return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+ memcpy (data+15, indata, indatalen);
+
+ sigcount = get_sig_counter (app);
+ log_info ("signatures created so far: %lu\n", sigcount);
+
+ /* FIXME: Check whether we are really required to enter the PIN for
+ each signature. There is a DO for this. */
+ if (!app->did_chv1 || 1)
+ {
+ char *pinvalue;
+
+ {
+ char *prompt;
+ if (asprintf (&prompt, "Signature PIN [sigs done: %lu]", sigcount) < 0)
+ return gpg_error_from_errno (errno);
+ rc = pincb (pincb_arg, prompt, &pinvalue);
+ free (prompt);
+ }
+/* pinvalue = xstrdup ("123456"); */
+/* rc = 0; */
+ if (rc)
+ {
+ log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+
+ rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV1 failed\n");
+ rc = gpg_error (GPG_ERR_GENERAL);
+ return rc;
+ }
+ app->did_chv1 = 1;
+ }
+
+ rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen);
+ return rc;
+}
+
+/* Compute a digital signature using the INTERNAL AUTHENTICATE command
+ on INDATA which is expected to be the raw message digest. For this
+ application the KEYIDSTR consists of the serialnumber and the
+ fingerprint delimited by a slash.
+
+ Note that this fucntion may return the error code
+ GPG_ERR_WRONG_CARD to indicate that the card currently present does
+ not match the one required for the requested action (e.g. the
+ serial number does not match). */
+static int
+do_auth (APP app, const char *keyidstr,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen )
+{
+ int rc;
+ unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */
+ const char *s;
+ int n;
+ const char *fpr = NULL;
+
+ if (!keyidstr || !*keyidstr)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (indatalen > 50) /* For a 1024 bit key. */
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ /* Check whether an OpenPGP card of any version has been requested. */
+ if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (n != 32)
+ return gpg_error (GPG_ERR_INV_ID);
+ else if (!*s)
+ ; /* no fingerprint given: we allow this for now. */
+ else if (*s == '/')
+ fpr = s + 1;
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=keyidstr, n=0; n < 16; s += 2, n++)
+ tmp_sn[n] = xtoi_2 (s);
+
+ if (app->serialnolen != 16)
+ return gpg_error (GPG_ERR_INV_CARD);
+ if (memcmp (app->serialno, tmp_sn, 16))
+ return gpg_error (GPG_ERR_WRONG_CARD);
+
+ /* If a fingerprint has been specified check it against the one on
+ the card. This is allows for a meaningful error message in case
+ the key on the card has been replaced but the shadow information
+ known to gpg was not updated. If there is no fingerprint, gpg
+ will detect a bogus signature anyway due to the
+ verify-after-signing feature. */
+ if (fpr)
+ {
+ for (s=fpr, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (n != 40)
+ return gpg_error (GPG_ERR_INV_ID);
+ else if (!*s)
+ ; /* okay */
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=fpr, n=0; n < 20; s += 2, n++)
+ tmp_sn[n] = xtoi_2 (s);
+ rc = compare_fingerprint (app, 3, tmp_sn);
+ if (rc)
+ return rc;
+ }
+
+ if (!app->did_chv2)
+ {
+ char *pinvalue;
+
+ rc = pincb (pincb_arg, "Authentication/Decryption PIN", &pinvalue);
+ if (rc)
+ {
+ log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+
+ rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV2 failed\n");
+ rc = gpg_error (GPG_ERR_GENERAL);
+ return rc;
+ }
+ app->did_chv2 = 1;
+ }
+
+ rc = iso7816_internal_authenticate (app->slot, indata, indatalen,
+ outdata, outdatalen);
+ return rc;
+}
+
+
+static int
+do_decipher (APP app, const char *keyidstr,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen )
+{
+ int rc;
+ unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */
+ const char *s;
+ int n;
+ const char *fpr = NULL;
+
+ if (!keyidstr || !*keyidstr || !indatalen)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ /* Check whether an OpenPGP card of any version has been requested. */
+ if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12))
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (n != 32)
+ return gpg_error (GPG_ERR_INV_ID);
+ else if (!*s)
+ ; /* no fingerprint given: we allow this for now. */
+ else if (*s == '/')
+ fpr = s + 1;
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=keyidstr, n=0; n < 16; s += 2, n++)
+ tmp_sn[n] = xtoi_2 (s);
+
+ if (app->serialnolen != 16)
+ return gpg_error (GPG_ERR_INV_CARD);
+ if (memcmp (app->serialno, tmp_sn, 16))
+ return gpg_error (GPG_ERR_WRONG_CARD);
+
+ /* If a fingerprint has been specified check it against the one on
+ the card. This is allows for a meaningful error message in case
+ the key on the card has been replaced but the shadow information
+ known to gpg was not updated. If there is no fingerprint, the
+ decryption will won't produce the right plaintext anyway. */
+ if (fpr)
+ {
+ for (s=fpr, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (n != 40)
+ return gpg_error (GPG_ERR_INV_ID);
+ else if (!*s)
+ ; /* okay */
+ else
+ return gpg_error (GPG_ERR_INV_ID);
+
+ for (s=fpr, n=0; n < 20; s += 2, n++)
+ tmp_sn[n] = xtoi_2 (s);
+ rc = compare_fingerprint (app, 2, tmp_sn);
+ if (rc)
+ return rc;
+ }
+
+ if (!app->did_chv2)
+ {
+ char *pinvalue;
+
+ rc = pincb (pincb_arg, "Decryption PIN", &pinvalue);
+/* pinvalue = xstrdup ("123456"); */
+/* rc = 0; */
+ if (rc)
+ {
+ log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+
+ rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_error ("verify CHV2 failed\n");
+ rc = gpg_error (GPG_ERR_GENERAL);
+ return rc;
+ }
+ app->did_chv2 = 1;
+ }
+
+ rc = iso7816_decipher (app->slot, indata, indatalen, outdata, outdatalen);
+ return rc;
+}
+
+
+
+
+/* Select the OpenPGP application on the card in SLOT. This function
+ must be used before any other OpenPGP application functions. */
+int
+app_select_openpgp (APP app, unsigned char **sn, size_t *snlen)
+{
+ static char const aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 };
+ int slot = app->slot;
+ int rc;
+ unsigned char *buffer;
+ size_t buflen;
+
+ rc = iso7816_select_application (slot, aid, sizeof aid);
+ if (!rc)
+ {
+ /* fixme: get the full AID and check that the version is okay
+ with us. */
+ rc = iso7816_get_data (slot, 0x004F, &buffer, &buflen);
+ if (rc)
+ goto leave;
+ if (opt.verbose)
+ {
+ log_info ("got AID: ");
+ log_printhex ("", buffer, buflen);
+ }
+
+ if (sn)
+ {
+ *sn = buffer;
+ *snlen = buflen;
+ app->card_version = buffer[6] << 8;
+ app->card_version |= buffer[7];
+ }
+ else
+ xfree (buffer);
+
+ if (opt.verbose > 1)
+ dump_all_do (slot);
+
+ app->fnc.learn_status = do_learn_status;
+ app->fnc.setattr = do_setattr;
+ app->fnc.genkey = do_genkey;
+ app->fnc.sign = do_sign;
+ app->fnc.auth = do_auth;
+ app->fnc.decipher = do_decipher;
+ app->fnc.change_pin = do_change_pin;
+ }
+
+leave:
+ return rc;
+}
+
+
+
+/* This function is a hack to retrieve essential information about the
+ card to be displayed by simple tools. It mostly resembles what the
+ LEARN command returns. All parameters return allocated strings or
+ buffers or NULL if the data object is not available. All returned
+ values are sanitized. */
+int
+app_openpgp_cardinfo (APP app,
+ char **serialno,
+ char **disp_name,
+ char **pubkey_url,
+ unsigned char **fpr1,
+ unsigned char **fpr2,
+ unsigned char **fpr3)
+{
+ int rc;
+ void *relptr;
+ unsigned char *value;
+ size_t valuelen;
+
+ if (serialno)
+ {
+ time_t dummy;
+
+ *serialno = NULL;
+ rc = app_get_serial_and_stamp (app, serialno, &dummy);
+ if (rc)
+ {
+ log_error ("error getting serial number: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+ }
+
+ if (disp_name)
+ {
+ *disp_name = NULL;
+ relptr = get_one_do (app->slot, 0x005B, &value, &valuelen);
+ if (relptr)
+ {
+ *disp_name = make_printable_string (value, valuelen, 0);
+ xfree (relptr);
+ }
+ }
+
+ if (pubkey_url)
+ {
+ *pubkey_url = NULL;
+ relptr = get_one_do (app->slot, 0x5F50, &value, &valuelen);
+ if (relptr)
+ {
+ *pubkey_url = make_printable_string (value, valuelen, 0);
+ xfree (relptr);
+ }
+ }
+
+ if (fpr1)
+ *fpr1 = NULL;
+ if (fpr2)
+ *fpr2 = NULL;
+ if (fpr3)
+ *fpr3 = NULL;
+ relptr = get_one_do (app->slot, 0x00C5, &value, &valuelen);
+ if (relptr && valuelen >= 60)
+ {
+ if (fpr1)
+ {
+ *fpr1 = xmalloc (20);
+ memcpy (*fpr1, value + 0, 20);
+ }
+ if (fpr2)
+ {
+ *fpr2 = xmalloc (20);
+ memcpy (*fpr2, value + 20, 20);
+ }
+ if (fpr3)
+ {
+ *fpr3 = xmalloc (20);
+ memcpy (*fpr3, value + 40, 20);
+ }
+ }
+ xfree (relptr);
+
+ return 0;
+}
+
+
+
+/* This function is currently only used by the sc-copykeys program to
+ store a key on the smartcard. APP ist the application handle,
+ KEYNO is the number of the key and PINCB, PINCB_ARG are used to ask
+ for the SO PIN. TEMPLATE and TEMPLATE_LEN describe a buffer with
+ the key template to store. CREATED_AT is the timestamp used to
+ create the fingerprint. M, MLEN is the RSA modulus and E, ELEN the
+ RSA public exponent. This function silently overwrites an existing
+ key.*/
+int
+app_openpgp_storekey (APP app, int keyno,
+ unsigned char *template, size_t template_len,
+ time_t created_at,
+ const unsigned char *m, size_t mlen,
+ const unsigned char *e, size_t elen,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg)
+{
+ int rc;
+ unsigned char fprbuf[20];
+
+ if (keyno < 1 || keyno > 3)
+ return gpg_error (GPG_ERR_INV_ID);
+ keyno--;
+
+ {
+ char *pinvalue;
+ rc = pincb (pincb_arg, "Admin PIN", &pinvalue);
+ if (rc)
+ {
+ log_error ("error getting PIN: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+ rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ }
+ if (rc)
+ {
+ log_error ("verify CHV3 failed: rc=%04X\n", rc);
+ goto leave;
+ }
+
+ rc = iso7816_put_data (app->slot,
+ (app->card_version > 0x0007? 0xE0 : 0xE9) + keyno,
+ template, template_len);
+ if (rc)
+ {
+ log_error ("failed to store the key: rc=%04X\n", rc);
+ rc = gpg_error (GPG_ERR_CARD);
+ goto leave;
+ }
+
+/* log_printhex ("RSA n:", m, mlen); */
+/* log_printhex ("RSA e:", e, elen); */
+
+ rc = store_fpr (app->slot, keyno, (u32)created_at,
+ m, mlen, e, elen, fprbuf, app->card_version);
+
+ leave:
+ return rc;
+}
+
+
+/* Utility function for external tools: Read the public RSA key at
+ KEYNO and return modulus and exponent in (M,MLEN) and (E,ELEN). */
+int
+app_openpgp_readkey (APP app, int keyno, unsigned char **m, size_t *mlen,
+ unsigned char **e, size_t *elen)
+{
+ int rc;
+ const unsigned char *keydata, *a;
+ unsigned char *buffer;
+ size_t buflen, keydatalen, alen;
+
+ *m = NULL;
+ *e = NULL;
+
+ if (keyno < 1 || keyno > 3)
+ return gpg_error (GPG_ERR_INV_ID);
+ keyno--;
+
+ rc = iso7816_read_public_key(app->slot,
+ keyno == 0? "\xB6" :
+ keyno == 1? "\xB8" : "\xA4",
+ 2,
+ &buffer, &buflen);
+ if (rc)
+ {
+ rc = gpg_error (GPG_ERR_CARD);
+ log_error ("reading key failed\n");
+ goto leave;
+ }
+
+ keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen, 0);
+ if (!keydata)
+ {
+ log_error ("response does not contain the public key data\n");
+ rc = gpg_error (GPG_ERR_CARD);
+ goto leave;
+ }
+
+ a = find_tlv (keydata, keydatalen, 0x0081, &alen, 0);
+ if (!a)
+ {
+ log_error ("response does not contain the RSA modulus\n");
+ rc = gpg_error (GPG_ERR_CARD);
+ goto leave;
+ }
+ *mlen = alen;
+ *m = xmalloc (alen);
+ memcpy (*m, a, alen);
+
+ a = find_tlv (keydata, keydatalen, 0x0082, &alen, 0);
+ if (!e)
+ {
+ log_error ("response does not contain the RSA public exponent\n");
+ rc = gpg_error (GPG_ERR_CARD);
+ goto leave;
+ }
+ *elen = alen;
+ *e = xmalloc (alen);
+ memcpy (*e, a, alen);
+
+ leave:
+ xfree (buffer);
+ if (rc)
+ {
+ xfree (*m); *m = NULL;
+ xfree (*e); *e = NULL;
+ }
+ return rc;
+}
diff --git a/scd/app.c b/scd/app.c
new file mode 100644
index 000000000..7a85df336
--- /dev/null
+++ b/scd/app.c
@@ -0,0 +1,278 @@
+/* app.c - Application selection.
+ * Copyright (C) 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dlfcn.h>
+
+#include "scdaemon.h"
+#include "app-common.h"
+#include "apdu.h"
+#include "iso7816.h"
+
+/* The select the best fitting application and return a context.
+ Returns NULL if no application was found or no card is present. */
+APP
+select_application (void)
+{
+ int reader_port = 32768; /* First USB reader. */
+ int slot;
+ int rc;
+ APP app;
+
+ slot = apdu_open_reader (reader_port);
+ if (slot == -1)
+ {
+ log_error ("card reader not available\n");
+ return NULL;
+ }
+
+ app = xtrycalloc (1, sizeof *app);
+ if (!app)
+ {
+ rc = out_of_core ();
+ log_info ("error allocating context: %s\n", gpg_strerror (rc));
+ /*apdu_close_reader (slot);*/
+ return NULL;
+ }
+
+ app->slot = slot;
+ rc = app_select_openpgp (app, &app->serialno, &app->serialnolen);
+ if (rc)
+ {
+/* apdu_close_reader (slot); */
+ log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc));
+ xfree (app);
+ return NULL;
+ }
+
+ app->initialized = 1;
+ return app;
+}
+
+
+
+/* Retrieve the serial number and the time of the last update of the
+ card. The serial number is returned as a malloced string (hex
+ encoded) in SERIAL and the time of update is returned in STAMP. If
+ no update time is available the returned value is 0. Caller must
+ free SERIAL unless the function returns an error. */
+int
+app_get_serial_and_stamp (APP app, char **serial, time_t *stamp)
+{
+ unsigned char *buf, *p;
+ int i;
+
+ if (!app || !serial || !stamp)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ *serial = NULL;
+ *stamp = 0; /* not available */
+
+ buf = xtrymalloc (app->serialnolen * 2 + 1);
+ if (!buf)
+ return gpg_error_from_errno (errno);
+ for (p=buf, i=0; i < app->serialnolen; p +=2, i++)
+ sprintf (p, "%02X", app->serialno[i]);
+ *p = 0;
+ *serial = buf;
+ return 0;
+}
+
+
+/* Write out the application specifig status lines for the LEARN
+ command. */
+int
+app_write_learn_status (APP app, CTRL ctrl)
+{
+ if (!app)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!app->initialized)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ if (!app->fnc.learn_status)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ return app->fnc.learn_status (app, ctrl);
+}
+
+
+/* Perform a SETATTR operation. */
+int
+app_setattr (APP app, const char *name,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const unsigned char *value, size_t valuelen)
+{
+ if (!app || !name || !*name || !value)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!app->initialized)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ if (!app->fnc.setattr)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ return app->fnc.setattr (app, name, pincb, pincb_arg, value, valuelen);
+}
+
+/* Create the signature and return the allocated result in OUTDATA.
+ If a PIN is required the PINCB will be used to ask for the PIN; it
+ should return the PIN in an allocated buffer and put it into PIN. */
+int
+app_sign (APP app, const char *keyidstr, int hashalgo,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen )
+{
+ int rc;
+
+ if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!app->initialized)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ if (!app->fnc.sign)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ rc = app->fnc.sign (app, keyidstr, hashalgo,
+ pincb, pincb_arg,
+ indata, indatalen,
+ outdata, outdatalen);
+ if (opt.verbose)
+ log_info ("operation sign result: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+/* Create the signature using the INTERNAL AUTHENTICATE command and
+ return the allocated result in OUTDATA. If a PIN is required the
+ PINCB will be used to ask for the PIN; it should return the PIN in
+ an allocated buffer and put it into PIN. */
+int
+app_auth (APP app, const char *keyidstr,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen )
+{
+ int rc;
+
+ if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!app->initialized)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ if (!app->fnc.auth)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ rc = app->fnc.auth (app, keyidstr,
+ pincb, pincb_arg,
+ indata, indatalen,
+ outdata, outdatalen);
+ if (opt.verbose)
+ log_info ("operation auth result: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+
+/* Decrypt the data in INDATA and return the allocated result in OUTDATA.
+ If a PIN is required the PINCB will be used to ask for the PIN; it
+ should return the PIN in an allocated buffer and put it into PIN. */
+int
+app_decipher (APP app, const char *keyidstr,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen )
+{
+ int rc;
+
+ if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!app->initialized)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ if (!app->fnc.decipher)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ rc = app->fnc.decipher (app, keyidstr,
+ pincb, pincb_arg,
+ indata, indatalen,
+ outdata, outdatalen);
+ if (opt.verbose)
+ log_info ("operation decipher result: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+
+/* Perform a SETATTR operation. */
+int
+app_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg)
+{
+ int rc;
+
+ if (!app || !keynostr || !*keynostr || !pincb)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!app->initialized)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ if (!app->fnc.genkey)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ rc = app->fnc.genkey (app, ctrl, keynostr, flags, pincb, pincb_arg);
+ if (opt.verbose)
+ log_info ("operation genkey result: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+
+/* Perform a GET CHALLENGE operation. This fucntion is special as it
+ directly accesses the card without any application specific
+ wrapper. */
+int
+app_get_challenge (APP app, size_t nbytes, unsigned char *buffer)
+{
+ if (!app || !nbytes || !buffer)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!app->initialized)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ return iso7816_get_challenge (app->slot, nbytes, buffer);
+}
+
+
+
+/* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */
+int
+app_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode,
+ int (*pincb)(void*, const char *, char **),
+ void *pincb_arg)
+{
+ int rc;
+
+ if (!app || !chvnostr || !*chvnostr || !pincb)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!app->initialized)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ if (!app->fnc.change_pin)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ rc = app->fnc.change_pin (app, ctrl, chvnostr, reset_mode, pincb, pincb_arg);
+ if (opt.verbose)
+ log_info ("operation change_pin result: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+
+
+
+
+
diff --git a/scd/card-common.h b/scd/card-common.h
new file mode 100644
index 000000000..31f0dfe8f
--- /dev/null
+++ b/scd/card-common.h
@@ -0,0 +1,73 @@
+/* card-common.h - Common declarations for all card types
+ * Copyright (C) 2001, 2002 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef CARD_COMMON_H
+#define CARD_COMMON_H
+
+/* Declaration of private data structure used by card-p15.c */
+struct p15private_s;
+
+
+struct card_ctx_s {
+ int reader; /* used reader */
+ struct sc_context *ctx;
+ struct sc_card *scard;
+ struct sc_pkcs15_card *p15card; /* only if there is a pkcs15 application */
+ struct p15private_s *p15priv; /* private data used by card-p15.c */
+
+ struct {
+ int initialized; /* the card has been initialied and the function
+ pointers may be used. However for
+ unsupported operations the particular
+ function pointer is set to NULL */
+
+ int (*enum_keypairs) (CARD card, int idx,
+ unsigned char *keygrip, char **keyid);
+ int (*enum_certs) (CARD card, int idx, char **certid, int *certtype);
+ int (*read_cert) (CARD card, const char *certidstr,
+ unsigned char **cert, size_t *ncert);
+ int (*sign) (CARD card,
+ const char *keyidstr, int hashalgo,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen );
+ int (*decipher) (CARD card, const char *keyidstr,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen);
+ } fnc;
+
+};
+
+/*-- card.c --*/
+gpg_error_t map_sc_err (int rc);
+int card_help_get_keygrip (KsbaCert cert, unsigned char *array);
+
+/*-- card-15.c --*/
+void p15_release_private_data (CARD card);
+
+/* constructors */
+void card_p15_bind (CARD card);
+void card_dinsig_bind (CARD card);
+
+
+#endif /*CARD_COMMON_H*/
diff --git a/scd/card-p15.c b/scd/card-p15.c
new file mode 100644
index 000000000..3cf4ba519
--- /dev/null
+++ b/scd/card-p15.c
@@ -0,0 +1,502 @@
+/* card-p15.c - PKCS-15 based card access
+ * Copyright (C) 2002 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifdef HAVE_OPENSC
+#include <opensc/pkcs15.h>
+#include <ksba.h>
+
+#include "scdaemon.h"
+#include "card-common.h"
+
+
+struct p15private_s {
+ int n_prkey_rsa_objs;
+ struct sc_pkcs15_object *prkey_rsa_objs[32];
+ int n_cert_objs;
+ struct sc_pkcs15_object *cert_objs[32];
+};
+
+
+/* Allocate private data. */
+static int
+init_private_data (CARD card)
+{
+ struct p15private_s *priv;
+ int rc;
+
+ if (card->p15priv)
+ return 0; /* already done. */
+
+ priv = xtrycalloc (1, sizeof *priv);
+ if (!priv)
+ return out_of_core ();
+
+ /* OpenSC (0.7.0) is a bit strange in that the get_objects functions
+ tries to be a bit too clever and implicitly does an enumeration
+ which eventually leads to the fact that every call to this
+ fucntion returns one more macthing object. The old code in
+ p15_enum_keypairs assume that it would alwyas return the same
+ numer of objects and used this to figure out what the last object
+ enumerated is. We now do an enum_objects just once and keep it
+ in the private data. */
+ rc = sc_pkcs15_get_objects (card->p15card, SC_PKCS15_TYPE_PRKEY_RSA,
+ priv->prkey_rsa_objs,
+ DIM (priv->prkey_rsa_objs));
+ if (rc < 0)
+ {
+ log_error ("private keys enumeration failed: %s\n", sc_strerror (rc));
+ xfree (priv);
+ return gpg_error (GPG_ERR_CARD);
+ }
+ priv->n_prkey_rsa_objs = rc;
+
+ /* Read all certificate objects. */
+ rc = sc_pkcs15_get_objects (card->p15card, SC_PKCS15_TYPE_CERT_X509,
+ priv->cert_objs,
+ DIM (priv->cert_objs));
+ if (rc < 0)
+ {
+ log_error ("private keys enumeration failed: %s\n", sc_strerror (rc));
+ xfree (priv);
+ return gpg_error (GPG_ERR_CARD);
+ }
+ priv->n_cert_objs = rc;
+
+ card->p15priv = priv;
+ return 0;
+}
+
+
+/* Release private data used in this module. */
+void
+p15_release_private_data (CARD card)
+{
+ if (!card->p15priv)
+ return;
+ xfree (card->p15priv);
+ card->p15priv = NULL;
+}
+
+
+
+/* See card.c for interface description */
+static int
+p15_enum_keypairs (CARD card, int idx,
+ unsigned char *keygrip, char **keyid)
+{
+ int rc;
+ KsbaError krc;
+ struct p15private_s *priv;
+ struct sc_pkcs15_object *tmpobj;
+ int nobjs;
+ struct sc_pkcs15_prkey_info *pinfo;
+ struct sc_pkcs15_cert_info *certinfo;
+ struct sc_pkcs15_cert *certder;
+ KsbaCert cert;
+
+ rc = init_private_data (card);
+ if (rc)
+ return rc;
+ priv = card->p15priv;
+ nobjs = priv->n_prkey_rsa_objs;
+ rc = 0;
+ if (idx >= nobjs)
+ return -1;
+ pinfo = priv->prkey_rsa_objs[idx]->data;
+
+ /* now we need to read the certificate so that we can calculate the
+ keygrip */
+ rc = sc_pkcs15_find_cert_by_id (card->p15card, &pinfo->id, &tmpobj);
+ if (rc)
+ {
+ log_info ("certificate for private key %d not found: %s\n",
+ idx, sc_strerror (rc));
+ /* note, that we return the ID anyway */
+ rc = gpg_error (GPG_ERR_MISSING_CERTIFICATE);
+ goto return_keyid;
+ }
+ certinfo = tmpobj->data;
+ rc = sc_pkcs15_read_certificate (card->p15card, certinfo, &certder);
+ if (rc)
+ {
+ log_info ("failed to read certificate for private key %d: %s\n",
+ idx, sc_strerror (rc));
+ return gpg_error (GPG_ERR_CARD);
+ }
+
+ cert = ksba_cert_new ();
+ if (!cert)
+ {
+ gpg_error_t tmperr = out_of_core ();
+ sc_pkcs15_free_certificate (certder);
+ return tmperr;
+ }
+ krc = ksba_cert_init_from_mem (cert, certder->data, certder->data_len);
+ sc_pkcs15_free_certificate (certder);
+ if (krc)
+ {
+ log_error ("failed to parse the certificate for private key %d: %s\n",
+ idx, ksba_strerror (krc));
+ ksba_cert_release (cert);
+ return gpg_error (GPG_ERR_CARD);
+ }
+ if (card_help_get_keygrip (cert, keygrip))
+ {
+ log_error ("failed to calculate the keygrip of private key %d\n", idx);
+ ksba_cert_release (cert);
+ return gpg_error (GPG_ERR_CARD);
+ }
+ ksba_cert_release (cert);
+
+ rc = 0;
+ return_keyid:
+ if (keyid)
+ {
+ char *p;
+ int i;
+
+ *keyid = p = xtrymalloc (9+pinfo->id.len*2+1);
+ if (!*keyid)
+ return out_of_core ();
+ p = stpcpy (p, "P15-5015.");
+ for (i=0; i < pinfo->id.len; i++, p += 2)
+ sprintf (p, "%02X", pinfo->id.value[i]);
+ *p = 0;
+ }
+
+ return rc;
+}
+
+/* See card.c for interface description */
+static int
+p15_enum_certs (CARD card, int idx, char **certid, int *type)
+{
+ int rc;
+ struct p15private_s *priv;
+ struct sc_pkcs15_object *obj;
+ struct sc_pkcs15_cert_info *cinfo;
+ int nobjs;
+
+ rc = init_private_data (card);
+ if (rc)
+ return rc;
+ priv = card->p15priv;
+ nobjs = priv->n_cert_objs;
+ rc = 0;
+ if (idx >= nobjs)
+ return -1;
+ obj = priv->cert_objs[idx];
+ cinfo = obj->data;
+
+ if (certid)
+ {
+ char *p;
+ int i;
+
+ *certid = p = xtrymalloc (9+cinfo->id.len*2+1);
+ if (!*certid)
+ return out_of_core ();
+ p = stpcpy (p, "P15-5015.");
+ for (i=0; i < cinfo->id.len; i++, p += 2)
+ sprintf (p, "%02X", cinfo->id.value[i]);
+ *p = 0;
+ }
+ if (type)
+ {
+ if (!obj->df)
+ *type = 0; /* unknown */
+ else if (obj->df->type == SC_PKCS15_CDF)
+ *type = 100;
+ else if (obj->df->type == SC_PKCS15_CDF_TRUSTED)
+ *type = 101;
+ else if (obj->df->type == SC_PKCS15_CDF_USEFUL)
+ *type = 102;
+ else
+ *type = 0; /* error -> unknown */
+ }
+
+ return rc;
+}
+
+
+
+static int
+idstr_to_id (const char *idstr, struct sc_pkcs15_id *id)
+{
+ const char *s;
+ int n;
+
+ /* For now we only support the standard DF */
+ if (strncmp (idstr, "P15-5015.", 9) )
+ return gpg_error (GPG_ERR_INV_ID);
+ for (s=idstr+9, n=0; hexdigitp (s); s++, n++)
+ ;
+ if (*s || (n&1))
+ return gpg_error (GPG_ERR_INV_ID); /*invalid or odd number of digits*/
+ n /= 2;
+ if (!n || n > SC_PKCS15_MAX_ID_SIZE)
+ return gpg_error (GPG_ERR_INV_ID); /* empty or too large */
+ for (s=idstr+9, n=0; *s; s += 2, n++)
+ id->value[n] = xtoi_2 (s);
+ id->len = n;
+ return 0;
+}
+
+
+/* See card.c for interface description */
+static int
+p15_read_cert (CARD card, const char *certidstr,
+ unsigned char **cert, size_t *ncert)
+{
+ struct sc_pkcs15_object *tmpobj;
+ struct sc_pkcs15_id certid;
+ struct sc_pkcs15_cert_info *certinfo;
+ struct sc_pkcs15_cert *certder;
+ int rc;
+
+ if (!card || !certidstr || !cert || !ncert)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!card->p15card)
+ return gpg_error (GPG_ERR_NO_PKCS15_APP);
+
+ rc = idstr_to_id (certidstr, &certid);
+ if (rc)
+ return rc;
+
+ rc = sc_pkcs15_find_cert_by_id (card->p15card, &certid, &tmpobj);
+ if (rc)
+ {
+ log_info ("certificate '%s' not found: %s\n",
+ certidstr, sc_strerror (rc));
+ return -1;
+ }
+ certinfo = tmpobj->data;
+ rc = sc_pkcs15_read_certificate (card->p15card, certinfo, &certder);
+ if (rc)
+ {
+ log_info ("failed to read certificate '%s': %s\n",
+ certidstr, sc_strerror (rc));
+ return gpg_error (GPG_ERR_CARD);
+ }
+
+ *cert = xtrymalloc (certder->data_len);
+ if (!*cert)
+ {
+ gpg_error_t tmperr = out_of_core ();
+ sc_pkcs15_free_certificate (certder);
+ return tmperr;
+ }
+ memcpy (*cert, certder->data, certder->data_len);
+ *ncert = certder->data_len;
+ sc_pkcs15_free_certificate (certder);
+ return 0;
+}
+
+
+
+
+
+static int
+p15_prepare_key (CARD card, const char *keyidstr,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg, struct sc_pkcs15_object **r_keyobj)
+{
+ struct sc_pkcs15_id keyid;
+ struct sc_pkcs15_pin_info *pin;
+ struct sc_pkcs15_object *keyobj, *pinobj;
+ char *pinvalue;
+ int rc;
+
+ rc = idstr_to_id (keyidstr, &keyid);
+ if (rc)
+ return rc;
+
+ rc = sc_pkcs15_find_prkey_by_id (card->p15card, &keyid, &keyobj);
+ if (rc < 0)
+ {
+ log_error ("private key not found: %s\n", sc_strerror(rc));
+ return gpg_error (GPG_ERR_NO_SECRET_KEY);
+ }
+
+ rc = sc_pkcs15_find_pin_by_auth_id (card->p15card,
+ &keyobj->auth_id, &pinobj);
+ if (rc)
+ {
+ log_error ("failed to find PIN by auth ID: %s\n", sc_strerror (rc));
+ return gpg_error (GPG_ERR_BAD_PIN_METHOD);
+ }
+ pin = pinobj->data;
+
+ /* Fixme: pack this into a verification loop */
+ /* Fixme: we might want to pass pin->min_length and
+ pin->stored_length */
+ rc = pincb (pincb_arg, pinobj->label, &pinvalue);
+ if (rc)
+ {
+ log_info ("PIN callback returned error: %s\n", gnupg_strerror (rc));
+ return rc;
+ }
+
+ rc = sc_pkcs15_verify_pin (card->p15card, pin,
+ pinvalue, strlen (pinvalue));
+ xfree (pinvalue);
+ if (rc)
+ {
+ log_info ("PIN verification failed: %s\n", sc_strerror (rc));
+ return gpg_error (GPG_ERR_BAD_PIN);
+ }
+
+ /* fixme: check wheter we need to release KEYOBJ in case of an error */
+ *r_keyobj = keyobj;
+ return 0;
+}
+
+
+/* See card.c for interface description */
+static int
+p15_sign (CARD card, const char *keyidstr, int hashalgo,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen )
+{
+ unsigned int cryptflags;
+ struct sc_pkcs15_object *keyobj;
+ int rc;
+ unsigned char *outbuf = NULL;
+ size_t outbuflen;
+
+ if (hashalgo != GCRY_MD_SHA1)
+ return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+
+ rc = p15_prepare_key (card, keyidstr, pincb, pincb_arg, &keyobj);
+ if (rc)
+ return rc;
+
+ cryptflags = SC_ALGORITHM_RSA_PAD_PKCS1;
+
+ outbuflen = 1024;
+ outbuf = xtrymalloc (outbuflen);
+ if (!outbuf)
+ return out_of_core ();
+
+ rc = sc_pkcs15_compute_signature (card->p15card, keyobj,
+ cryptflags,
+ indata, indatalen,
+ outbuf, outbuflen );
+ if (rc < 0)
+ {
+ log_error ("failed to create signature: %s\n", sc_strerror (rc));
+ rc = gpg_error (GPG_ERR_CARD);
+ }
+ else
+ {
+ *outdatalen = rc;
+ *outdata = outbuf;
+ outbuf = NULL;
+ rc = 0;
+ }
+
+ xfree (outbuf);
+ return rc;
+}
+
+
+/* See card.c for description */
+static int
+p15_decipher (CARD card, const char *keyidstr,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen )
+{
+ struct sc_pkcs15_object *keyobj;
+ int rc;
+ unsigned char *outbuf = NULL;
+ size_t outbuflen;
+
+ rc = p15_prepare_key (card, keyidstr, pincb, pincb_arg, &keyobj);
+ if (rc)
+ return rc;
+
+ if (card && card->scard && card->scard->driver
+ && !strcasecmp (card->scard->driver->short_name, "tcos"))
+ {
+ /* very ugly hack to force the use of a local key. We need this
+ until we have fixed the initialization code for TCOS cards */
+ struct sc_pkcs15_prkey_info *prkey = keyobj->data;
+ if ( !(prkey->key_reference & 0x80))
+ {
+ prkey->key_reference |= 0x80;
+ log_debug ("using TCOS hack to force the use of local keys\n");
+ }
+ if (*keyidstr && keyidstr[strlen(keyidstr)-1] == '6')
+ {
+ prkey->key_reference |= 1;
+ log_debug ("warning: using even more TCOS hacks\n");
+ }
+ }
+
+ outbuflen = indatalen < 256? 256 : indatalen;
+ outbuf = xtrymalloc (outbuflen);
+ if (!outbuf)
+ return out_of_core ();
+
+ rc = sc_pkcs15_decipher (card->p15card, keyobj,
+ 0,
+ indata, indatalen,
+ outbuf, outbuflen);
+ if (rc < 0)
+ {
+ log_error ("failed to decipher the data: %s\n", sc_strerror (rc));
+ rc = gpg_error (GPG_ERR_CARD);
+ }
+ else
+ {
+ *outdatalen = rc;
+ *outdata = outbuf;
+ outbuf = NULL;
+ rc = 0;
+ }
+
+ xfree (outbuf);
+ return rc;
+}
+
+
+
+/* Bind our operations to the card */
+void
+card_p15_bind (CARD card)
+{
+ card->fnc.enum_keypairs = p15_enum_keypairs;
+ card->fnc.enum_certs = p15_enum_certs;
+ card->fnc.read_cert = p15_read_cert;
+ card->fnc.sign = p15_sign;
+ card->fnc.decipher = p15_decipher;
+}
+#endif /*HAVE_OPENSC*/
diff --git a/scd/card.c b/scd/card.c
new file mode 100644
index 000000000..02b7bfdbf
--- /dev/null
+++ b/scd/card.c
@@ -0,0 +1,564 @@
+/* card.c - SCdaemon card functions
+ * Copyright (C) 2002 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifdef HAVE_OPENSC
+#include <opensc/pkcs15.h>
+#endif
+#include <ksba.h>
+
+#include "scdaemon.h"
+#include "card-common.h"
+
+/* Map the SC error codes to the GNUPG ones */
+gpg_error_t
+map_sc_err (int rc)
+{
+ gpg_err_code_t e;
+
+ switch (rc)
+ {
+ case 0: e = 0; break;
+#ifdef HAVE_OPENSC
+ case SC_ERROR_NOT_SUPPORTED: e = GPG_ERR_NOT_SUPPORTED; break;
+ case SC_ERROR_PKCS15_APP_NOT_FOUND: e = GPG_ERR_NO_PKCS15_APP; break;
+ case SC_ERROR_OUT_OF_MEMORY: e = GPG_ERR_ENOMEM; break;
+ case SC_ERROR_CARD_NOT_PRESENT: e = GPG_ERR_CARD_NOT_PRESENT; break;
+ case SC_ERROR_CARD_REMOVED: e = GPG_ERR_CARD_REMOVED; break;
+ case SC_ERROR_INVALID_CARD: e = GPG_ERR_INV_CARD; break;
+#endif
+ default: e = GPG_ERR_CARD; break;
+ }
+ return gpg_err_make (GPG_ERR_SOURCE_UNKNOWN, e);
+}
+
+/* Get the keygrip from CERT, return 0 on success */
+int
+card_help_get_keygrip (KsbaCert cert, unsigned char *array)
+{
+ gcry_sexp_t s_pkey;
+ int rc;
+ KsbaSexp p;
+ size_t n;
+
+ p = ksba_cert_get_public_key (cert);
+ if (!p)
+ return -1; /* oops */
+ n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+ if (!n)
+ return -1; /* libksba did not return a proper S-expression */
+ rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n);
+ xfree (p);
+ if (rc)
+ return -1; /* can't parse that S-expression */
+ array = gcry_pk_get_keygrip (s_pkey, array);
+ gcry_sexp_release (s_pkey);
+ if (!array)
+ return -1; /* failed to calculate the keygrip */
+ return 0;
+}
+
+
+
+
+
+
+
+/* Create a new context for the card and figures out some basic
+ information of the card. Detects whgether a PKCS_15 application is
+ stored.
+
+ Common errors: GPG_ERR_CARD_NOT_PRESENT */
+int
+card_open (CARD *rcard)
+{
+#ifdef HAVE_OPENSC
+ CARD card;
+ int rc;
+
+ card = xtrycalloc (1, sizeof *card);
+ if (!card)
+ return out_of_core ();
+ card->reader = 0;
+
+ rc = sc_establish_context (&card->ctx, "scdaemon");
+ if (rc)
+ {
+ log_error ("failed to establish SC context: %s\n", sc_strerror (rc));
+ rc = map_sc_err (rc);
+ goto leave;
+ }
+ if (card->reader >= card->ctx->reader_count)
+ {
+ log_error ("no card reader available\n");
+ rc = gpg_error (GPG_ERR_CARD);
+ goto leave;
+ }
+ card->ctx->error_file = log_get_stream ();
+ card->ctx->debug = opt.debug_sc;
+ card->ctx->debug_file = log_get_stream ();
+
+ if (sc_detect_card_presence (card->ctx->reader[card->reader], 0) != 1)
+ {
+ rc = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
+ goto leave;
+ }
+
+ rc = sc_connect_card (card->ctx->reader[card->reader], 0, &card->scard);
+ if (rc)
+ {
+ log_error ("failed to connect card in reader %d: %s\n",
+ card->reader, sc_strerror (rc));
+ rc = map_sc_err (rc);
+ goto leave;
+ }
+ if (opt.verbose)
+ log_info ("connected to card in reader %d using driver `%s'\n",
+ card->reader, card->scard->driver->name);
+
+ rc = sc_lock (card->scard);
+ if (rc)
+ {
+ log_error ("can't lock card in reader %d: %s\n",
+ card->reader, sc_strerror (rc));
+ rc = map_sc_err (rc);
+ goto leave;
+ }
+
+
+ leave:
+ if (rc)
+ card_close (card);
+ else
+ *rcard = card;
+
+ return rc;
+#else
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+#endif
+}
+
+
+/* Close a card and release all resources */
+void
+card_close (CARD card)
+{
+ if (card)
+ {
+#ifdef HAVE_OPENSC
+ if (card->p15card)
+ {
+ sc_pkcs15_unbind (card->p15card);
+ card->p15card = NULL;
+ }
+ if (card->p15priv)
+ p15_release_private_data (card);
+ if (card->scard)
+ {
+ sc_unlock (card->scard);
+ sc_disconnect_card (card->scard, 0);
+ card->scard = NULL;
+ }
+ if (card->ctx)
+ {
+ sc_release_context (card->ctx);
+ card->ctx = NULL;
+ }
+#endif
+ xfree (card);
+ }
+}
+
+/* Locate a simple TLV encoded data object in BUFFER of LENGTH and
+ return a pointer to value as well as its length in NBYTES. Return
+ NULL if it was not found. Note, that the function does not check
+ whether the value fits into the provided buffer. */
+#ifdef HAVE_OPENSC
+static const char *
+find_simple_tlv (const unsigned char *buffer, size_t length,
+ int tag, size_t *nbytes)
+{
+ const char *s = buffer;
+ size_t n = length;
+ size_t len;
+
+ for (;;)
+ {
+ buffer = s;
+ if (n < 2)
+ return NULL; /* buffer too short for tag and length. */
+ len = s[1];
+ s += 2; n -= 2;
+ if (len == 255)
+ {
+ if (n < 2)
+ return NULL; /* we expected 2 more bytes with the length. */
+ len = (s[0] << 8) | s[1];
+ s += 2; n -= 2;
+ }
+ if (*buffer == tag)
+ {
+ *nbytes = len;
+ return s;
+ }
+ if (len > n)
+ return NULL; /* buffer too short to skip to the next tag. */
+ s += len; n -= len;
+ }
+}
+#endif /*HAVE_OPENSC*/
+
+/* Find the ICC Serial Number within the provided BUFFER of LENGTH
+ (which should contain the GDO file) and return it as a hex encoded
+ string and allocated string in SERIAL. Return an error code when
+ the ICCSN was not found. */
+#ifdef HAVE_OPENSC
+static int
+find_iccsn (const unsigned char *buffer, size_t length, char **serial)
+{
+ size_t n;
+ const unsigned char *s;
+ char *p;
+
+ s = find_simple_tlv (buffer, length, 0x5A, &n);
+ if (!s)
+ return gpg_error (GPG_ERR_CARD);
+ length -= s - buffer;
+ if (n > length)
+ {
+ /* Oops, it does not fit into the buffer. This is an invalid
+ encoding (or the buffer is too short. However, I have some
+ test cards with such an invalid encoding and therefore I use
+ this ugly workaround to return something I can further
+ experiment with. */
+ if (n == 0x0D && length+1 == n)
+ {
+ log_debug ("enabling BMI testcard workaround\n");
+ n--;
+ }
+ else
+ return gpg_error (GPG_ERR_CARD); /* Bad encoding; does
+ not fit into buffer. */
+ }
+ if (!n)
+ return gpg_error (GPG_ERR_CARD); /* Well, that is too short. */
+
+ *serial = p = xtrymalloc (2*n+1);
+ if (!*serial)
+ return out_of_core ();
+ for (; n; n--, p += 2, s++)
+ sprintf (p, "%02X", *s);
+ *p = 0;
+ return 0;
+}
+#endif /*HAVE_OPENSC*/
+
+/* Retrieve the serial number and the time of the last update of the
+ card. The serial number is returned as a malloced string (hex
+ encoded) in SERIAL and the time of update is returned in STAMP.
+ If no update time is available the returned value is 0. The serial
+ is mandatory for a PKCS_15 application and an error will be
+ returned if this value is not availbale. For non-PKCS-15 cards a
+ serial number is constructed by other means. Caller must free
+ SERIAL unless the function returns an error. */
+int
+card_get_serial_and_stamp (CARD card, char **serial, time_t *stamp)
+{
+#ifdef HAVE_OPENSC
+ int rc;
+ struct sc_path path;
+ struct sc_file *file;
+ unsigned char buf[256];
+ int buflen;
+#endif
+
+ if (!card || !serial || !stamp)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ *serial = NULL;
+ *stamp = 0; /* not available */
+
+#ifdef HAVE_OPENSC
+ if (!card->fnc.initialized)
+ {
+ card->fnc.initialized = 1;
+ /* The first use of this card tries to figure out the type of the card
+ and sets up the function pointers. */
+ rc = sc_pkcs15_bind (card->scard, &card->p15card);
+ if (rc)
+ {
+ if (rc != SC_ERROR_PKCS15_APP_NOT_FOUND)
+ log_error ("binding of existing PKCS-15 failed in reader %d: %s\n",
+ card->reader, sc_strerror (rc));
+ card->p15card = NULL;
+ rc = 0;
+ }
+ if (card->p15card)
+ card_p15_bind (card);
+ else
+ card_dinsig_bind (card);
+ card->fnc.initialized = 1;
+ }
+
+
+ /* We should lookup the iso 7812-1 and 8583-3 - argh ISO
+ practice is suppressing innovation - IETF rules! So we
+ always get the serialnumber from the 2F02 GDO file. */
+ /* FIXME: in case we can't parse the 2F02 EF and we have a P15 card,
+ we should get the serial number from the respective P15 file */
+ sc_format_path ("3F002F02", &path);
+ rc = sc_select_file (card->scard, &path, &file);
+ if (rc)
+ {
+ log_error ("sc_select_file failed: %s\n", sc_strerror (rc));
+ return gpg_error (GPG_ERR_CARD);
+ }
+ if (file->type != SC_FILE_TYPE_WORKING_EF
+ || file->ef_structure != SC_FILE_EF_TRANSPARENT)
+ {
+ log_error ("wrong type or structure of GDO file\n");
+ sc_file_free (file);
+ return gpg_error (GPG_ERR_CARD);
+ }
+
+ if (!file->size || file->size >= DIM(buf) )
+ { /* FIXME: Use a real parser */
+ log_error ("unsupported size of GDO file (%d)\n", file->size);
+ sc_file_free (file);
+ return gpg_error (GPG_ERR_CARD);
+ }
+ buflen = file->size;
+
+ rc = sc_read_binary (card->scard, 0, buf, buflen, 0);
+ sc_file_free (file);
+ if (rc < 0)
+ {
+ log_error ("error reading GDO file: %s\n", sc_strerror (rc));
+ return gpg_error (GPG_ERR_CARD);
+ }
+ if (rc != buflen)
+ {
+ log_error ("short read on GDO file\n");
+ return gpg_error (GPG_ERR_CARD);
+ }
+
+ rc = find_iccsn (buf, buflen, serial);
+ if (gpg_err_code (rc) == GPG_ERR_CARD)
+ log_error ("invalid structure of GDO file\n");
+ if (!rc && card->p15card && !strcmp (*serial, "D27600000000000000000000"))
+ { /* This is a German card with a silly serial number. Try to get
+ the serial number from the EF(TokenInfo). We indicate such a
+ serial number by the using the prefix: "FF0100". */
+ const char *efser = card->p15card->serial_number;
+ char *p;
+
+ if (!efser)
+ efser = "";
+
+ xfree (*serial);
+ *serial = NULL;
+ p = xtrymalloc (strlen (efser) + 7);
+ if (!p)
+ rc = out_of_core ();
+ else
+ {
+ strcpy (p, "FF0100");
+ strcpy (p+6, efser);
+ *serial = p;
+ }
+ }
+ else if (!rc && **serial == 'F' && (*serial)[1] == 'F')
+ { /* The serial number starts with our special prefix. This
+ requires that we put our default prefix "FF0000" in front. */
+ char *p = xtrymalloc (strlen (*serial) + 7);
+ if (!p)
+ {
+ xfree (*serial);
+ *serial = NULL;
+ rc = out_of_core ();
+ }
+ else
+ {
+ strcpy (p, "FF0000");
+ strcpy (p+6, *serial);
+ xfree (*serial);
+ *serial = p;
+ }
+ }
+ return rc;
+#else
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+#endif
+}
+
+
+/* Enumerate all keypairs on the card and return the Keygrip as well
+ as the internal identification of the key. KEYGRIP must be a
+ caller provided buffer with a size of 20 bytes which will receive
+ the KEYGRIP of the keypair. If KEYID is not NULL, it returns the
+ ID field of the key in allocated memory; this is a string without
+ spaces. The function returns -1 when all keys have been
+ enumerated. Note that the error GPG_ERR_MISSING_CERTIFICATE may be
+ returned if there is just the private key but no public key (ie.e a
+ certificate) available. Applications might want to continue
+ enumerating after this error.*/
+int
+card_enum_keypairs (CARD card, int idx,
+ unsigned char *keygrip,
+ char **keyid)
+{
+ int rc;
+
+ if (keyid)
+ *keyid = NULL;
+
+ if (!card || !keygrip)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (idx < 0)
+ return gpg_error (GPG_ERR_INV_INDEX);
+ if (!card->fnc.initialized)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ if (!card->fnc.enum_keypairs)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ rc = card->fnc.enum_keypairs (card, idx, keygrip, keyid);
+ if (opt.verbose)
+ log_info ("card operation enum_keypairs result: %s\n",
+ gpg_strerror (rc));
+ return rc;
+}
+
+
+/* Enumerate all trusted certificates available on the card, return
+ their ID in CERT and the type in CERTTYPE. Types of certificates
+ are:
+ 0 := Unknown
+ 100 := Regular X.509 cert
+ 101 := Trusted X.509 cert
+ 102 := Useful X.509 cert
+ */
+int
+card_enum_certs (CARD card, int idx, char **certid, int *certtype)
+{
+ int rc;
+
+ if (certid)
+ *certid = NULL;
+
+ if (!card)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (idx < 0)
+ return gpg_error (GPG_ERR_INV_INDEX);
+ if (!card->fnc.initialized)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ if (!card->fnc.enum_certs)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ rc = card->fnc.enum_certs (card, idx, certid, certtype);
+ if (opt.verbose)
+ log_info ("card operation enum_certs result: %s\n",
+ gpg_strerror (rc));
+ return rc;
+}
+
+
+
+/* Read the certificate identified by CERTIDSTR which is the
+ hexadecimal encoded ID of the certificate, prefixed with the string
+ "3F005015.". The certificate is return in DER encoded form in CERT
+ and NCERT. */
+int
+card_read_cert (CARD card, const char *certidstr,
+ unsigned char **cert, size_t *ncert)
+{
+ int rc;
+
+ if (!card || !certidstr || !cert || !ncert)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!card->fnc.initialized)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ if (!card->fnc.read_cert)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ rc = card->fnc.read_cert (card, certidstr, cert, ncert);
+ if (opt.verbose)
+ log_info ("card operation read_cert result: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+
+/* Create the signature and return the allocated result in OUTDATA.
+ If a PIN is required the PINCB will be used to ask for the PIN; it
+ should return the PIN in an allocated buffer and put it into PIN. */
+int
+card_sign (CARD card, const char *keyidstr, int hashalgo,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen )
+{
+ int rc;
+
+ if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!card->fnc.initialized)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ if (!card->fnc.sign)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ rc = card->fnc.sign (card, keyidstr, hashalgo,
+ pincb, pincb_arg,
+ indata, indatalen,
+ outdata, outdatalen);
+ if (opt.verbose)
+ log_info ("card operation sign result: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
+
+/* Create the signature and return the allocated result in OUTDATA.
+ If a PIN is required the PINCB will be used to ask for the PIN; it
+ should return the PIN in an allocated buffer and put it into PIN. */
+int
+card_decipher (CARD card, const char *keyidstr,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen )
+{
+ int rc;
+
+ if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ if (!card->fnc.initialized)
+ return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+ if (!card->fnc.decipher)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+ rc = card->fnc.decipher (card, keyidstr,
+ pincb, pincb_arg,
+ indata, indatalen,
+ outdata, outdatalen);
+ if (opt.verbose)
+ log_info ("card operation decipher result: %s\n", gpg_strerror (rc));
+ return rc;
+}
+
diff --git a/scd/command.c b/scd/command.c
new file mode 100644
index 000000000..c53af84f9
--- /dev/null
+++ b/scd/command.c
@@ -0,0 +1,1034 @@
+/* command.c - SCdaemon command handler
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <ksba.h>
+
+#include <assuan.h>
+
+#include "scdaemon.h"
+#include "app-common.h"
+
+/* maximum length aloowed as a PIN; used for INQUIRE NEEDPIN */
+#define MAXLEN_PIN 100
+
+#define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t))
+
+/* Data used to associate an Assuan context with local server data */
+struct server_local_s {
+ ASSUAN_CONTEXT assuan_ctx;
+};
+
+
+/* Check whether the option NAME appears in LINE */
+static int
+has_option (const char *line, const char *name)
+{
+ const char *s;
+ int n = strlen (name);
+
+ s = strstr (line, name);
+ return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n)));
+}
+
+
+
+
+/* Note, that this reset_notify is also used for cleanup purposes. */
+static void
+reset_notify (ASSUAN_CONTEXT ctx)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+
+ if (ctrl->card_ctx)
+ {
+ card_close (ctrl->card_ctx);
+ ctrl->card_ctx = NULL;
+ xfree (ctrl->in_data.value);
+ ctrl->in_data.value = NULL;
+ }
+ if (ctrl->app_ctx)
+ {
+ /* FIXME: close the application. */
+ xfree (ctrl->app_ctx);
+ ctrl->app_ctx = NULL;
+ }
+}
+
+
+static int
+option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value)
+{
+ return 0;
+}
+
+
+/* If the card has not yet been opened, do it. Note that this
+ function returns an Assuan error, so don't map the error a second
+ time */
+static AssuanError
+open_card (CTRL ctrl)
+{
+ if (ctrl->app_ctx)
+ return 0; /* Already initialized for one specific application. */
+ if (ctrl->card_ctx)
+ return 0; /* Already initialized using a card context. */
+
+ ctrl->app_ctx = select_application ();
+ if (!ctrl->app_ctx)
+ { /* No application found - fall back to old mode. */
+ int rc = card_open (&ctrl->card_ctx);
+ if (rc)
+ return map_to_assuan_status (rc);
+ }
+ return 0;
+}
+
+
+/* Do the percent and plus/space unescaping in place and return tghe
+ length of the valid buffer. */
+static size_t
+percent_plus_unescape (unsigned char *string)
+{
+ unsigned char *p = string;
+ size_t n = 0;
+
+ while (*string)
+ {
+ if (*string == '%' && string[1] && string[2])
+ {
+ string++;
+ *p++ = xtoi_2 (string);
+ n++;
+ string+= 2;
+ }
+ else if (*string == '+')
+ {
+ *p++ = ' ';
+ n++;
+ string++;
+ }
+ else
+ {
+ *p++ = *string++;
+ n++;
+ }
+ }
+
+ return n;
+}
+
+
+
+/* SERIALNO
+
+ Return the serial number of the card using a status reponse. This
+ functon should be used to check for the presence of a card.
+
+ This function is special in that it can be used to reset the card.
+ Most other functions will return an error when a card change has
+ been detected and the use of this function is therefore required.
+
+ Background: We want to keep the client clear of handling card
+ changes between operations; i.e. the client can assume that all
+ operations are done on the same card unless he calls this function.
+ */
+static int
+cmd_serialno (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc = 0;
+ char *serial_and_stamp;
+ char *serial;
+ time_t stamp;
+
+ if ((rc = open_card (ctrl)))
+ return rc;
+
+ if (ctrl->app_ctx)
+ rc = app_get_serial_and_stamp (ctrl->app_ctx, &serial, &stamp);
+ else
+ rc = card_get_serial_and_stamp (ctrl->card_ctx, &serial, &stamp);
+ if (rc)
+ return map_to_assuan_status (rc);
+ rc = asprintf (&serial_and_stamp, "%s %lu", serial, (unsigned long)stamp);
+ xfree (serial);
+ if (rc < 0)
+ return ASSUAN_Out_Of_Core;
+ rc = 0;
+ assuan_write_status (ctx, "SERIALNO", serial_and_stamp);
+ free (serial_and_stamp);
+ return 0;
+}
+
+
+
+
+/* LEARN [--force]
+
+ Learn all useful information of the currently inserted card. When
+ used without the force options, the command might do an INQUIRE
+ like this:
+
+ INQUIRE KNOWNCARDP <hexstring_with_serialNumber> <timestamp>
+
+ The client should just send an "END" if the processing should go on
+ or a "CANCEL" to force the function to terminate with a Cancel
+ error message. The response of this command is a list of status
+ lines formatted as this:
+
+ S APPTYPE <apptype>
+
+ This returns the type of the application, currently the strings:
+
+ P15 = PKCS-15 structure used
+ DINSIG = DIN SIG
+ OPENPGP = OpenPGP card
+
+ are implemented. These strings are aliases for the AID
+
+ S KEYPAIRINFO <hexstring_with_keygrip> <hexstring_with_id>
+
+ If there is no certificate yet stored on the card a single "X" is
+ returned as the keygrip. In addition to the keypair info, information
+ about all certificates stored on the card is also returned:
+
+ S CERTINFO <certtype> <hexstring_with_id>
+
+ Where CERTTYPE is a number indicating the type of certificate:
+ 0 := Unknown
+ 100 := Regular X.509 cert
+ 101 := Trusted X.509 cert
+ 102 := Useful X.509 cert
+
+ For certain cards, more information will be returned:
+
+ S KEY-FPR <no> <hexstring>
+
+ For OpenPGP cards this returns the stored fingerprints of the
+ keys. This can be used check whether a key is available on the
+ card. NO may be 1, 2 or 3.
+
+ S CA-FPR <no> <hexstring>
+
+ Similar to above, these are the fingerprints of keys assumed to be
+ ultimately trusted.
+
+ S DISP-NAME <name_of_card_holder>
+
+ The name of the card holder as stored on the card; percent
+ aescaping takes place, spaces are encoded as '+'
+
+ S PUBKEY-URL <url>
+
+ The URL to be used for locating the entire public key.
+
+*/
+static int
+cmd_learn (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc = 0;
+ int idx;
+
+ if ((rc = open_card (ctrl)))
+ return rc;
+
+ /* Unless the force option is used we try a shortcut by identifying
+ the card using a serial number and inquiring the client with
+ that. The client may choose to cancel the operation if he already
+ knows about this card */
+ {
+ char *serial_and_stamp;
+ char *serial;
+ time_t stamp;
+
+ if (ctrl->app_ctx)
+ rc = app_get_serial_and_stamp (ctrl->app_ctx, &serial, &stamp);
+ else
+ rc = card_get_serial_and_stamp (ctrl->card_ctx, &serial, &stamp);
+ if (rc)
+ return map_to_assuan_status (rc);
+ rc = asprintf (&serial_and_stamp, "%s %lu", serial, (unsigned long)stamp);
+ xfree (serial);
+ if (rc < 0)
+ return ASSUAN_Out_Of_Core;
+ rc = 0;
+ assuan_write_status (ctx, "SERIALNO", serial_and_stamp);
+
+ if (!has_option (line, "--force"))
+ {
+ char *command;
+
+ rc = asprintf (&command, "KNOWNCARDP %s", serial_and_stamp);
+ if (rc < 0)
+ {
+ free (serial_and_stamp);
+ return ASSUAN_Out_Of_Core;
+ }
+ rc = 0;
+ rc = assuan_inquire (ctx, command, NULL, NULL, 0);
+ free (command); /* (must use standard free here) */
+ if (rc)
+ {
+ if (rc != ASSUAN_Canceled)
+ log_error ("inquire KNOWNCARDP failed: %s\n",
+ assuan_strerror (rc));
+ free (serial_and_stamp);
+ return rc;
+ }
+ /* not canceled, so we have to proceeed */
+ }
+ free (serial_and_stamp);
+ }
+
+ /* Return information about the certificates. */
+ if (ctrl->app_ctx)
+ rc = -1; /* This information is not yet available for applications. */
+ for (idx=0; !rc; idx++)
+ {
+ char *certid;
+ int certtype;
+
+ rc = card_enum_certs (ctrl->card_ctx, idx, &certid, &certtype);
+ if (!rc)
+ {
+ char *buf;
+
+ buf = xtrymalloc (40 + 1 + strlen (certid) + 1);
+ if (!buf)
+ rc = out_of_core ();
+ else
+ {
+ sprintf (buf, "%d %s", certtype, certid);
+ assuan_write_status (ctx, "CERTINFO", buf);
+ xfree (buf);
+ }
+ }
+ xfree (certid);
+ }
+ if (rc == -1)
+ rc = 0;
+
+
+ /* Return information about the keys. */
+ if (ctrl->app_ctx)
+ rc = -1; /* This information is not yet available for applications. */
+ for (idx=0; !rc; idx++)
+ {
+ unsigned char keygrip[20];
+ char *keyid;
+ int no_cert = 0;
+
+ rc = card_enum_keypairs (ctrl->card_ctx, idx, keygrip, &keyid);
+ if (gpg_err_code (rc) == GPG_ERR_MISSING_CERT && keyid)
+ {
+ /* this does happen with an incomplete personalized
+ card; i.e. during the time we have stored the key on the
+ card but not stored the certificate; probably becuase it
+ has not yet been received back from the CA. Note that we
+ must release KEYID in this case. */
+ rc = 0;
+ no_cert = 1;
+ }
+ if (!rc)
+ {
+ char *buf, *p;
+
+ buf = p = xtrymalloc (40 + 1 + strlen (keyid) + 1);
+ if (!buf)
+ rc = out_of_core ();
+ else
+ {
+ int i;
+
+ if (no_cert)
+ *p++ = 'X';
+ else
+ {
+ for (i=0; i < 20; i++, p += 2)
+ sprintf (p, "%02X", keygrip[i]);
+ }
+ *p++ = ' ';
+ strcpy (p, keyid);
+ assuan_write_status (ctx, "KEYPAIRINFO", buf);
+ xfree (buf);
+ }
+ }
+ xfree (keyid);
+ }
+ if (rc == -1)
+ rc = 0;
+
+ if (!rc && ctrl->app_ctx)
+ rc = app_write_learn_status (ctrl->app_ctx, ctrl);
+
+
+ return map_to_assuan_status (rc);
+}
+
+
+
+/* READCERT <hexified_certid>
+
+ */
+static int
+cmd_readcert (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ unsigned char *cert;
+ size_t ncert;
+
+ if ((rc = open_card (ctrl)))
+ return rc;
+
+ if (ctrl->app_ctx)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+
+ rc = card_read_cert (ctrl->card_ctx, line, &cert, &ncert);
+ if (rc)
+ {
+ log_error ("card_read_cert failed: %s\n", gpg_strerror (rc));
+ }
+ if (!rc)
+ {
+ rc = assuan_send_data (ctx, cert, ncert);
+ xfree (cert);
+ if (rc)
+ return rc;
+ }
+
+ return map_to_assuan_status (rc);
+}
+
+
+/* READKEY <hexified_certid>
+
+ Return the public key for the given cert or key ID as an standard
+ S-Expression. */
+static int
+cmd_readkey (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ unsigned char *cert = NULL;
+ size_t ncert, n;
+ KsbaCert kc = NULL;
+ KsbaSexp p;
+
+ if ((rc = open_card (ctrl)))
+ return rc;
+
+ if (ctrl->app_ctx)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+
+ rc = card_read_cert (ctrl->card_ctx, line, &cert, &ncert);
+ if (rc)
+ {
+ log_error ("card_read_cert failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ kc = ksba_cert_new ();
+ if (!kc)
+ {
+ rc = out_of_core ();
+ xfree (cert);
+ goto leave;
+ }
+ rc = ksba_cert_init_from_mem (kc, cert, ncert);
+ if (rc)
+ {
+ log_error ("failed to parse the certificate: %s\n", ksba_strerror (rc));
+ rc = map_ksba_err (rc);
+ goto leave;
+ }
+
+ p = ksba_cert_get_public_key (kc);
+ if (!p)
+ {
+ rc = gpg_error (GPG_ERR_NO_PUBKEY);
+ goto leave;
+ }
+
+ n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+ rc = assuan_send_data (ctx, p, n);
+ rc = map_assuan_err (rc);
+ xfree (p);
+
+
+ leave:
+ ksba_cert_release (kc);
+ xfree (cert);
+ return map_to_assuan_status (rc);
+}
+
+
+
+
+/* SETDATA <hexstring>
+
+ The client should use this command to tell us the data he want to
+ sign. */
+static int
+cmd_setdata (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int n;
+ char *p;
+ unsigned char *buf;
+
+ /* parse the hexstring */
+ for (p=line,n=0; hexdigitp (p); p++, n++)
+ ;
+ if (*p)
+ return set_error (Parameter_Error, "invalid hexstring");
+ if (!n)
+ return set_error (Parameter_Error, "no data given");
+ if ((n&1))
+ return set_error (Parameter_Error, "odd number of digits");
+ n /= 2;
+ buf = xtrymalloc (n);
+ if (!buf)
+ return ASSUAN_Out_Of_Core;
+
+ ctrl->in_data.value = buf;
+ ctrl->in_data.valuelen = n;
+ for (p=line, n=0; n < ctrl->in_data.valuelen; p += 2, n++)
+ buf[n] = xtoi_2 (p);
+ return 0;
+}
+
+
+
+static int
+pin_cb (void *opaque, const char *info, char **retstr)
+{
+ ASSUAN_CONTEXT ctx = opaque;
+ char *command;
+ int rc;
+ char *value;
+ size_t valuelen;
+
+ *retstr = NULL;
+ log_debug ("asking for PIN '%s'\n", info);
+
+ rc = asprintf (&command, "NEEDPIN %s", info);
+ if (rc < 0)
+ return out_of_core ();
+
+ /* FIXME: Write an inquire function which returns the result in
+ secure memory */
+ rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
+ free (command);
+ if (rc)
+ return map_assuan_err (rc);
+
+ if (!valuelen || value[valuelen-1])
+ {
+ /* We require that the returned value is an UTF-8 string */
+ xfree (value);
+ return gpg_error (GPG_ERR_INV_RESPONSE);
+ }
+ *retstr = value;
+ return 0;
+}
+
+
+/* PKSIGN <hexified_id>
+
+ */
+static int
+cmd_pksign (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ unsigned char *outdata;
+ size_t outdatalen;
+ char *keyidstr;
+
+ if ((rc = open_card (ctrl)))
+ return rc;
+
+ /* We have to use a copy of the key ID because the function may use
+ the pin_cb which in turn uses the assuan line buffer and thus
+ overwriting the original line with the keyid */
+ keyidstr = strdup (line);
+ if (!keyidstr)
+ return ASSUAN_Out_Of_Core;
+
+ if (ctrl->app_ctx)
+ rc = app_sign (ctrl->app_ctx,
+ keyidstr, GCRY_MD_SHA1,
+ pin_cb, ctx,
+ ctrl->in_data.value, ctrl->in_data.valuelen,
+ &outdata, &outdatalen);
+ else
+ rc = card_sign (ctrl->card_ctx,
+ keyidstr, GCRY_MD_SHA1,
+ pin_cb, ctx,
+ ctrl->in_data.value, ctrl->in_data.valuelen,
+ &outdata, &outdatalen);
+ free (keyidstr);
+ if (rc)
+ {
+ log_error ("card_sign failed: %s\n", gpg_strerror (rc));
+ }
+ else
+ {
+ rc = assuan_send_data (ctx, outdata, outdatalen);
+ xfree (outdata);
+ if (rc)
+ return rc; /* that is already an assuan error code */
+ }
+
+ return map_to_assuan_status (rc);
+}
+
+/* PKAUTH <hexified_id>
+
+ */
+static int
+cmd_pkauth (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ unsigned char *outdata;
+ size_t outdatalen;
+ char *keyidstr;
+
+ if ((rc = open_card (ctrl)))
+ return rc;
+
+ if (!ctrl->app_ctx)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+
+ /* We have to use a copy of the key ID because the function may use
+ the pin_cb which in turn uses the assuan line buffer and thus
+ overwriting the original line with the keyid */
+ keyidstr = strdup (line);
+ if (!keyidstr)
+ return ASSUAN_Out_Of_Core;
+
+ rc = app_auth (ctrl->app_ctx,
+ keyidstr,
+ pin_cb, ctx,
+ ctrl->in_data.value, ctrl->in_data.valuelen,
+ &outdata, &outdatalen);
+ free (keyidstr);
+ if (rc)
+ {
+ log_error ("app_auth_sign failed: %s\n", gpg_strerror (rc));
+ }
+ else
+ {
+ rc = assuan_send_data (ctx, outdata, outdatalen);
+ xfree (outdata);
+ if (rc)
+ return rc; /* that is already an assuan error code */
+ }
+
+ return map_to_assuan_status (rc);
+}
+
+/* PKDECRYPT <hexified_id>
+
+ */
+static int
+cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ unsigned char *outdata;
+ size_t outdatalen;
+ char *keyidstr;
+
+ if ((rc = open_card (ctrl)))
+ return rc;
+
+ keyidstr = strdup (line);
+ if (!keyidstr)
+ return ASSUAN_Out_Of_Core;
+ if (ctrl->app_ctx)
+ rc = app_decipher (ctrl->app_ctx,
+ keyidstr,
+ pin_cb, ctx,
+ ctrl->in_data.value, ctrl->in_data.valuelen,
+ &outdata, &outdatalen);
+ else
+ rc = card_decipher (ctrl->card_ctx,
+ keyidstr,
+ pin_cb, ctx,
+ ctrl->in_data.value, ctrl->in_data.valuelen,
+ &outdata, &outdatalen);
+ free (keyidstr);
+ if (rc)
+ {
+ log_error ("card_create_signature failed: %s\n", gpg_strerror (rc));
+ }
+ else
+ {
+ rc = assuan_send_data (ctx, outdata, outdatalen);
+ xfree (outdata);
+ if (rc)
+ return rc; /* that is already an assuan error code */
+ }
+
+ return map_to_assuan_status (rc);
+}
+
+
+/* SETATTR <name> <value>
+
+ This command is used to store data on a a smartcard. The allowed
+ names and values are depend on the currently selected smartcard
+ application. NAME and VALUE must be percent and '+' escaped.
+
+ However, the curent implementation assumes that Name is not escaped;
+ this works as long as noone uses arbitrary escaping.
+
+ A PIN will be requested for most NAMEs. See the corresponding
+ setattr function of the actually used application (app-*.c) for
+ details. */
+static int
+cmd_setattr (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ char *keyword;
+ int keywordlen;
+ size_t nbytes;
+
+ if ((rc = open_card (ctrl)))
+ return rc;
+
+ keyword = line;
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ if (*line)
+ *line++ = 0;
+ while (spacep (line))
+ line++;
+ nbytes = percent_plus_unescape (line);
+
+ rc = app_setattr (ctrl->app_ctx, keyword, pin_cb, ctx, line, nbytes);
+
+ return map_to_assuan_status (rc);
+}
+
+/* GENKEY [--force] <no>
+
+ Generate a key on-card identified by NO, which is application
+ specific. Return values are application specific. For OpenPGP
+ cards 2 status lines are returned:
+
+ S KEY-FPR <hexstring>
+ S KEY-CREATED-AT <seconds_since_epoch>
+ S KEY-DATA [p|n] <hexdata>
+
+
+ --force is required to overwriet an already existing key. The
+ KEY-CREATED-AT is required for further processing because it is
+ part of the hashed key material for the fingerprint.
+
+ The public part of the key can also later be retrieved using the
+ READKEY command.
+
+ */
+static int
+cmd_genkey (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ char *keyno;
+ int force = has_option (line, "--force");
+
+ /* Skip over options. */
+ while ( *line == '-' && line[1] == '-' )
+ {
+ while (!spacep (line))
+ line++;
+ while (spacep (line))
+ line++;
+ }
+ if (!*line)
+ return set_error (Parameter_Error, "no key number given");
+ keyno = line;
+ while (!spacep (line))
+ line++;
+ *line = 0;
+
+ if ((rc = open_card (ctrl)))
+ return rc;
+
+ if (!ctrl->app_ctx)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+
+ rc = app_genkey (ctrl->app_ctx, ctrl, keyno, force? 1:0, pin_cb, ctx);
+
+ return map_to_assuan_status (rc);
+}
+
+
+/* RANDOM <nbytes>
+
+ Get NBYTES of random from the card and send them back as data.
+*/
+static int
+cmd_random (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ size_t nbytes;
+ unsigned char *buffer;
+
+ if (!*line)
+ return set_error (Parameter_Error, "number of requested bytes missing");
+ nbytes = strtoul (line, NULL, 0);
+
+ if ((rc = open_card (ctrl)))
+ return rc;
+
+ if (!ctrl->app_ctx)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+
+ buffer = xtrymalloc (nbytes);
+ if (!buffer)
+ return ASSUAN_Out_Of_Core;
+
+ rc = app_get_challenge (ctrl->app_ctx, nbytes, buffer);
+ if (!rc)
+ {
+ rc = assuan_send_data (ctx, buffer, nbytes);
+ xfree (buffer);
+ return rc; /* that is already an assuan error code */
+ }
+ xfree (buffer);
+
+ return map_to_assuan_status (rc);
+}
+
+
+/* PASSWD [--reset] <chvno>
+
+ Change the PIN or reset thye retry counter of the card holder
+ verfication vector CHVNO. */
+static int
+cmd_passwd (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ char *chvnostr;
+ int reset_mode = has_option (line, "--reset");
+
+ /* Skip over options. */
+ while (*line == '-' && line[1] == '-')
+ {
+ while (!spacep (line))
+ line++;
+ while (spacep (line))
+ line++;
+ }
+ if (!*line)
+ return set_error (Parameter_Error, "no CHV number given");
+ chvnostr = line;
+ while (!spacep (line))
+ line++;
+ *line = 0;
+
+ if ((rc = open_card (ctrl)))
+ return rc;
+
+ if (!ctrl->app_ctx)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+
+ rc = app_change_pin (ctrl->app_ctx, ctrl, chvnostr, reset_mode, pin_cb, ctx
+);
+ if (rc)
+ log_error ("command passwd failed: %s\n", gpg_strerror (rc));
+ return map_to_assuan_status (rc);
+}
+
+
+
+
+/* Tell the assuan library about our commands */
+static int
+register_commands (ASSUAN_CONTEXT ctx)
+{
+ static struct {
+ const char *name;
+ int (*handler)(ASSUAN_CONTEXT, char *line);
+ } table[] = {
+ { "SERIALNO", cmd_serialno },
+ { "LEARN", cmd_learn },
+ { "READCERT", cmd_readcert },
+ { "READKEY", cmd_readkey },
+ { "SETDATA", cmd_setdata },
+ { "PKSIGN", cmd_pksign },
+ { "PKAUTH", cmd_pkauth },
+ { "PKDECRYPT", cmd_pkdecrypt },
+ { "INPUT", NULL },
+ { "OUTPUT", NULL },
+ { "SETATTR", cmd_setattr },
+ { "GENKEY", cmd_genkey },
+ { "RANDOM", cmd_random },
+ { "PASSWD", cmd_passwd },
+ { NULL }
+ };
+ int i, rc;
+
+ for (i=0; table[i].name; i++)
+ {
+ rc = assuan_register_command (ctx, table[i].name, table[i].handler);
+ if (rc)
+ return rc;
+ }
+ assuan_set_hello_line (ctx, "GNU Privacy Guard's Smartcard server ready");
+
+ assuan_register_reset_notify (ctx, reset_notify);
+ assuan_register_option_handler (ctx, option_handler);
+ return 0;
+}
+
+
+/* Startup the server. If LISTEN_FD is given as -1, this is simple
+ piper server, otherwise it is a regular server */
+void
+scd_command_handler (int listen_fd)
+{
+ int rc;
+ ASSUAN_CONTEXT ctx;
+ struct server_control_s ctrl;
+
+ memset (&ctrl, 0, sizeof ctrl);
+ scd_init_default_ctrl (&ctrl);
+
+ if (listen_fd == -1)
+ {
+ int filedes[2];
+
+ filedes[0] = 0;
+ filedes[1] = 1;
+ rc = assuan_init_pipe_server (&ctx, filedes);
+ }
+ else
+ {
+ rc = assuan_init_socket_server (&ctx, listen_fd);
+ }
+ if (rc)
+ {
+ log_error ("failed to initialize the server: %s\n",
+ assuan_strerror(rc));
+ scd_exit (2);
+ }
+ rc = register_commands (ctx);
+ if (rc)
+ {
+ log_error ("failed to register commands with Assuan: %s\n",
+ assuan_strerror(rc));
+ scd_exit (2);
+ }
+ assuan_set_pointer (ctx, &ctrl);
+ ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local);
+ ctrl.server_local->assuan_ctx = ctx;
+
+ if (DBG_ASSUAN)
+ assuan_set_log_stream (ctx, log_get_stream ());
+
+ for (;;)
+ {
+ rc = assuan_accept (ctx);
+ if (rc == -1)
+ {
+ break;
+ }
+ else if (rc)
+ {
+ log_info ("Assuan accept problem: %s\n", assuan_strerror (rc));
+ break;
+ }
+
+ rc = assuan_process (ctx);
+ if (rc)
+ {
+ log_info ("Assuan processing failed: %s\n", assuan_strerror (rc));
+ continue;
+ }
+ }
+ reset_notify (ctx); /* used for cleanup */
+
+ assuan_deinit_server (ctx);
+}
+
+
+/* Send a line with status information via assuan and escape all given
+ buffers. The variable elements are pairs of (char *, size_t),
+ terminated with a (NULL, 0). */
+void
+send_status_info (CTRL ctrl, const char *keyword, ...)
+{
+ va_list arg_ptr;
+ const unsigned char *value;
+ size_t valuelen;
+ char buf[950], *p;
+ size_t n;
+ ASSUAN_CONTEXT ctx = ctrl->server_local->assuan_ctx;
+
+ va_start (arg_ptr, keyword);
+
+ p = buf;
+ n = 0;
+ while ( (value = va_arg (arg_ptr, const unsigned char *)) )
+ {
+ valuelen = va_arg (arg_ptr, size_t);
+ if (!valuelen)
+ continue; /* empty buffer */
+ if (n)
+ {
+ *p++ = ' ';
+ n++;
+ }
+ for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++)
+ {
+ if (*value < ' ' || *value == '+')
+ {
+ sprintf (p, "%%%02X", *value);
+ p += 3;
+ }
+ else if (*value == ' ')
+ *p++ = '+';
+ else
+ *p++ = *value;
+ }
+ }
+ *p = 0;
+ assuan_write_status (ctx, keyword, buf);
+
+ va_end (arg_ptr);
+}
+
diff --git a/scd/iso7816.c b/scd/iso7816.c
new file mode 100644
index 000000000..8903d8a5c
--- /dev/null
+++ b/scd/iso7816.c
@@ -0,0 +1,371 @@
+/* iso7816.c - ISO 7816 commands
+ * Copyright (C) 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dlfcn.h>
+
+#include "scdaemon.h"
+#include "iso7816.h"
+#include "apdu.h"
+
+#define CMD_SELECT_FILE 0xA4
+#define CMD_VERIFY 0x20
+#define CMD_CHANGE_REFERENCE_DATA 0x24
+#define CMD_RESET_RETRY_COUNTER 0x2C
+#define CMD_GET_DATA 0xCA
+#define CMD_PUT_DATA 0xDA
+#define CMD_PSO 0x2A
+#define CMD_INTERNAL_AUTHENTICATE 0x88
+#define CMD_GENERATE_KEYPAIR 0x47
+#define CMD_GET_CHALLENGE 0x84
+
+static gpg_error_t
+map_sw (int sw)
+{
+ gpg_err_code_t ec;
+
+ switch (sw)
+ {
+ case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break;
+ case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break;
+ case SW_CHV_WRONG: ec = GPG_ERR_BAD_PIN; break;
+ case SW_CHV_BLOCKED: ec = GPG_ERR_PIN_BLOCKED; break;
+ case SW_USE_CONDITIONS: ec = GPG_ERR_USE_CONDITIONS; break;
+ case SW_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break;
+ case SW_BAD_PARAMETER: ec = GPG_ERR_INV_VALUE; break;
+ case SW_REF_NOT_FOUND: ec = GPG_ERR_NO_OBJ; break;
+ case SW_BAD_P0_P1: ec = GPG_ERR_INV_VALUE; break;
+ case SW_INS_NOT_SUP: ec = GPG_ERR_CARD; break;
+ case SW_CLA_NOT_SUP: ec = GPG_ERR_CARD; break;
+ case SW_SUCCESS: ec = 0; break;
+
+ case SW_HOST_OUT_OF_CORE: ec = GPG_ERR_ENOMEM; break;
+ case SW_HOST_INV_VALUE: ec = GPG_ERR_INV_VALUE; break;
+ case SW_HOST_INCOMPLETE_CARD_RESPONSE: ec = GPG_ERR_CARD; break;
+ default:
+ if ((sw & 0x010000))
+ ec = GPG_ERR_GENERAL; /* Should not happen. */
+ else if ((sw & 0xff00) == SW_MORE_DATA)
+ ec = 0; /* This should actually never been seen here. */
+ else
+ ec = GPG_ERR_CARD;
+ }
+ return gpg_error (ec);
+}
+
+/* This function is specialized version of the SELECT FILE command.
+ SLOT is the card and reader as created for example by
+ apdu_open_reader (), AID is a buffer of size AIDLEN holding the
+ requested application ID. The function can't be used to enumerate
+ AIDs and won't return the AID on success. The return value is 0
+ for okay or GNUPG error code. Note that ISO error codes are
+ internally mapped. */
+gpg_error_t
+iso7816_select_application (int slot, const char *aid, size_t aidlen)
+{
+ int sw;
+
+ sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4, 0, aidlen, aid);
+ return map_sw (sw);
+}
+
+
+/* Perform a VERIFY command on SLOT using the card holder verification
+ vector CHVNO with a CHV of lenght CHVLEN. Returns 0 on success. */
+gpg_error_t
+iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen)
+{
+ int sw;
+
+ sw = apdu_send_simple (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv);
+ return map_sw (sw);
+}
+
+/* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder
+ verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN
+ 0), a "change reference data" is done, otherwise an "exchange
+ reference data". The new reference data is expected in NEWCHV of
+ length NEWCHVLEN. */
+gpg_error_t
+iso7816_change_reference_data (int slot, int chvno,
+ const char *oldchv, size_t oldchvlen,
+ const char *newchv, size_t newchvlen)
+{
+ int sw;
+ char *buf;
+
+ if ((!oldchv && oldchvlen)
+ || (oldchv && !oldchvlen)
+ || !newchv || !newchvlen )
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ buf = xtrymalloc (oldchvlen + newchvlen);
+ if (!buf)
+ return out_of_core ();
+ if (oldchvlen)
+ memcpy (buf, oldchv, oldchvlen);
+ memcpy (buf+oldchvlen, newchv, newchvlen);
+
+ sw = apdu_send_simple (slot, 0x00, CMD_CHANGE_REFERENCE_DATA,
+ oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf);
+ xfree (buf);
+ return map_sw (sw);
+
+}
+
+gpg_error_t
+iso7816_reset_retry_counter (int slot, int chvno,
+ const char *newchv, size_t newchvlen)
+{
+ int sw;
+
+ if (!newchv || !newchvlen )
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ sw = apdu_send_simple (slot, 0x00, CMD_RESET_RETRY_COUNTER,
+ 2, chvno, newchvlen, newchv);
+ return map_sw (sw);
+}
+
+
+/* Perform a GET DATA command requesting TAG and storing the result in
+ a newly allocated buffer at the address passed by RESULT. Return
+ the length of this data at the address of RESULTLEN. */
+gpg_error_t
+iso7816_get_data (int slot, int tag,
+ unsigned char **result, size_t *resultlen)
+{
+ int sw;
+
+ if (!result || !resultlen)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ *result = NULL;
+ *resultlen = 0;
+
+ sw = apdu_send (slot, 0x00, CMD_GET_DATA,
+ ((tag >> 8) & 0xff), (tag & 0xff), -1, NULL,
+ result, resultlen);
+ if (sw != SW_SUCCESS)
+ {
+ /* Make sure that pending buffers are released. */
+ xfree (*result);
+ *result = NULL;
+ *resultlen = 0;
+ return map_sw (sw);
+ }
+
+ return 0;
+}
+
+
+/* Perform a PUT DATA command on card in SLOT. Write DATA of length
+ DATALEN to TAG. */
+gpg_error_t
+iso7816_put_data (int slot, int tag,
+ const unsigned char *data, size_t datalen)
+{
+ int sw;
+
+ sw = apdu_send_simple (slot, 0x00, CMD_PUT_DATA,
+ ((tag >> 8) & 0xff), (tag & 0xff),
+ datalen, data);
+ return map_sw (sw);
+}
+
+
+/* Perform the security operation COMPUTE DIGITAL SIGANTURE. On
+ success 0 is returned and the data is availavle in a newly
+ allocated buffer stored at RESULT with its length stored at
+ RESULTLEN. */
+gpg_error_t
+iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen)
+{
+ int sw;
+
+ if (!data || !datalen || !result || !resultlen)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ *result = NULL;
+ *resultlen = 0;
+
+ sw = apdu_send (slot, 0x00, CMD_PSO, 0x9E, 0x9A, datalen, data,
+ result, resultlen);
+ if (sw != SW_SUCCESS)
+ {
+ /* Make sure that pending buffers are released. */
+ xfree (*result);
+ *result = NULL;
+ *resultlen = 0;
+ return map_sw (sw);
+ }
+
+ return 0;
+}
+
+
+/* Perform the security operation DECIPHER. On
+ success 0 is returned and the plaintext is available in a newly
+ allocated buffer stored at RESULT with its length stored at
+ RESULTLEN. */
+gpg_error_t
+iso7816_decipher (int slot, const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen)
+{
+ int sw;
+ unsigned char *buf;
+
+ if (!data || !datalen || !result || !resultlen)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ *result = NULL;
+ *resultlen = 0;
+
+ /* We need to prepend the padding indicator. */
+ buf = xtrymalloc (datalen + 1);
+ if (!buf)
+ return out_of_core ();
+ *buf = 0; /* Padding indicator. */
+ memcpy (buf+1, data, datalen);
+ sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, datalen+1, buf,
+ result, resultlen);
+ xfree (buf);
+ if (sw != SW_SUCCESS)
+ {
+ /* Make sure that pending buffers are released. */
+ xfree (*result);
+ *result = NULL;
+ *resultlen = 0;
+ return map_sw (sw);
+ }
+
+ return 0;
+}
+
+
+gpg_error_t
+iso7816_internal_authenticate (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen)
+{
+ int sw;
+
+ if (!data || !datalen || !result || !resultlen)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ *result = NULL;
+ *resultlen = 0;
+
+ sw = apdu_send (slot, 0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0,
+ datalen, data, result, resultlen);
+ if (sw != SW_SUCCESS)
+ {
+ /* Make sure that pending buffers are released. */
+ xfree (*result);
+ *result = NULL;
+ *resultlen = 0;
+ return map_sw (sw);
+ }
+
+ return 0;
+}
+
+
+static gpg_error_t
+generate_keypair (int slot, int readonly,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen)
+{
+ int sw;
+
+ if (!data || !datalen || !result || !resultlen)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ *result = NULL;
+ *resultlen = 0;
+
+ sw = apdu_send (slot, 0x00, CMD_GENERATE_KEYPAIR, readonly? 0x81:0x80, 0,
+ datalen, data, result, resultlen);
+ if (sw != SW_SUCCESS)
+ {
+ /* Make sure that pending buffers are released. */
+ xfree (*result);
+ *result = NULL;
+ *resultlen = 0;
+ return map_sw (sw);
+ }
+
+ return 0;
+}
+
+
+gpg_error_t
+iso7816_generate_keypair (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen)
+{
+ return generate_keypair (slot, 0, data, datalen, result, resultlen);
+}
+
+
+gpg_error_t
+iso7816_read_public_key (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen)
+{
+ return generate_keypair (slot, 1, data, datalen, result, resultlen);
+}
+
+
+
+gpg_error_t
+iso7816_get_challenge (int slot, int length, unsigned char *buffer)
+{
+ int sw;
+ unsigned char *result;
+ size_t resultlen, n;
+
+ if (!buffer || length < 1)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ do
+ {
+ result = NULL;
+ n = length > 254? 254 : length;
+ sw = apdu_send_le (slot, 0x00, CMD_GET_CHALLENGE, 0, 0, -1, NULL,
+ n,
+ &result, &resultlen);
+ if (sw != SW_SUCCESS)
+ {
+ /* Make sure that pending buffers are released. */
+ xfree (result);
+ return map_sw (sw);
+ }
+ if (resultlen > n)
+ resultlen = n;
+ memcpy (buffer, result, resultlen);
+ buffer += resultlen;
+ length -= resultlen;
+ xfree (result);
+ }
+ while (length > 0);
+
+ return 0;
+}
diff --git a/scd/iso7816.h b/scd/iso7816.h
new file mode 100644
index 000000000..d7e77a101
--- /dev/null
+++ b/scd/iso7816.h
@@ -0,0 +1,56 @@
+/* iso7816.h - ISO 7816 commands
+ * Copyright (C) 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef ISO7816_H
+#define ISO7816_H
+
+gpg_error_t iso7816_select_application (int slot,
+ const char *aid, size_t aidlen);
+gpg_error_t iso7816_verify (int slot,
+ int chvno, const char *chv, size_t chvlen);
+gpg_error_t iso7816_change_reference_data (int slot, int chvno,
+ const char *oldchv, size_t oldchvlen,
+ const char *newchv, size_t newchvlen);
+gpg_error_t iso7816_reset_retry_counter (int slot, int chvno,
+ const char *newchv, size_t newchvlen);
+gpg_error_t iso7816_get_data (int slot, int tag,
+ unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_put_data (int slot, int tag,
+ const unsigned char *data, size_t datalen);
+gpg_error_t iso7816_compute_ds (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_decipher (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_internal_authenticate (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_generate_keypair (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_read_public_key (int slot,
+ const unsigned char *data, size_t datalen,
+ unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_get_challenge (int slot,
+ int length, unsigned char *buffer);
+
+
+#endif /*ISO7816_H*/
diff --git a/scd/sc-copykeys.c b/scd/sc-copykeys.c
new file mode 100644
index 000000000..9caf39a8a
--- /dev/null
+++ b/scd/sc-copykeys.c
@@ -0,0 +1,731 @@
+/* sc-copykeys.c - A tool to store keys on a smartcard.
+ * Copyright (C) 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define JNLIB_NEED_LOG_LOGV
+#include "scdaemon.h"
+#include <gcrypt.h>
+
+#include "../common/ttyio.h"
+#include "../common/simple-pwquery.h"
+#include "apdu.h" /* for open_reader */
+#include "atr.h"
+#include "app-common.h"
+
+#define _(a) (a)
+
+
+enum cmd_and_opt_values
+{ oVerbose = 'v',
+ oReaderPort = 500,
+ oDebug,
+ oDebugAll,
+
+aTest };
+
+
+static ARGPARSE_OPTS opts[] = {
+
+ { 301, NULL, 0, "@Options:\n " },
+
+ { oVerbose, "verbose", 0, "verbose" },
+ { oReaderPort, "reader-port", 1, "|N|connect to reader at port N"},
+ { oDebug, "debug" ,4|16, "set debugging flags"},
+ { oDebugAll, "debug-all" ,0, "enable full debugging"},
+ {0}
+};
+
+
+static void copykeys (APP app, const char *fname);
+
+
+static const char *
+my_strusage (int level)
+{
+ const char *p;
+ switch (level)
+ {
+ case 11: p = "sc-copykeys (GnuPG)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
+ break;
+ case 1:
+ case 40: p = _("Usage: sc-copykeys [options] (-h for help)\n");
+ break;
+ case 41: p = _("Syntax: sc-copykeys [options] "
+ "file-with-key\n"
+ "Copy keys to a smartcards\n");
+ break;
+
+ default: p = NULL;
+ }
+ return p;
+}
+
+/* Used by gcry for logging */
+static void
+my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr)
+{
+ /* translate the log levels */
+ switch (level)
+ {
+ case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break;
+ case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break;
+ case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break;
+ case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break;
+ case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break;
+ case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break;
+ case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break;
+ default: level = JNLIB_LOG_ERROR; break;
+ }
+ log_logv (level, fmt, arg_ptr);
+}
+
+
+int
+main (int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ int slot, rc;
+ int reader_port = 32768; /* First USB reader. */
+ struct app_ctx_s appbuf;
+
+ memset (&appbuf, 0, sizeof appbuf);
+
+ set_strusage (my_strusage);
+ gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+ log_set_prefix ("sc-copykeys", 1);
+
+ /* check that the libraries are suitable. Do it here because
+ the option parsing may need services of the library */
+ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
+ {
+ log_fatal( _("libgcrypt is too old (need %s, have %s)\n"),
+ NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
+ }
+
+ gcry_set_log_handler (my_gcry_logger, NULL);
+ gcry_control (GCRYCTL_DISABLE_SECMEM, 0); /* FIXME - we want to use it */
+ /* FIXME? gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);*/
+
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1; /* do not remove the args */
+ while (arg_parse (&pargs, opts) )
+ {
+ switch (pargs.r_opt)
+ {
+ case oVerbose: opt.verbose++; break;
+ case oDebug: opt.debug |= pargs.r.ret_ulong; break;
+ case oDebugAll: opt.debug = ~0; break;
+ default : pargs.err = 2; break;
+ }
+ }
+ if (log_get_errorcount(0))
+ exit(2);
+
+ if (argc != 1)
+ usage (1);
+
+ slot = apdu_open_reader (reader_port);
+ if (slot == -1)
+ exit (1);
+
+ /* FIXME: Use select_application. */
+ appbuf.slot = slot;
+ rc = app_select_openpgp (&appbuf, &appbuf.serialno, &appbuf.serialnolen);
+ if (rc)
+ {
+ log_error ("selecting openpgp failed: %s\n", gpg_strerror (rc));
+ exit (1);
+ }
+ appbuf.initialized = 1;
+ log_info ("openpgp application selected\n");
+
+ copykeys (&appbuf, *argv);
+
+
+ return 0;
+}
+
+
+
+void
+send_status_info (CTRL ctrl, const char *keyword, ...)
+{
+ /* DUMMY */
+}
+
+
+
+static char *
+read_file (const char *fname, size_t *r_length)
+{
+ FILE *fp;
+ struct stat st;
+ char *buf;
+ size_t buflen;
+
+ fp = fname? fopen (fname, "rb") : stdin;
+ if (!fp)
+ {
+ log_error ("can't open `%s': %s\n",
+ fname? fname: "[stdin]", strerror (errno));
+ return NULL;
+ }
+
+ if (fstat (fileno(fp), &st))
+ {
+ log_error ("can't stat `%s': %s\n",
+ fname? fname: "[stdin]", strerror (errno));
+ if (fname)
+ fclose (fp);
+ return NULL;
+ }
+
+ buflen = st.st_size;
+ buf = xmalloc (buflen+1);
+ if (fread (buf, buflen, 1, fp) != 1)
+ {
+ log_error ("error reading `%s': %s\n",
+ fname? fname: "[stdin]", strerror (errno));
+ if (fname)
+ fclose (fp);
+ xfree (buf);
+ return NULL;
+ }
+ if (fname)
+ fclose (fp);
+
+ *r_length = buflen;
+ return buf;
+}
+
+
+static gcry_sexp_t
+read_key (const char *fname)
+{
+ char *buf;
+ size_t buflen;
+ gcry_sexp_t private;
+ int rc;
+
+ buf = read_file (fname, &buflen);
+ if (!buf)
+ return NULL;
+
+ rc = gcry_sexp_new (&private, buf, buflen, 1);
+ if (rc)
+ {
+ log_error ("gcry_sexp_new failed: %s\n", gpg_strerror (rc));
+ return NULL;
+ }
+ xfree (buf);
+
+ return private;
+}
+
+
+
+static gcry_mpi_t *
+sexp_to_kparms (gcry_sexp_t sexp, unsigned long *created)
+{
+ gcry_sexp_t list, l2;
+ const char *name;
+ const char *s;
+ size_t n;
+ int i, idx;
+ const char *elems;
+ gcry_mpi_t *array;
+
+ *created = 0;
+ list = gcry_sexp_find_token (sexp, "private-key", 0 );
+ if(!list)
+ return NULL;
+
+ /* quick hack to get the creation time. */
+ l2 = gcry_sexp_find_token (list, "created", 0);
+ if (l2 && (name = gcry_sexp_nth_data (l2, 1, &n)))
+ {
+ char *tmp = xmalloc (n+1);
+ memcpy (tmp, name, n);
+ tmp[n] = 0;
+ *created = strtoul (tmp, NULL, 10);
+ xfree (tmp);
+ }
+ gcry_sexp_release (l2);
+ 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. */
+ elems = "nedpqu";
+ array = xcalloc (strlen(elems) + 1, sizeof *array);
+ for (idx=0, s=elems; *s; s++, idx++ )
+ {
+ l2 = gcry_sexp_find_token (list, s, 1);
+ if (!l2)
+ {
+ for (i=0; i<idx; i++)
+ gcry_mpi_release (array[i]);
+ xfree (array);
+ gcry_sexp_release (list);
+ return NULL; /* required parameter not found */
+ }
+ array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
+ gcry_sexp_release (l2);
+ if (!array[idx])
+ {
+ for (i=0; i<idx; i++)
+ gcry_mpi_release (array[i]);
+ xfree (array);
+ gcry_sexp_release (list);
+ return NULL; /* required parameter is invalid */
+ }
+ }
+
+ gcry_sexp_release (list);
+ return array;
+}
+
+
+/* Return true if the SHA1 fingerprint FPR consists only of zeroes. */
+static int
+fpr_is_zero (const char *fpr)
+{
+ int i;
+
+ for (i=0; i < 20 && !fpr[i]; i++)
+ ;
+ return (i == 20);
+}
+
+
+static void
+show_sha1_fpr (const unsigned char *fpr)
+{
+ int i;
+
+ if (fpr)
+ {
+ for (i=0; i < 20 ; i+=2, fpr += 2 )
+ {
+ if (i == 10 )
+ tty_printf (" ");
+ tty_printf (" %02X%02X", *fpr, fpr[1]);
+ }
+ }
+ else
+ tty_printf (" [none]");
+ tty_printf ("\n");
+}
+
+/* Query the card, show a list of already stored keys and ask the user
+ where to store the key. Returns the key number or 0 for cancel
+ operation. */
+static int
+query_card (APP app)
+{
+ int keyno = 0;
+ char *serialno, *disp_name, *pubkey_url;
+ unsigned char *fpr1, *fpr2, *fpr3;
+
+
+ if (app_openpgp_cardinfo (app,
+ &serialno,
+ &disp_name,
+ &pubkey_url,
+ &fpr1, &fpr2, &fpr3))
+ return 0;
+
+
+ for (;;)
+ {
+ char *answer;
+
+ tty_printf ("\n");
+
+ tty_printf ("Serial number ....: %s\n",
+ serialno? serialno : "[none]");
+ tty_printf ("Name of cardholder: %s\n",
+ disp_name && *disp_name? disp_name : "[not set]");
+ tty_printf ("URL of public key : %s\n",
+ pubkey_url && *pubkey_url? pubkey_url : "[not set]");
+ tty_printf ("Signature key ....:");
+ show_sha1_fpr (fpr1);
+ tty_printf ("Encryption key....:");
+ show_sha1_fpr (fpr2);
+ tty_printf ("Authentication key:");
+ show_sha1_fpr (fpr3);
+
+ tty_printf ("\n"
+ "1 - store as signature key and reset usage counter\n"
+ "2 - store as encryption key\n"
+ "3 - store as authentication key\n"
+ "Q - quit\n"
+ "\n");
+
+ answer = tty_get("Your selection? ");
+ tty_kill_prompt();
+ if (strlen (answer) != 1)
+ ;
+ else if ( *answer == '1' )
+ {
+ if ( (fpr1 && !fpr_is_zero (fpr1)) )
+ {
+ tty_printf ("\n");
+ log_error ("WARNING: signature key does already exists!\n");
+ tty_printf ("\n");
+ if ( tty_get_answer_is_yes ("Replace existing key? ") )
+ {
+ keyno = 1;
+ break;
+ }
+ }
+ else
+ {
+ keyno = 1;
+ break;
+ }
+ }
+ else if ( *answer == '2' )
+ {
+ if ( (fpr2 && !fpr_is_zero (fpr2)) )
+ {
+ tty_printf ("\n");
+ log_error ("WARNING: encryption key does already exists!\n");
+ tty_printf ("\n");
+ if ( tty_get_answer_is_yes ("Replace existing key? ") )
+ {
+ keyno = 2;
+ break;
+ }
+ }
+ else
+ {
+ keyno = 2;
+ break;
+ }
+ }
+ else if ( *answer == '3' )
+ {
+ if ( (fpr3 && !fpr_is_zero (fpr3)) )
+ {
+ tty_printf ("\n");
+ log_error ("WARNING: authentication key does already exists!\n");
+ tty_printf ("\n");
+ if ( tty_get_answer_is_yes ("Replace existing key? ") )
+ {
+ keyno = 3;
+ break;
+ }
+ }
+ else
+ {
+ keyno = 3;
+ break;
+ }
+ }
+ else if ( *answer == 'q' || *answer == 'Q')
+ {
+ keyno = 0;
+ break;
+ }
+ }
+
+ xfree (serialno);
+ xfree (disp_name);
+ xfree (pubkey_url);
+ xfree (fpr1);
+ xfree (fpr2);
+ xfree (fpr3);
+
+ return keyno;
+}
+
+
+/* Callback function to ask for a PIN. */
+static int
+pincb (void *arg, const char *prompt, char **pinvalue)
+{
+ char *pin = xstrdup ("12345678");
+
+/* pin = simple_pwquery (NULL, NULL, prompt, */
+/* "We need the admin's PIN to store the key on the card", */
+/* NULL); */
+/* if (!pin) */
+/* return gpg_error (GPG_ERR_CANCELED); */
+
+
+
+ *pinvalue = pin;
+ return 0;
+}
+
+
+/* This function expects a file (or NULL for stdin) with the secret
+ and public key parameters. This file should consist of an
+ S-expression as used by gpg-agent. Only the unprotected format is
+ supported. Example:
+
+ (private-key
+ (rsa
+ (n #00e0ce9..[some bytes not shown]..51#)
+ (e #010001#)
+ (d #046129F..[some bytes not shown]..81#)
+ (p #00e861b..[some bytes not shown]..f1#)
+ (q #00f7a7c..[some bytes not shown]..61#)
+ (u #304559a..[some bytes not shown]..9b#))
+ (uri http://foo.bar x-foo:whatever_you_want))
+
+*/
+static void
+copykeys (APP app, const char *fname)
+{
+ int rc;
+ gcry_sexp_t private;
+ gcry_mpi_t *mpis, rsa_n, rsa_e, rsa_p, rsa_q;
+ unsigned int nbits;
+ size_t n;
+ unsigned char *template, *tp;
+ unsigned char m[128], e[4];
+ size_t mlen, elen;
+ unsigned long creation_date;
+ time_t created_at;
+ int keyno;
+
+ if (!strcmp (fname, "-"))
+ fname = NULL;
+
+ private = read_key (fname);
+ if (!private)
+ exit (1);
+
+ mpis = sexp_to_kparms (private, &creation_date);
+ if (!creation_date)
+ {
+ log_info ("no creation date found - assuming current date\n");
+ created_at = time (NULL);
+ }
+ else
+ created_at = creation_date;
+ gcry_sexp_release (private);
+ if (!mpis)
+ {
+ log_error ("invalid structure of key file or not RSA\n");
+ exit (1);
+ }
+ /* MPIS is now an array with the key parameters as defined by OpenPGP. */
+ rsa_n = mpis[0];
+ rsa_e = mpis[1];
+ gcry_mpi_release (mpis[2]);
+ rsa_p = mpis[3];
+ rsa_q = mpis[4];
+ gcry_mpi_release (mpis[5]);
+ xfree (mpis);
+
+ nbits = gcry_mpi_get_nbits (rsa_e);
+ if (nbits < 2 || nbits > 32)
+ {
+ log_error ("public exponent too large (more than 32 bits)\n");
+ goto failure;
+ }
+ nbits = gcry_mpi_get_nbits (rsa_p);
+ if (nbits != 512)
+ {
+ log_error ("length of first RSA prime is not 512\n");
+ goto failure;
+ }
+ nbits = gcry_mpi_get_nbits (rsa_q);
+ if (nbits != 512)
+ {
+ log_error ("length of second RSA prime is not 512\n");
+ goto failure;
+ }
+
+ nbits = gcry_mpi_get_nbits (rsa_n);
+ if (nbits != 1024)
+ {
+ log_error ("length of RSA modulus is not 1024\n");
+ goto failure;
+ }
+
+ keyno = query_card (app);
+ if (!keyno)
+ goto failure;
+
+ /* Build the private key template as described in section 4.3.3.6 of
+ the specs.
+ 0xC0 <length> public exponent
+ 0xC1 <length> prime p
+ 0xC2 <length> prime q */
+ template = tp = xmalloc (1+2 + 1+1+4 + 1+1+64 + 1+1+64);
+ *tp++ = 0xC0;
+ *tp++ = 4;
+ rc = gcry_mpi_print (GCRYMPI_FMT_USG, tp, 4, &n, rsa_e);
+ if (rc)
+ {
+ log_error ("mpi_print failed: %s\n", gpg_strerror (rc));
+ goto failure;
+ }
+ assert (n <= 4);
+ memcpy (e, tp, n);
+ elen = n;
+ if (n != 4)
+ {
+ memmove (tp+4-n, tp, 4-n);
+ memset (tp, 0, 4-n);
+ }
+ tp += 4;
+
+ *tp++ = 0xC1;
+ *tp++ = 64;
+ rc = gcry_mpi_print (GCRYMPI_FMT_USG, tp, 64, &n, rsa_p);
+ if (rc)
+ {
+ log_error ("mpi_print failed: %s\n", gpg_strerror (rc));
+ goto failure;
+ }
+ assert (n == 64);
+ tp += 64;
+
+ *tp++ = 0xC2;
+ *tp++ = 64;
+ rc = gcry_mpi_print (GCRYMPI_FMT_USG, tp, 64, &n, rsa_q);
+ if (rc)
+ {
+ log_error ("mpi_print failed: %s\n", gpg_strerror (rc));
+ goto failure;
+ }
+ assert (n == 64);
+ tp += 64;
+ assert (tp - template == 138);
+
+ /* (we need the modulus to calculate the fingerprint) */
+ rc = gcry_mpi_print (GCRYMPI_FMT_USG, m, 128, &n, rsa_n);
+ if (rc)
+ {
+ log_error ("mpi_print failed: %s\n", gpg_strerror (rc));
+ goto failure;
+ }
+ assert (n == 128);
+ mlen = 128;
+
+
+ rc = app_openpgp_storekey (app, keyno,
+ template, tp - template,
+ created_at,
+ m, mlen,
+ e, elen,
+ pincb, NULL);
+
+ if (rc)
+ {
+ log_error ("error storing key: %s\n", gpg_strerror (rc));
+ goto failure;
+ }
+ log_info ("key successfully stored\n");
+ {
+ unsigned char *mm, *ee;
+ size_t mmlen, eelen;
+ int i;
+
+ rc = app_openpgp_readkey (app, keyno, &mm, &mmlen, &ee, &eelen);
+ if (rc)
+ {
+ log_error ("error reading key back: %s\n", gpg_strerror (rc));
+ goto failure;
+ }
+
+ /* Strip leading zeroes. */
+ for (i=0; i < mmlen && !mm[i]; i++)
+ ;
+ mmlen -= i;
+ memmove (mm, mm+i, mmlen);
+ for (i=0; i < eelen && !ee[i]; i++)
+ ;
+ eelen -= i;
+ memmove (ee, ee+i, eelen);
+
+ if (eelen != elen || mmlen != mlen)
+ {
+ log_error ("key parameter length mismatch (n=%u/%u, e=%u/%u)\n",
+ (unsigned int)mlen, (unsigned int)mmlen,
+ (unsigned int)elen, (unsigned int)eelen);
+ xfree (mm);
+ xfree (ee);
+ goto failure;
+ }
+
+ if (memcmp (m, mm, mlen))
+ {
+ log_error ("key parameter n mismatch\n");
+ log_printhex ("original n: ", m, mlen);
+ log_printhex (" copied n: ", mm, mlen);
+ xfree (mm);
+ xfree (ee);
+ goto failure;
+ }
+ if (memcmp (e, ee, elen))
+ {
+ log_error ("key parameter e mismatch\n");
+ log_printhex ("original e: ", e, elen);
+ log_printhex (" copied e: ", ee, elen);
+ xfree (mm);
+ xfree (ee);
+ goto failure;
+ }
+ xfree (mm);
+ xfree (ee);
+ }
+
+
+ gcry_mpi_release (rsa_e);
+ gcry_mpi_release (rsa_p);
+ gcry_mpi_release (rsa_q);
+ gcry_mpi_release (rsa_n);
+ return;
+
+ failure:
+ gcry_mpi_release (rsa_e);
+ gcry_mpi_release (rsa_p);
+ gcry_mpi_release (rsa_q);
+ gcry_mpi_release (rsa_n);
+ exit (1);
+}
+
+
diff --git a/scd/sc-investigate.c b/scd/sc-investigate.c
new file mode 100644
index 000000000..e8f0eb83c
--- /dev/null
+++ b/scd/sc-investigate.c
@@ -0,0 +1,209 @@
+/* sc-investigate.c - A tool to look around on smartcards.
+ * Copyright (C) 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#define JNLIB_NEED_LOG_LOGV
+#include "scdaemon.h"
+#include <gcrypt.h>
+
+#include "apdu.h" /* for open_reader */
+#include "atr.h"
+#include "app-common.h"
+
+#define _(a) (a)
+
+
+enum cmd_and_opt_values
+{ oVerbose = 'v',
+ oReaderPort = 500,
+ oDebug,
+ oDebugAll,
+
+ oGenRandom,
+
+aTest };
+
+
+static ARGPARSE_OPTS opts[] = {
+
+ { 301, NULL, 0, "@Options:\n " },
+
+ { oVerbose, "verbose", 0, "verbose" },
+ { oReaderPort, "reader-port", 1, "|N|connect to reader at port N"},
+ { oDebug, "debug" ,4|16, "set debugging flags"},
+ { oDebugAll, "debug-all" ,0, "enable full debugging"},
+ { oGenRandom, "gen-random", 4, "|N|generate N bytes of random"},
+ {0}
+};
+
+static const char *
+my_strusage (int level)
+{
+ const char *p;
+ switch (level)
+ {
+ case 11: p = "sc-investigate (GnuPG)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
+ break;
+ case 1:
+ case 40: p = _("Usage: sc-investigate [options] (-h for help)\n");
+ break;
+ case 41: p = _("Syntax: sc-investigate [options] [args]]\n"
+ "Have a look at smartcards\n");
+ break;
+
+ default: p = NULL;
+ }
+ return p;
+}
+
+/* Used by gcry for logging */
+static void
+my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr)
+{
+ /* translate the log levels */
+ switch (level)
+ {
+ case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break;
+ case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break;
+ case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break;
+ case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break;
+ case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break;
+ case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break;
+ case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break;
+ default: level = JNLIB_LOG_ERROR; break;
+ }
+ log_logv (level, fmt, arg_ptr);
+}
+
+
+int
+main (int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ int slot, rc;
+ int reader_port = 32768; /* First USB reader. */
+ struct app_ctx_s appbuf;
+ unsigned long gen_random = 0;
+
+ memset (&appbuf, 0, sizeof appbuf);
+
+ set_strusage (my_strusage);
+ gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+ log_set_prefix ("sc-investigate", 1);
+
+ /* check that the libraries are suitable. Do it here because
+ the option parsing may need services of the library */
+ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
+ {
+ log_fatal( _("libgcrypt is too old (need %s, have %s)\n"),
+ NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
+ }
+
+ gcry_set_log_handler (my_gcry_logger, NULL);
+ /* FIXME? gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);*/
+
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1; /* do not remove the args */
+ while (arg_parse (&pargs, opts) )
+ {
+ switch (pargs.r_opt)
+ {
+ case oVerbose: opt.verbose++; break;
+ case oDebug: opt.debug |= pargs.r.ret_ulong; break;
+ case oDebugAll: opt.debug = ~0; break;
+ case oGenRandom: gen_random = pargs.r.ret_ulong; break;
+ default : pargs.err = 2; break;
+ }
+ }
+ if (log_get_errorcount(0))
+ exit(2);
+
+ if (opt.verbose < 2)
+ opt.verbose = 2; /* hack to let select_openpgp print some info. */
+
+ if (argc)
+ usage (1);
+
+ slot = apdu_open_reader (reader_port);
+ if (slot == -1)
+ exit (1);
+
+ if (!gen_random)
+ {
+ rc = atr_dump (slot, stdout);
+ if (rc)
+ log_error ("can't dump ATR: %s\n", gpg_strerror (rc));
+ }
+
+ appbuf.slot = slot;
+ rc = app_select_openpgp (&appbuf, NULL, NULL);
+ if (rc)
+ log_error ("selecting openpgp failed: %s\n", gpg_strerror (rc));
+ else
+ {
+ appbuf.initialized = 1;
+ log_info ("openpgp application selected\n");
+
+ if (gen_random)
+ {
+ size_t nbytes;
+ unsigned char *buffer;
+
+ buffer = xmalloc (4096);
+ do
+ {
+ nbytes = gen_random > 4096? 4096 : gen_random;
+ rc = app_get_challenge (&appbuf, nbytes, buffer);
+ if (rc)
+ log_error ("app_get_challenge failed: %s\n",gpg_strerror (rc));
+ else
+ {
+ if (fwrite (buffer, nbytes, 1, stdout) != 1)
+ log_error ("writing to stdout failed: %s\n",
+ strerror (errno));
+ gen_random -= nbytes;
+ }
+ }
+ while (gen_random && !log_get_errorcount (0));
+ xfree (buffer);
+ }
+ }
+
+ return log_get_errorcount (0)? 2:0;
+}
+
+
+
+void
+send_status_info (CTRL ctrl, const char *keyword, ...)
+{
+ /* DUMMY */
+}
diff --git a/scd/scdaemon.c b/scd/scdaemon.c
new file mode 100644
index 000000000..8e0ef37c9
--- /dev/null
+++ b/scd/scdaemon.c
@@ -0,0 +1,638 @@
+/* scdaemon.c - The GnuPG Smartcard Daemon
+ * Copyright (C) 2001, 2002 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <time.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <signal.h>
+
+#define JNLIB_NEED_LOG_LOGV
+#include "scdaemon.h"
+#include <ksba.h>
+#include <gcrypt.h>
+
+#include <assuan.h> /* malloc hooks */
+
+#include "i18n.h"
+#include "sysutils.h"
+
+
+
+enum cmd_and_opt_values
+{ aNull = 0,
+ oCsh = 'c',
+ oQuiet = 'q',
+ oSh = 's',
+ oVerbose = 'v',
+
+ oNoVerbose = 500,
+ oOptions,
+ oDebug,
+ oDebugAll,
+ oDebugWait,
+ oDebugSC,
+ oNoGreeting,
+ oNoOptions,
+ oHomedir,
+ oNoDetach,
+ oNoGrab,
+ oLogFile,
+ oServer,
+ oDaemon,
+ oBatch,
+ oReaderPort,
+
+aTest };
+
+
+
+static ARGPARSE_OPTS opts[] = {
+
+ { 301, NULL, 0, N_("@Options:\n ") },
+
+ { oServer, "server", 0, N_("run in server mode (foreground)") },
+ { oDaemon, "daemon", 0, N_("run in daemon mode (background)") },
+ { oVerbose, "verbose", 0, N_("verbose") },
+ { oQuiet, "quiet", 0, N_("be somewhat more quiet") },
+ { oSh, "sh", 0, N_("sh-style command output") },
+ { oCsh, "csh", 0, N_("csh-style command output") },
+ { oOptions, "options" , 2, N_("read options from file")},
+ { oDebug, "debug" ,4|16, N_("set debugging flags")},
+ { oDebugAll, "debug-all" ,0, N_("enable full debugging")},
+ { oDebugWait,"debug-wait",1, "@"},
+ { oDebugSC, "debug-sc", 1, N_("|N|set OpenSC debug level to N")},
+ { oNoDetach, "no-detach" ,0, N_("do not detach from the console")},
+ { oLogFile, "log-file" ,2, N_("use a log file for the server")},
+ { oReaderPort, "reader-port", 1, N_("|N|connect to reader at port N")},
+
+ {0}
+};
+
+
+static volatile int caught_fatal_sig = 0;
+
+/* It is possible that we are currently running under setuid permissions */
+static int maybe_setuid = 1;
+
+/* Name of the communication socket */
+static char socket_name[128];
+
+static const char *
+my_strusage (int level)
+{
+ const char *p;
+ switch (level)
+ {
+ case 11: p = "scdaemon (GnuPG)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
+ break;
+ case 1:
+ case 40: p = _("Usage: scdaemon [options] (-h for help)");
+ break;
+ case 41: p = _("Syntax: scdaemon [options] [command [args]]\n"
+ "Smartcard daemon for GnuPG\n");
+ break;
+
+ default: p = NULL;
+ }
+ return p;
+}
+
+
+
+static void
+i18n_init (void)
+{
+#ifdef USE_SIMPLE_GETTEXT
+ set_gettext_file( PACKAGE );
+#else
+#ifdef ENABLE_NLS
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+#endif
+#endif
+}
+
+
+
+/* Used by gcry for logging */
+static void
+my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr)
+{
+ /* translate the log levels */
+ switch (level)
+ {
+ case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break;
+ case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break;
+ case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break;
+ case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break;
+ case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break;
+ case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break;
+ case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break;
+ default: level = JNLIB_LOG_ERROR; break;
+ }
+ log_logv (level, fmt, arg_ptr);
+}
+
+
+static void
+cleanup (void)
+{
+ if (*socket_name)
+ {
+ char *p;
+
+ remove (socket_name);
+ p = strrchr (socket_name, '/');
+ if (p)
+ {
+ *p = 0;
+ rmdir (socket_name);
+ *p = '/';
+ }
+ *socket_name = 0;
+ }
+}
+
+
+static RETSIGTYPE
+cleanup_sh (int sig)
+{
+ if (caught_fatal_sig)
+ raise (sig);
+ caught_fatal_sig = 1;
+
+ /* gcry_control( GCRYCTL_TERM_SECMEM );*/
+ cleanup ();
+
+#ifndef HAVE_DOSISH_SYSTEM
+ { /* reset action to default action and raise signal again */
+ struct sigaction nact;
+ nact.sa_handler = SIG_DFL;
+ sigemptyset( &nact.sa_mask );
+ nact.sa_flags = 0;
+ sigaction( sig, &nact, NULL);
+ }
+#endif
+ raise( sig );
+}
+
+int
+main (int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ int orig_argc;
+ int may_coredump;
+ char **orig_argv;
+ FILE *configfp = NULL;
+ char *configname = NULL;
+ const char *shell;
+ unsigned configlineno;
+ int parse_debug = 0;
+ int default_config =1;
+ int greeting = 0;
+ int nogreeting = 0;
+ int pipe_server = 0;
+ int is_daemon = 0;
+ int nodetach = 0;
+ int csh_style = 0;
+ char *logfile = NULL;
+ int debug_wait = 0;
+ int reader_port = 32768; /* First USB reader. */
+
+ set_strusage (my_strusage);
+ gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+ /* Please note that we may running SUID(ROOT), so be very CAREFUL
+ when adding any stuff between here and the call to INIT_SECMEM()
+ somewhere after the option parsing */
+ log_set_prefix ("scdaemon", 1|4);
+ i18n_init ();
+
+ /* check that the libraries are suitable. Do it here because
+ the option parsing may need services of the library */
+ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
+ {
+ log_fatal( _("libgcrypt is too old (need %s, have %s)\n"),
+ NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
+ }
+
+ ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
+ assuan_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
+ gcry_set_log_handler (my_gcry_logger, NULL);
+ gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
+
+ may_coredump = disable_core_dumps ();
+
+ shell = getenv ("SHELL");
+ if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
+ csh_style = 1;
+
+ /* FIXME: Using this homedir option does only make sense when not
+ running as a system service. We might want to check for this by
+ looking at the uid or ebtter use an explict option for this */
+ opt.homedir = getenv("GNUPGHOME");
+ if (!opt.homedir || !*opt.homedir)
+ opt.homedir = GNUPG_DEFAULT_HOMEDIR;
+
+ /* check whether we have a config file on the commandline */
+ orig_argc = argc;
+ orig_argv = argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
+ while (arg_parse( &pargs, opts))
+ {
+ if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
+ parse_debug++;
+ else if (pargs.r_opt == oOptions)
+ { /* yes there is one, so we do not try the default one, but
+ read the option file when it is encountered at the
+ commandline */
+ default_config = 0;
+ }
+ else if (pargs.r_opt == oNoOptions)
+ default_config = 0; /* --no-options */
+ else if (pargs.r_opt == oHomedir)
+ opt.homedir = pargs.r.ret_str;
+ }
+
+ /* initialize the secure memory. */
+ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+ maybe_setuid = 0;
+
+ /*
+ Now we are working under our real uid
+ */
+
+
+ if (default_config)
+ configname = make_filename (opt.homedir, "scdaemon.conf", NULL );
+
+ argc = orig_argc;
+ argv = orig_argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1; /* do not remove the args */
+ next_pass:
+ if (configname)
+ {
+ configlineno = 0;
+ configfp = fopen (configname, "r");
+ if (!configfp)
+ {
+ if (default_config)
+ {
+ if( parse_debug )
+ log_info (_("NOTE: no default option file `%s'\n"),
+ configname );
+ }
+ else
+ {
+ log_error (_("option file `%s': %s\n"),
+ configname, strerror(errno) );
+ exit(2);
+ }
+ xfree (configname);
+ configname = NULL;
+ }
+ if (parse_debug && configname )
+ log_info (_("reading options from `%s'\n"), configname );
+ default_config = 0;
+ }
+
+ while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
+ {
+ switch (pargs.r_opt)
+ {
+ case oQuiet: opt.quiet = 1; break;
+ case oVerbose: opt.verbose++; break;
+ case oBatch: opt.batch=1; break;
+
+ case oDebug: opt.debug |= pargs.r.ret_ulong; break;
+ case oDebugAll: opt.debug = ~0; break;
+ case oDebugWait: debug_wait = pargs.r.ret_int; break;
+ case oDebugSC: opt.debug_sc = pargs.r.ret_int; break;
+
+ case oOptions:
+ /* config files may not be nested (silently ignore them) */
+ if (!configfp)
+ {
+ xfree(configname);
+ configname = xstrdup(pargs.r.ret_str);
+ goto next_pass;
+ }
+ break;
+ case oNoGreeting: nogreeting = 1; break;
+ case oNoVerbose: opt.verbose = 0; break;
+ case oNoOptions: break; /* no-options */
+ case oHomedir: opt.homedir = pargs.r.ret_str; break;
+ case oNoDetach: nodetach = 1; break;
+ case oLogFile: logfile = pargs.r.ret_str; break;
+ case oCsh: csh_style = 1; break;
+ case oSh: csh_style = 0; break;
+ case oServer: pipe_server = 1; break;
+ case oDaemon: is_daemon = 1; break;
+
+ case oReaderPort: reader_port = pargs.r.ret_int; break;
+
+ default : pargs.err = configfp? 1:2; break;
+ }
+ }
+ if (configfp)
+ {
+ fclose( configfp );
+ configfp = NULL;
+ xfree(configname);
+ configname = NULL;
+ goto next_pass;
+ }
+ xfree (configname);
+ configname = NULL;
+ if (log_get_errorcount(0))
+ exit(2);
+ if (nogreeting )
+ greeting = 0;
+
+ if (greeting)
+ {
+ fprintf (stderr, "%s %s; %s\n",
+ strusage(11), strusage(13), strusage(14) );
+ fprintf (stderr, "%s\n", strusage(15) );
+ }
+#ifdef IS_DEVELOPMENT_VERSION
+ log_info ("NOTE: this is a development version!\n");
+#endif
+
+
+ if (atexit (cleanup))
+ {
+ log_error ("atexit failed\n");
+ cleanup ();
+ exit (1);
+ }
+
+
+ if (debug_wait && pipe_server)
+ {
+ log_debug ("waiting for debugger - my pid is %u .....\n",
+ (unsigned int)getpid());
+ sleep (debug_wait);
+ log_debug ("... okay\n");
+ }
+
+ /* now start with logging to a file if this is desired */
+ if (logfile)
+ {
+ log_set_file (logfile);
+ log_set_prefix (NULL, 1|2|4);
+ }
+
+
+ if (pipe_server)
+ { /* this is the simple pipe based server */
+ scd_command_handler (-1);
+ }
+ else if (!is_daemon)
+ {
+ log_info (_("please use the option `--daemon'"
+ " to run the program in the background\n"));
+ }
+ else
+ { /* regular server mode */
+ int fd;
+ pid_t pid;
+ int i;
+ int len;
+ struct sockaddr_un serv_addr;
+ char *p;
+
+ /* fixme: if there is already a running gpg-agent we should
+ share the same directory - and vice versa */
+ *socket_name = 0;
+ snprintf (socket_name, DIM(socket_name)-1,
+ "/tmp/gpg-XXXXXX/S.scdaemon");
+ socket_name[DIM(socket_name)-1] = 0;
+ p = strrchr (socket_name, '/');
+ if (!p)
+ BUG ();
+ *p = 0;;
+ if (!mkdtemp(socket_name))
+ {
+ log_error ("can't create directory `%s': %s\n",
+ socket_name, strerror(errno) );
+ exit (1);
+ }
+ *p = '/';
+
+ if (strchr (socket_name, ':') )
+ {
+ log_error ("colons are not allowed in the socket name\n");
+ exit (1);
+ }
+ if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path )
+ {
+ log_error ("name of socket to long\n");
+ exit (1);
+ }
+
+
+ fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ {
+ log_error ("can't create socket: %s\n", strerror(errno) );
+ exit (1);
+ }
+
+ memset (&serv_addr, 0, sizeof serv_addr);
+ serv_addr.sun_family = AF_UNIX;
+ strcpy (serv_addr.sun_path, socket_name);
+ len = (offsetof (struct sockaddr_un, sun_path)
+ + strlen(serv_addr.sun_path) + 1);
+
+ if (bind (fd, (struct sockaddr*)&serv_addr, len) == -1)
+ {
+ log_error ("error binding socket to `%s': %s\n",
+ serv_addr.sun_path, strerror (errno) );
+ close (fd);
+ exit (1);
+ }
+
+ if (listen (fd, 5 ) == -1)
+ {
+ log_error ("listen() failed: %s\n", strerror (errno));
+ close (fd);
+ exit (1);
+ }
+
+ if (opt.verbose)
+ log_info ("listening on socket `%s'\n", socket_name );
+
+
+ fflush (NULL);
+ pid = fork ();
+ if (pid == (pid_t)-1)
+ {
+ log_fatal ("fork failed: %s\n", strerror (errno) );
+ exit (1);
+ }
+ else if (pid)
+ { /* we are the parent */
+ char *infostr;
+
+ close (fd);
+
+ /* create the info string: <name>:<pid>:<protocol_version> */
+ if (asprintf (&infostr, "SCDAEMON_INFO=%s:%lu:1",
+ socket_name, (ulong)pid ) < 0)
+ {
+ log_error ("out of core\n");
+ kill (pid, SIGTERM);
+ exit (1);
+ }
+ *socket_name = 0; /* don't let cleanup() remove the socket -
+ the child should do this from now on */
+ if (argc)
+ { /* run the program given on the commandline */
+ if (putenv (infostr))
+ {
+ log_error ("failed to set environment: %s\n",
+ strerror (errno) );
+ kill (pid, SIGTERM );
+ exit (1);
+ }
+ execvp (argv[0], argv);
+ log_error ("failed to run the command: %s\n", strerror (errno));
+ kill (pid, SIGTERM);
+ exit (1);
+ }
+ else
+ {
+ /* print the environment string, so that the caller can use
+ shell's eval to set it */
+ if (csh_style)
+ {
+ *strchr (infostr, '=') = ' ';
+ printf ( "setenv %s\n", infostr);
+ }
+ else
+ {
+ printf ( "%s; export SCDAEMON_INFO;\n", infostr);
+ }
+ free (infostr);
+ exit (0);
+ }
+ /* NOTREACHED */
+ } /* end parent */
+
+ /* this is the child */
+
+ /* detach from tty and put process into a new session */
+ if (!nodetach )
+ { /* close stdin, stdout and stderr unless it is the log stream */
+ for (i=0; i <= 2; i++)
+ {
+ if ( log_get_fd () != i)
+ close (i);
+ }
+ if (setsid() == -1)
+ {
+ log_error ("setsid() failed: %s\n", strerror(errno) );
+ cleanup ();
+ exit (1);
+ }
+ }
+
+ /* setup signals */
+ {
+ struct sigaction oact, nact;
+
+ nact.sa_handler = cleanup_sh;
+ sigemptyset (&nact.sa_mask);
+ nact.sa_flags = 0;
+
+ sigaction (SIGHUP, NULL, &oact);
+ if (oact.sa_handler != SIG_IGN)
+ sigaction (SIGHUP, &nact, NULL);
+ sigaction( SIGTERM, NULL, &oact );
+ if (oact.sa_handler != SIG_IGN)
+ sigaction (SIGTERM, &nact, NULL);
+ nact.sa_handler = SIG_IGN;
+ sigaction (SIGPIPE, &nact, NULL);
+ sigaction (SIGINT, &nact, NULL);
+ }
+
+ if (chdir("/"))
+ {
+ log_error ("chdir to / failed: %s\n", strerror (errno));
+ exit (1);
+ }
+
+ scd_command_handler (fd);
+
+ close (fd);
+ }
+
+ return 0;
+}
+
+void
+scd_exit (int rc)
+{
+ #if 0
+#warning no update_random_seed_file
+ update_random_seed_file();
+ #endif
+#if 0
+ /* at this time a bit annoying */
+ if (opt.debug & DBG_MEMSTAT_VALUE)
+ {
+ gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
+ gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
+ }
+ if (opt.debug)
+ gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
+#endif
+ gcry_control (GCRYCTL_TERM_SECMEM );
+ rc = rc? rc : log_get_errorcount(0)? 2 : 0;
+ exit (rc);
+}
+
+
+void
+scd_init_default_ctrl (CTRL ctrl)
+{
+
+}
+
diff --git a/scd/scdaemon.h b/scd/scdaemon.h
new file mode 100644
index 000000000..b21e19f8c
--- /dev/null
+++ b/scd/scdaemon.h
@@ -0,0 +1,127 @@
+/* scdaemon.h - Global definitions for the SCdaemon
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef SCDAEMON_H
+#define SCDAEMON_H
+
+#ifdef GPG_ERR_SOURCE_DEFAULT
+#error GPG_ERR_SOURCE_DEFAULT already defined
+#endif
+#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_SCD
+#include <gpg-error.h>
+#include <errno.h>
+
+#include <time.h>
+#include <gcrypt.h>
+#include "../common/util.h"
+#include "../common/errors.h"
+
+/* Convenience funcion to be used instead of returning the old
+ GNUPG_Out_Of_Core. */
+static __inline__ gpg_error_t
+out_of_core (void)
+{
+ return gpg_error (gpg_err_code_from_errno (errno));
+}
+
+
+#define MAX_DIGEST_LEN 24
+
+/* A large struct name "opt" to keep global flags */
+struct {
+ unsigned int debug; /* debug flags (DBG_foo_VALUE) */
+ int debug_sc; /* OpenSC debug level */
+ int verbose; /* verbosity level */
+ int quiet; /* be as quiet as possible */
+ int dry_run; /* don't change any persistent data */
+ int batch; /* batch mode */
+ const char *homedir; /* configuration directory name */
+} opt;
+
+
+#define DBG_COMMAND_VALUE 1 /* debug commands i/o */
+#define DBG_MPI_VALUE 2 /* debug mpi details */
+#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
+#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
+#define DBG_CACHE_VALUE 64 /* debug the caching */
+#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
+#define DBG_HASHING_VALUE 512 /* debug hashing operations */
+#define DBG_ASSUAN_VALUE 1024
+#define DBG_CARD_IO_VALUE 2048
+
+#define DBG_COMMAND (opt.debug & DBG_COMMAND_VALUE)
+#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
+#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE)
+#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
+#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
+#define DBG_ASSUAN (opt.debug & DBG_ASSUAN_VALUE)
+#define DBG_CARD_IO (opt.debug & DBG_CARD_IO_VALUE)
+
+struct server_local_s;
+struct card_ctx_s;
+struct app_ctx_s;
+
+struct server_control_s {
+ struct server_local_s *server_local;
+ struct card_ctx_s *card_ctx;
+ struct app_ctx_s *app_ctx;
+ struct {
+ unsigned char *value;
+ int valuelen;
+ } in_data; /* helper to store the value we are going to sign */
+
+};
+
+typedef struct server_control_s *CTRL;
+typedef struct card_ctx_s *CARD;
+typedef struct app_ctx_s *APP;
+
+/*-- scdaemon.c --*/
+void scd_exit (int rc);
+void scd_init_default_ctrl (CTRL ctrl);
+
+/*-- command.c --*/
+void scd_command_handler (int);
+void send_status_info (CTRL ctrl, const char *keyword, ...);
+
+/*-- card.c --*/
+int card_open (CARD *rcard);
+void card_close (CARD card);
+int card_get_serial_and_stamp (CARD card, char **serial, time_t *stamp);
+int card_enum_keypairs (CARD card, int idx,
+ unsigned char *keygrip,
+ char **keyid);
+int card_enum_certs (CARD card, int idx, char **certid, int *certtype);
+int card_read_cert (CARD card, const char *certidstr,
+ unsigned char **cert, size_t *ncert);
+int card_sign (CARD card,
+ const char *keyidstr, int hashalgo,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen );
+int card_decipher (CARD card, const char *keyidstr,
+ int (pincb)(void*, const char *, char **),
+ void *pincb_arg,
+ const void *indata, size_t indatalen,
+ unsigned char **outdata, size_t *outdatalen);
+
+
+#endif /*SCDAEMON_H*/
diff --git a/sm/ChangeLog b/sm/ChangeLog
new file mode 100644
index 000000000..59a6b3271
--- /dev/null
+++ b/sm/ChangeLog
@@ -0,0 +1,816 @@
+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 Free Software Foundation, Inc.
+
+ This file is free software; as a special exception the author gives
+ unlimited permission to copy and/or distribute it, with or without
+ modifications, as long as this notice is preserved.
+
+ This file is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/sm/Makefile.am b/sm/Makefile.am
new file mode 100644
index 000000000..6345573e5
--- /dev/null
+++ b/sm/Makefile.am
@@ -0,0 +1,55 @@
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+## Process this file with automake to produce Makefile.in
+
+localedir = $(datadir)/locale
+INCLUDES = -I../intl -DLOCALEDIR=\"$(localedir)\"
+
+bin_PROGRAMS = gpgsm
+
+AM_CPPFLAGS = -I$(top_srcdir)/common -I$(top_srcdir)/intl \
+ $(LIBGCRYPT_CFLAGS) $(LIBASSUAN_CFLAGS) $(KSBA_CFLAGS)
+
+gpgsm_SOURCES = \
+ gpgsm.c gpgsm.h \
+ misc.c \
+ keydb.c keydb.h \
+ server.c \
+ call-agent.c \
+ call-dirmngr.c \
+ fingerprint.c \
+ base64.c \
+ certlist.c \
+ certdump.c \
+ certcheck.c \
+ certchain.c \
+ keylist.c \
+ verify.c \
+ sign.c \
+ encrypt.c \
+ decrypt.c \
+ import.c \
+ export.c \
+ delete.c \
+ certreqgen.c
+
+
+gpgsm_LDADD = ../jnlib/libjnlib.a ../kbx/libkeybox.a ../common/libcommon.a \
+ $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(KSBA_LIBS) -lgpg-error \
+ @INTLLIBS@
diff --git a/sm/call-agent.c b/sm/call-agent.c
new file mode 100644
index 000000000..4d26e3450
--- /dev/null
+++ b/sm/call-agent.c
@@ -0,0 +1,713 @@
+/* call-agent.c - divert operations to the agent
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <assuan.h>
+#include "i18n.h"
+#include "keydb.h" /* fixme: Move this to import.c */
+#include "../common/membuf.h"
+
+
+static ASSUAN_CONTEXT agent_ctx = NULL;
+static int force_pipe_server = 0;
+
+struct cipher_parm_s {
+ ASSUAN_CONTEXT ctx;
+ const char *ciphertext;
+ size_t ciphertextlen;
+};
+
+struct genkey_parm_s {
+ ASSUAN_CONTEXT ctx;
+ const char *sexp;
+ size_t sexplen;
+};
+
+struct learn_parm_s {
+ int error;
+ ASSUAN_CONTEXT ctx;
+ membuf_t *data;
+};
+
+
+
+/* Try to connect to the agent via socket or fork it off and work by
+ pipes. Handle the server's initial greeting */
+static int
+start_agent (void)
+{
+ int rc = 0;
+ char *infostr, *p;
+ ASSUAN_CONTEXT ctx;
+ char *dft_display = NULL;
+ char *dft_ttyname = NULL;
+ char *dft_ttytype = NULL;
+ char *old_lc = NULL;
+ char *dft_lc = NULL;
+
+ if (agent_ctx)
+ return 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 */
+
+ infostr = force_pipe_server? NULL : getenv ("GPG_AGENT_INFO");
+ if (!infostr)
+ {
+ const char *pgmname;
+ const char *argv[3];
+ int no_close_list[3];
+ int i;
+
+ if (opt.verbose)
+ log_info (_("no running gpg-agent - starting one\n"));
+
+ if (fflush (NULL))
+ {
+ gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("error flushing pending output: %s\n", strerror (errno));
+ return tmperr;
+ }
+
+ if (!opt.agent_program || !*opt.agent_program)
+ opt.agent_program = GNUPG_DEFAULT_AGENT;
+ if ( !(pgmname = strrchr (opt.agent_program, '/')))
+ pgmname = opt.agent_program;
+ else
+ pgmname++;
+
+ argv[0] = pgmname;
+ argv[1] = "--server";
+ argv[2] = NULL;
+
+ i=0;
+ if (log_get_fd () != -1)
+ no_close_list[i++] = log_get_fd ();
+ no_close_list[i++] = fileno (stderr);
+ no_close_list[i] = -1;
+
+ /* connect to the agent and perform initial handshaking */
+ rc = assuan_pipe_connect (&ctx, opt.agent_program, (char**)argv,
+ no_close_list);
+ }
+ else
+ {
+ int prot;
+ int pid;
+
+ infostr = xstrdup (infostr);
+ if ( !(p = strchr (infostr, ':')) || p == infostr)
+ {
+ log_error (_("malformed GPG_AGENT_INFO environment variable\n"));
+ xfree (infostr);
+ force_pipe_server = 1;
+ return start_agent ();
+ }
+ *p++ = 0;
+ pid = atoi (p);
+ while (*p && *p != ':')
+ p++;
+ prot = *p? atoi (p+1) : 0;
+ if (prot != 1)
+ {
+ log_error (_("gpg-agent protocol version %d is not supported\n"),
+ prot);
+ xfree (infostr);
+ force_pipe_server = 1;
+ return start_agent ();
+ }
+
+ rc = assuan_socket_connect (&ctx, infostr, pid);
+ xfree (infostr);
+ if (rc == ASSUAN_Connect_Failed)
+ {
+ log_error (_("can't connect to the agent - trying fall back\n"));
+ force_pipe_server = 1;
+ return start_agent ();
+ }
+ }
+
+ if (rc)
+ {
+ log_error ("can't connect to the agent: %s\n", assuan_strerror (rc));
+ return gpg_error (GPG_ERR_NO_AGENT);
+ }
+ agent_ctx = ctx;
+
+ if (DBG_ASSUAN)
+ log_debug ("connection to agent established\n");
+
+ rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return map_assuan_err (rc);
+
+ dft_display = getenv ("DISPLAY");
+ if (opt.display || dft_display)
+ {
+ char *optstr;
+ if (asprintf (&optstr, "OPTION display=%s",
+ opt.display ? opt.display : dft_display) < 0)
+ return OUT_OF_CORE (errno);
+ rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ free (optstr);
+ if (rc)
+ return map_assuan_err (rc);
+ }
+ if (!opt.ttyname)
+ {
+ dft_ttyname = getenv ("GPG_TTY");
+ if ((!dft_ttyname || !*dft_ttyname) && ttyname (0))
+ dft_ttyname = ttyname (0);
+ }
+ if (opt.ttyname || dft_ttyname)
+ {
+ char *optstr;
+ if (asprintf (&optstr, "OPTION ttyname=%s",
+ opt.ttyname ? opt.ttyname : dft_ttyname) < 0)
+ return OUT_OF_CORE (errno);
+ rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ free (optstr);
+ if (rc)
+ return map_assuan_err (rc);
+ }
+ dft_ttytype = getenv ("TERM");
+ if (opt.ttytype || (dft_ttyname && dft_ttytype))
+ {
+ char *optstr;
+ if (asprintf (&optstr, "OPTION ttytype=%s",
+ opt.ttyname ? opt.ttytype : dft_ttytype) < 0)
+ return OUT_OF_CORE (errno);
+ rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ free (optstr);
+ if (rc)
+ return map_assuan_err (rc);
+ }
+#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE)
+ old_lc = setlocale (LC_CTYPE, NULL);
+ if (old_lc)
+ {
+ old_lc = strdup (old_lc);
+ if (!old_lc)
+ return OUT_OF_CORE (errno);
+ }
+ dft_lc = setlocale (LC_CTYPE, "");
+#endif
+ if (opt.lc_ctype || (dft_ttyname && dft_lc))
+ {
+ char *optstr;
+ if (asprintf (&optstr, "OPTION lc-ctype=%s",
+ opt.lc_ctype ? opt.lc_ctype : dft_lc) < 0)
+ rc = OUT_OF_CORE (errno);
+ else
+ {
+ rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ free (optstr);
+ if (rc)
+ rc = map_assuan_err (rc);
+ }
+ }
+#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE)
+ if (old_lc)
+ {
+ setlocale (LC_CTYPE, old_lc);
+ free (old_lc);
+ }
+#endif
+ if (rc)
+ return rc;
+#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES)
+ old_lc = setlocale (LC_MESSAGES, NULL);
+ if (old_lc)
+ {
+ old_lc = strdup (old_lc);
+ if (!old_lc)
+ return OUT_OF_CORE (errno);
+ }
+ dft_lc = setlocale (LC_MESSAGES, "");
+#endif
+ if (opt.lc_messages || (dft_ttyname && dft_lc))
+ {
+ char *optstr;
+ if (asprintf (&optstr, "OPTION lc-messages=%s",
+ opt.lc_messages ? opt.lc_messages : dft_lc) < 0)
+ rc = OUT_OF_CORE (errno);
+ else
+ {
+ rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
+ NULL);
+ free (optstr);
+ if (rc)
+ rc = map_assuan_err (rc);
+ }
+ }
+#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES)
+ if (old_lc)
+ {
+ setlocale (LC_MESSAGES, old_lc);
+ free (old_lc);
+ }
+#endif
+
+ return rc;
+}
+
+
+static AssuanError
+membuf_data_cb (void *opaque, const void *buffer, size_t length)
+{
+ membuf_t *data = opaque;
+
+ if (buffer)
+ put_membuf (data, buffer, length);
+ return 0;
+}
+
+
+
+
+/* Call the agent to do a sign operation using the key identified by
+ the hex string KEYGRIP. */
+int
+gpgsm_agent_pksign (const char *keygrip,
+ unsigned char *digest, size_t digestlen, int digestalgo,
+ char **r_buf, size_t *r_buflen )
+{
+ int rc, i;
+ char *p, line[ASSUAN_LINELENGTH];
+ membuf_t data;
+ size_t len;
+
+ *r_buf = NULL;
+ rc = start_agent ();
+ if (rc)
+ return rc;
+
+ if (digestlen*2 + 50 > DIM(line))
+ return gpg_error (GPG_ERR_GENERAL);
+
+ rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return map_assuan_err (rc);
+
+ snprintf (line, DIM(line)-1, "SIGKEY %s", keygrip);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return map_assuan_err (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 map_assuan_err (rc);
+
+ init_membuf (&data, 1024);
+ rc = assuan_transact (agent_ctx, "PKSIGN",
+ membuf_data_cb, &data, NULL, NULL, NULL, NULL);
+ if (rc)
+ {
+ xfree (get_membuf (&data, &len));
+ return map_assuan_err (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 (errno);
+}
+
+
+
+
+/* Handle a CIPHERTEXT inquiry. Note, we only send the data,
+ assuan_transact talkes care of flushing and writing the end */
+static AssuanError
+inq_ciphertext_cb (void *opaque, const char *keyword)
+{
+ struct cipher_parm_s *parm = opaque;
+ AssuanError rc;
+
+ assuan_begin_confidential (parm->ctx);
+ rc = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen);
+ assuan_end_confidential (parm->ctx);
+ return rc;
+}
+
+
+/* Call the agent to do a decrypt operation using the key identified by
+ the hex string KEYGRIP. */
+int
+gpgsm_agent_pkdecrypt (const char *keygrip,
+ KsbaConstSexp 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 *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 ();
+ if (rc)
+ return rc;
+
+ rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return map_assuan_err (rc);
+
+ assert ( DIM(line) >= 50 );
+ snprintf (line, DIM(line)-1, "SETKEY %s", keygrip);
+ line[DIM(line)-1] = 0;
+ rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return map_assuan_err (rc);
+
+ init_membuf (&data, 1024);
+ cipher_parm.ctx = agent_ctx;
+ cipher_parm.ciphertext = ciphertext;
+ cipher_parm.ciphertextlen = ciphertextlen;
+ rc = assuan_transact (agent_ctx, "PKDECRYPT",
+ membuf_data_cb, &data,
+ inq_ciphertext_cb, &cipher_parm, NULL, NULL);
+ if (rc)
+ {
+ xfree (get_membuf (&data, &len));
+ return map_assuan_err (rc);
+ }
+
+ put_membuf (&data, "", 1); /* make sure it is 0 terminated */
+ buf = get_membuf (&data, &len);
+ if (!buf)
+ return gpg_error (GPG_ERR_ENOMEM);
+ /* FIXME: We would better a return a full S-exp and not just a part */
+ assert (len);
+ len--; /* remove the terminating 0 */
+ n = strtoul (buf, &endp, 10);
+ if (!n || *endp != ':')
+ return gpg_error (GPG_ERR_INV_SEXP);
+ endp++;
+ if (endp-buf+n > len)
+ return gpg_error (GPG_ERR_INV_SEXP); /* oops len does not
+ match internal len*/
+ 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 AssuanError
+inq_genkey_parms (void *opaque, const char *keyword)
+{
+ struct genkey_parm_s *parm = opaque;
+ AssuanError rc;
+
+ rc = assuan_send_data (parm->ctx, parm->sexp, parm->sexplen);
+ return rc;
+}
+
+
+
+/* Call the agent to generate a newkey */
+int
+gpgsm_agent_genkey (KsbaConstSexp keyparms, KsbaSexp *r_pubkey)
+{
+ int rc;
+ struct genkey_parm_s gk_parm;
+ membuf_t data;
+ size_t len;
+ char *buf;
+
+ *r_pubkey = NULL;
+ rc = start_agent ();
+ if (rc)
+ return rc;
+
+ rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
+ if (rc)
+ return map_assuan_err (rc);
+
+ init_membuf (&data, 1024);
+ gk_parm.ctx = agent_ctx;
+ gk_parm.sexp = keyparms;
+ gk_parm.sexplen = gcry_sexp_canon_len (keyparms, 0, NULL, NULL);
+ if (!gk_parm.sexplen)
+ return gpg_error (GPG_ERR_INV_VALUE);
+ rc = assuan_transact (agent_ctx, "GENKEY",
+ membuf_data_cb, &data,
+ inq_genkey_parms, &gk_parm, NULL, NULL);
+ if (rc)
+ {
+ xfree (get_membuf (&data, &len));
+ return map_assuan_err (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;
+}
+
+
+/* Ask the agent whether the certificate is in the list of trusted
+ keys */
+int
+gpgsm_agent_istrusted (KsbaCert cert)
+{
+ int rc;
+ char *fpr;
+ char line[ASSUAN_LINELENGTH];
+
+ rc = start_agent ();
+ if (rc)
+ return rc;
+
+ fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
+ if (!fpr)
+ {
+ log_error ("error getting the fingerprint\n");
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+
+ snprintf (line, DIM(line)-1, "ISTRUSTED %s", fpr);
+ line[DIM(line)-1] = 0;
+ xfree (fpr);
+
+ rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ return map_assuan_err (rc);
+}
+
+/* Ask the agent to mark CERT as a trusted Root-CA one */
+int
+gpgsm_agent_marktrusted (KsbaCert cert)
+{
+ int rc;
+ char *fpr, *dn;
+ char line[ASSUAN_LINELENGTH];
+
+ rc = start_agent ();
+ if (rc)
+ return rc;
+
+ fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
+ if (!fpr)
+ {
+ log_error ("error getting the fingerprint\n");
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+
+ dn = ksba_cert_get_issuer (cert, 0);
+ if (!dn)
+ {
+ xfree (fpr);
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+ snprintf (line, DIM(line)-1, "MARKTRUSTED %s S %s", fpr, dn);
+ line[DIM(line)-1] = 0;
+ ksba_free (dn);
+ xfree (fpr);
+
+ rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ return map_assuan_err (rc);
+}
+
+
+
+/* Ask the agent whether the a corresponding secret key is available
+ for the given keygrip */
+int
+gpgsm_agent_havekey (const char *hexkeygrip)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+
+ rc = start_agent ();
+ if (rc)
+ return rc;
+
+ if (!hexkeygrip || strlen (hexkeygrip) != 40)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ snprintf (line, DIM(line)-1, "HAVEKEY %s", hexkeygrip);
+ line[DIM(line)-1] = 0;
+
+ rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ return map_assuan_err (rc);
+}
+
+
+static AssuanError
+learn_cb (void *opaque, const void *buffer, size_t length)
+{
+ struct learn_parm_s *parm = opaque;
+ size_t len;
+ char *buf;
+ KsbaCert 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;
+ }
+
+
+ /* FIXME: this should go into import.c */
+ cert = ksba_cert_new ();
+ if (!cert)
+ {
+ parm->error = gpg_error (GPG_ERR_ENOMEM);
+ return 0;
+ }
+ rc = ksba_cert_init_from_mem (cert, buf, len);
+ if (rc)
+ {
+ log_error ("failed to parse a certificate: %s\n", ksba_strerror (rc));
+ ksba_cert_release (cert);
+ parm->error = map_ksba_err (rc);
+ return 0;
+ }
+
+ rc = gpgsm_basic_cert_check (cert);
+ if (gpg_err_code (rc) == GPG_ERR_MISSING_CERT)
+ { /* For later use we store it in the ephemeral database. */
+ log_info ("issuer certificate missing - storing as ephemeral\n");
+ keydb_store_cert (cert, 1, NULL);
+ }
+ else if (rc)
+ log_error ("invalid certificate: %s\n", gpg_strerror (rc));
+ else
+ {
+ int existed;
+
+ if (!keydb_store_cert (cert, 0, &existed))
+ {
+ if (opt.verbose > 1 && existed)
+ log_info ("certificate already in DB\n");
+ else if (opt.verbose && !existed)
+ log_info ("certificate imported\n");
+ }
+ }
+
+ ksba_cert_release (cert);
+ init_membuf (parm->data, 4096);
+ return 0;
+}
+
+/* Call the agent to learn about a smartcard */
+int
+gpgsm_agent_learn ()
+{
+ int rc;
+ struct learn_parm_s learn_parm;
+ membuf_t data;
+ size_t len;
+
+ rc = start_agent ();
+ if (rc)
+ return rc;
+
+ init_membuf (&data, 4096);
+ learn_parm.error = 0;
+ learn_parm.ctx = agent_ctx;
+ learn_parm.data = &data;
+ rc = assuan_transact (agent_ctx, "LEARN --send",
+ learn_cb, &learn_parm,
+ NULL, NULL, NULL, NULL);
+ xfree (get_membuf (&data, &len));
+ if (rc)
+ return map_assuan_err (rc);
+ return learn_parm.error;
+}
+
+
+/* Ask the agent to change the passphrase of the key identified by HEXKEYGRIP. */
+int
+gpgsm_agent_passwd (const char *hexkeygrip)
+{
+ int rc;
+ char line[ASSUAN_LINELENGTH];
+
+ rc = start_agent ();
+ if (rc)
+ return rc;
+
+ if (!hexkeygrip || strlen (hexkeygrip) != 40)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ snprintf (line, DIM(line)-1, "PASSWD %s", hexkeygrip);
+ line[DIM(line)-1] = 0;
+
+ rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
+ return map_assuan_err (rc);
+}
+
diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c
new file mode 100644
index 000000000..b182b246c
--- /dev/null
+++ b/sm/call-dirmngr.c
@@ -0,0 +1,632 @@
+/* call-dirmngr.c - communication with the dromngr
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <assuan.h>
+
+#include "i18n.h"
+
+struct membuf {
+ size_t len;
+ size_t size;
+ char *buf;
+ int out_of_core;
+};
+
+
+
+static ASSUAN_CONTEXT dirmngr_ctx = NULL;
+static int force_pipe_server = 0;
+
+struct inq_certificate_parm_s {
+ ASSUAN_CONTEXT ctx;
+ KsbaCert cert;
+};
+
+struct lookup_parm_s {
+ CTRL ctrl;
+ ASSUAN_CONTEXT ctx;
+ void (*cb)(void *, KsbaCert);
+ void *cb_value;
+ struct membuf data;
+ int error;
+};
+
+struct run_command_parm_s {
+ ASSUAN_CONTEXT ctx;
+};
+
+
+/* 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;
+}
+
+
+
+
+
+/* Try to connect to the agent via socket or fork it off and work by
+ pipes. Handle the server's initial greeting */
+static int
+start_dirmngr (void)
+{
+ int rc;
+ char *infostr, *p;
+ ASSUAN_CONTEXT ctx;
+
+ if (dirmngr_ctx)
+ return 0; /* fixme: We need a context for each thread or serialize
+ the access to the dirmngr */
+
+ infostr = force_pipe_server? NULL : getenv ("DIRMNGR_INFO");
+ if (!infostr)
+ {
+ const char *pgmname;
+ const char *argv[3];
+ int no_close_list[3];
+ int i;
+
+ if (opt.verbose)
+ log_info (_("no running dirmngr - starting one\n"));
+
+ if (fflush (NULL))
+ {
+ gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("error flushing pending output: %s\n", strerror (errno));
+ return tmperr;
+ }
+
+ if (!opt.dirmngr_program || !*opt.dirmngr_program)
+ opt.dirmngr_program = GNUPG_DEFAULT_DIRMNGR;
+ if ( !(pgmname = strrchr (opt.dirmngr_program, '/')))
+ pgmname = opt.dirmngr_program;
+ else
+ pgmname++;
+
+ argv[0] = pgmname;
+ argv[1] = "--server";
+ argv[2] = NULL;
+
+ i=0;
+ if (log_get_fd () != -1)
+ no_close_list[i++] = log_get_fd ();
+ no_close_list[i++] = fileno (stderr);
+ no_close_list[i] = -1;
+
+ /* connect to the agent and perform initial handshaking */
+ rc = assuan_pipe_connect (&ctx, opt.dirmngr_program, (char**)argv,
+ no_close_list);
+ }
+ else
+ {
+ int prot;
+ int pid;
+
+ infostr = xstrdup (infostr);
+ if ( !(p = strchr (infostr, ':')) || p == infostr)
+ {
+ log_error (_("malformed DIRMNGR_INFO environment variable\n"));
+ xfree (infostr);
+ force_pipe_server = 1;
+ return start_dirmngr ();
+ }
+ *p++ = 0;
+ pid = atoi (p);
+ while (*p && *p != ':')
+ p++;
+ prot = *p? atoi (p+1) : 0;
+ if (prot != 1)
+ {
+ log_error (_("dirmngr protocol version %d is not supported\n"),
+ prot);
+ xfree (infostr);
+ force_pipe_server = 1;
+ return start_dirmngr ();
+ }
+
+ rc = assuan_socket_connect (&ctx, infostr, pid);
+ xfree (infostr);
+ if (rc == ASSUAN_Connect_Failed)
+ {
+ log_error (_("can't connect to the dirmngr - trying fall back\n"));
+ force_pipe_server = 1;
+ return start_dirmngr ();
+ }
+ }
+
+ if (rc)
+ {
+ log_error ("can't connect to the dirmngr: %s\n", assuan_strerror (rc));
+ return gpg_error (GPG_ERR_NO_DIRMNGR);
+ }
+ dirmngr_ctx = ctx;
+
+ if (DBG_ASSUAN)
+ log_debug ("connection to dirmngr established\n");
+ return 0;
+}
+
+
+
+/* Handle a SENDCERT inquiry. */
+static AssuanError
+inq_certificate (void *opaque, const char *line)
+{
+ struct inq_certificate_parm_s *parm = opaque;
+ AssuanError rc;
+ const unsigned char *der;
+ size_t derlen;
+
+ if (!(!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8])))
+ {
+ log_error ("unsupported inquiry `%s'\n", line);
+ return ASSUAN_Inquire_Unknown;
+ }
+ line += 8;
+
+ if (!*line)
+ { /* send the current certificate */
+ der = ksba_cert_get_image (parm->cert, &derlen);
+ if (!der)
+ rc = ASSUAN_Inquire_Error;
+ else
+ rc = assuan_send_data (parm->ctx, der, derlen);
+ }
+ else
+ { /* send the given certificate */
+ int err;
+ KsbaCert cert;
+
+ err = gpgsm_find_cert (line, &cert);
+ if (err)
+ {
+ log_error ("certificate not found: %s\n", gpg_strerror (err));
+ rc = ASSUAN_Inquire_Error;
+ }
+ else
+ {
+ der = ksba_cert_get_image (cert, &derlen);
+ if (!der)
+ rc = ASSUAN_Inquire_Error;
+ else
+ rc = assuan_send_data (parm->ctx, der, derlen);
+ ksba_cert_release (cert);
+ }
+ }
+
+ return rc;
+}
+
+
+
+/* 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
+ */
+int
+gpgsm_dirmngr_isvalid (KsbaCert cert)
+{
+ int rc;
+ char *certid;
+ char line[ASSUAN_LINELENGTH];
+ struct inq_certificate_parm_s parm;
+
+ rc = start_dirmngr ();
+ if (rc)
+ return rc;
+
+ certid = gpgsm_get_certid (cert);
+ if (!certid)
+ {
+ log_error ("error getting the certificate ID\n");
+ 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\n", fpr);
+ xfree (fpr);
+ }
+
+ parm.ctx = dirmngr_ctx;
+ parm.cert = cert;
+
+ snprintf (line, DIM(line)-1, "ISVALID %s", certid);
+ line[DIM(line)-1] = 0;
+ xfree (certid);
+
+ rc = assuan_transact (dirmngr_ctx, line, NULL, NULL,
+ inq_certificate, &parm, NULL, NULL);
+ if (opt.verbose > 1)
+ log_info ("response of dirmngr: %s\n", rc? assuan_strerror (rc): "okay");
+ return map_assuan_err (rc);
+}
+
+
+
+/* Lookup helpers*/
+static AssuanError
+lookup_cb (void *opaque, const void *buffer, size_t length)
+{
+ struct lookup_parm_s *parm = opaque;
+ size_t len;
+ char *buf;
+ KsbaCert 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;
+ }
+
+ cert = ksba_cert_new ();
+ if (!cert)
+ {
+ parm->error = gpg_error (GPG_ERR_ENOMEM);
+ return 0;
+ }
+ rc = ksba_cert_init_from_mem (cert, buf, len);
+ if (rc)
+ {
+ log_error ("failed to parse a certificate: %s\n", ksba_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 names)
+{
+ STRLIST 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 (n=0, 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 AssuanError
+lookup_status_cb (void *opaque, const char *line)
+{
+ struct lookup_parm_s *parm = opaque;
+
+ if (!strncmp (line, "TRUNCATED", 9) && (line[9]==' ' || !line[9]))
+ {
+ if (parm->ctrl)
+ {
+ for (line +=9; *line == ' '; line++)
+ ;
+ gpgsm_status (parm->ctrl, STATUS_TRUNCATED, line);
+ }
+ }
+ return 0;
+}
+
+
+/* Run the Directroy Managers lookup command using the pattern
+ compiled from the strings given in NAMES. The caller must provide
+ the callback CB which will be passed cert by cert. Note that CTRL
+ is optional. */
+int
+gpgsm_dirmngr_lookup (CTRL ctrl, STRLIST names,
+ void (*cb)(void*, KsbaCert), void *cb_value)
+{
+ int rc;
+ char *pattern;
+ char line[ASSUAN_LINELENGTH];
+ struct lookup_parm_s parm;
+ size_t len;
+
+ rc = start_dirmngr ();
+ if (rc)
+ return rc;
+
+ pattern = pattern_from_strlist (names);
+ if (!pattern)
+ return OUT_OF_CORE (errno);
+ snprintf (line, DIM(line)-1, "LOOKUP %s", pattern);
+ line[DIM(line)-1] = 0;
+ xfree (pattern);
+
+ parm.ctrl = ctrl;
+ parm.ctx = dirmngr_ctx;
+ parm.cb = cb;
+ parm.cb_value = cb_value;
+ parm.error = 0;
+ init_membuf (&parm.data, 4096);
+
+ rc = assuan_transact (dirmngr_ctx, line, lookup_cb, &parm,
+ NULL, NULL, lookup_status_cb, &parm);
+ xfree (get_membuf (&parm.data, &len));
+ if (rc)
+ return map_assuan_err (rc);
+ return parm.error;
+}
+
+
+
+/* Run Command helpers*/
+
+/* Fairly simple callback to write all output of dirmngr to stdout. */
+static AssuanError
+run_command_cb (void *opaque, const void *buffer, size_t length)
+{
+ 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 AssuanError
+run_command_inq_cb (void *opaque, const char *line)
+{
+ struct run_command_parm_s *parm = opaque;
+ AssuanError rc = 0;
+
+ if ( !strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8]) )
+ { /* send the given certificate */
+ int err;
+ KsbaCert cert;
+ const unsigned char *der;
+ size_t derlen;
+
+ line += 8;
+ if (!*line)
+ return ASSUAN_Inquire_Error;
+
+ err = gpgsm_find_cert (line, &cert);
+ if (err)
+ {
+ log_error ("certificate not found: %s\n", gpg_strerror (err));
+ rc = ASSUAN_Inquire_Error;
+ }
+ else
+ {
+ der = ksba_cert_get_image (cert, &derlen);
+ if (!der)
+ rc = ASSUAN_Inquire_Error;
+ else
+ rc = assuan_send_data (parm->ctx, der, derlen);
+ ksba_cert_release (cert);
+ }
+ }
+ else if ( !strncmp (line, "PRINTINFO", 9) && (line[9] == ' ' || !line[9]) )
+ { /* Simply show the message given in the argument. */
+ line += 9;
+ log_info ("dirmngr: %s\n", line);
+ }
+ else
+ {
+ log_error ("unsupported inquiry `%s'\n", line);
+ rc = ASSUAN_Inquire_Unknown;
+ }
+
+ return rc;
+}
+
+static AssuanError
+run_command_status_cb (void *opaque, const char *line)
+{
+ if (opt.verbose)
+ {
+ log_info ("dirmngr status: %s\n", line);
+ }
+ 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 ctrl, const char *command,
+ int argc, char **argv)
+{
+ int rc;
+ int i;
+ const char *s;
+ char *line, *p;
+ size_t len;
+ struct run_command_parm_s parm;
+
+ rc = start_dirmngr ();
+ if (rc)
+ return rc;
+
+ parm.ctx = dirmngr_ctx;
+
+ len = strlen (command) + 1;
+ for (i=0; i < argc; i++)
+ len += 1 + 3*strlen (argv[i]); /* enough space for percent escaping */
+ line = xtrymalloc (len);
+ if (!line)
+ return OUT_OF_CORE (errno);
+
+ 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", *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, NULL);
+ xfree (line);
+ log_info ("response of dirmngr: %s\n", rc? assuan_strerror (rc): "okay");
+ return map_assuan_err (rc);
+}
diff --git a/sm/certchain.c b/sm/certchain.c
new file mode 100644
index 000000000..6323c725e
--- /dev/null
+++ b/sm/certchain.c
@@ -0,0 +1,793 @@
+/* certchain.c - certificate chain validation
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "keydb.h"
+#include "i18n.h"
+
+static int
+unknown_criticals (KsbaCert cert)
+{
+ static const char *known[] = {
+ "2.5.29.15", /* keyUsage */
+ "2.5.29.19", /* basic Constraints */
+ "2.5.29.32", /* certificatePolicies */
+ NULL
+ };
+ int rc = 0, i, idx, crit;
+ const char *oid;
+ KsbaError err;
+
+ 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++)
+ ;
+ if (!known[i])
+ {
+ log_error (_("critical certificate extension %s is not supported\n"),
+ oid);
+ rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT);
+ }
+ }
+ if (err && err != -1)
+ rc = map_ksba_err (err);
+
+ return rc;
+}
+
+static int
+allowed_ca (KsbaCert cert, int *chainlen)
+{
+ KsbaError err;
+ int flag;
+
+ err = ksba_cert_is_ca (cert, &flag, chainlen);
+ if (err)
+ return map_ksba_err (err);
+ if (!flag)
+ {
+ log_error (_("issuer certificate is not marked as a CA\n"));
+ return gpg_error (GPG_ERR_BAD_CA_CERT);
+ }
+ return 0;
+}
+
+
+static int
+check_cert_policy (KsbaCert cert)
+{
+ KsbaError err;
+ char *policies;
+ FILE *fp;
+ int any_critical;
+
+ err = ksba_cert_get_cert_policies (cert, &policies);
+ if (err == KSBA_No_Data)
+ return 0; /* no policy given */
+ if (err)
+ return map_ksba_err (err);
+
+ /* STRING is a line delimited list of certifiate 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)
+ 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)
+ {
+ log_error ("critical marked policy without configured policies\n");
+ return gpg_error (GPG_ERR_NO_POLICY_MATCH);
+ }
+ return 0;
+ }
+
+ fp = fopen (opt.policy_file, "r");
+ if (!fp)
+ {
+ log_error ("failed to open `%s': %s\n",
+ opt.policy_file, strerror (errno));
+ xfree (policies);
+ return gpg_error (GPG_ERR_NO_POLICY_MATCH);
+ }
+
+ for (;;)
+ {
+ int c;
+ char *p, line[256];
+ char *haystack, *allowed;
+
+ /* read line */
+ do
+ {
+ if (!fgets (line, DIM(line)-1, fp) )
+ {
+ gpg_error_t tmperr;
+
+ xfree (policies);
+ if (feof (fp))
+ {
+ fclose (fp);
+ /* with no critical policies this is only a warning */
+ if (!any_critical)
+ {
+ log_info (_("note: certificate policy not allowed\n"));
+ return 0;
+ }
+ log_error (_("certificate policy not allowed\n"));
+ return gpg_error (GPG_ERR_NO_POLICY_MATCH);
+ }
+ tmperr = gpg_error (gpg_err_code_from_errno (errno));
+ fclose (fp);
+ return tmperr;
+ }
+
+ if (!*line || line[strlen(line)-1] != '\n')
+ {
+ /* eat until end of line */
+ while ( (c=getc (fp)) != EOF && c != '\n')
+ ;
+ fclose (fp);
+ xfree (policies);
+ return gpg_error (*line? GPG_ERR_LINE_TOO_LONG
+ : GPG_ERR_INCOMPLETE_LINE);
+ }
+
+ /* Allow for empty lines and spaces */
+ for (p=line; spacep (p); p++)
+ ;
+ }
+ while (!*p || *p == '\n' || *p == '#');
+
+ /* parse line */
+ for (allowed=line; spacep (allowed); allowed++)
+ ;
+ p = strpbrk (allowed, " :\n");
+ if (!*p || p == allowed)
+ {
+ fclose (fp);
+ xfree (policies);
+ return gpg_error (GPG_ERR_CONFIGURATION);
+ }
+ *p = 0; /* strip the rest of the line */
+ /* See whether we find ALLOWED (which is an OID) in POLICIES */
+ for (haystack=policies; (p=strstr (haystack, allowed)); haystack = p+1)
+ {
+ if ( !(p == policies || p[-1] == '\n') )
+ continue; /* does not match the begin of a line */
+ if (p[strlen (allowed)] != ':')
+ continue; /* the length does not match */
+ /* Yep - it does match so return okay */
+ fclose (fp);
+ xfree (policies);
+ return 0;
+ }
+ }
+}
+
+
+static void
+find_up_store_certs_cb (void *cb_value, KsbaCert cert)
+{
+ if (keydb_store_cert (cert, 1, NULL))
+ log_error ("error storing issuer certificate as ephemeral\n");
+ ++*(int*)cb_value;
+}
+
+
+static int
+find_up (KEYDB_HANDLE kh, KsbaCert cert, const char *issuer)
+{
+ KsbaName authid;
+ KsbaSexp authidno;
+ int rc = -1;
+
+ if (!ksba_cert_get_auth_key_id (cert, NULL, &authid, &authidno))
+ {
+ const char *s = ksba_name_enum (authid, 0);
+ if (s && *authidno)
+ {
+ rc = keydb_search_issuer_sn (kh, s, authidno);
+ if (rc)
+ keydb_search_reset (kh);
+ if (rc == -1)
+ { /* And try the ephemeral DB. */
+ int old = keydb_set_ephemeral (kh, 1);
+ if (!old)
+ {
+ rc = keydb_search_issuer_sn (kh, s, authidno);
+ if (rc)
+ keydb_search_reset (kh);
+ }
+ keydb_set_ephemeral (kh, old);
+ }
+ }
+ /* print a note so that the user does not feel too helpless when
+ an issuer certificate was found and gpgsm prints BAD
+ signature becuase it is not the correct one. */
+ if (rc == -1)
+ {
+ log_info ("issuer certificate (#");
+ gpgsm_dump_serial (authidno);
+ log_printf ("/");
+ gpgsm_dump_string (s);
+ log_printf (") not found\n");
+ }
+ else if (rc)
+ log_error ("failed to find authorityKeyIdentifier: rc=%d\n", rc);
+ ksba_name_release (authid);
+ xfree (authidno);
+ /* Fixme: don't know how to do dirmngr lookup with serial+issuer. */
+ }
+
+ if (rc) /* not found via authorithyKeyIdentifier, try regular issuer name */
+ rc = keydb_search_subject (kh, issuer);
+ if (rc == -1)
+ {
+ /* Not found, lets see whether we have one in the ephemeral key DB. */
+ int old = keydb_set_ephemeral (kh, 1);
+ if (!old)
+ {
+ keydb_search_reset (kh);
+ rc = keydb_search_subject (kh, issuer);
+ }
+ keydb_set_ephemeral (kh, old);
+ }
+
+ if (rc == -1 && opt.auto_issuer_key_retrieve)
+ {
+ STRLIST names = NULL;
+ int count = 0;
+ char *pattern;
+ const char *s;
+
+ if (opt.verbose)
+ log_info (_("looking up issuer at external location\n"));
+ /* dirmngr is confused about unknown attributes so has a quick
+ and ugly hack we locate the CN and use this and the
+ following. Fixme: we should have far better parsing in the
+ dirmngr. */
+ s = strstr (issuer, "CN=");
+ if (!s || s == issuer || s[-1] != ',')
+ s = issuer;
+
+ pattern = xtrymalloc (strlen (s)+2);
+ if (!pattern)
+ return OUT_OF_CORE (errno);
+ strcpy (stpcpy (pattern, "/"), s);
+ add_to_strlist (&names, pattern);
+ xfree (pattern);
+ rc = gpgsm_dirmngr_lookup (NULL, names, find_up_store_certs_cb, &count);
+ free_strlist (names);
+ if (opt.verbose)
+ log_info (_("number of issuers matching: %d\n"), count);
+ if (rc)
+ {
+ log_error ("external key lookup failed: %s\n", gpg_strerror (rc));
+ rc = -1;
+ }
+ else if (!count)
+ rc = -1;
+ else
+ {
+ int old;
+ /* The issuers are currently stored in the ephemeral key
+ DB, so we temporary switch to ephemeral mode. */
+ old = keydb_set_ephemeral (kh, 1);
+ keydb_search_reset (kh);
+ rc = keydb_search_subject (kh, issuer);
+ keydb_set_ephemeral (kh, old);
+ }
+ }
+ 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 (KsbaCert start, KsbaCert *r_next)
+{
+ int rc = 0;
+ char *issuer = NULL;
+ char *subject = NULL;
+ KEYDB_HANDLE kh = keydb_new (0);
+
+ *r_next = NULL;
+ if (!kh)
+ {
+ log_error (_("failed to allocated keyDB handle\n"));
+ rc = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+
+ issuer = ksba_cert_get_issuer (start, 0);
+ subject = ksba_cert_get_subject (start, 0);
+ if (!issuer)
+ {
+ log_error ("no issuer found in certificate\n");
+ rc = gpg_error (GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+ if (!subject)
+ {
+ log_error ("no subject found in certificate\n");
+ rc = gpg_error (GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+
+ if (!strcmp (issuer, subject))
+ {
+ rc = -1; /* we are at the root */
+ goto leave;
+ }
+
+ rc = find_up (kh, start, issuer);
+ 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_CERT);
+ goto leave;
+ }
+
+ rc = keydb_get_cert (kh, r_next);
+ if (rc)
+ {
+ log_error ("failed to get cert: rc=%d\n", rc);
+ rc = gpg_error (GPG_ERR_GENERAL);
+ }
+
+ leave:
+ xfree (issuer);
+ xfree (subject);
+ keydb_release (kh);
+ return rc;
+}
+
+
+/* Check whether the CERT is a root certificate. Returns True if this
+ is the case. */
+int
+gpgsm_is_root_cert (KsbaCert cert)
+{
+ char *issuer;
+ char *subject;
+ int yes;
+
+ issuer = ksba_cert_get_issuer (cert, 0);
+ subject = ksba_cert_get_subject (cert, 0);
+ yes = (issuer && subject && !strcmp (issuer, subject));
+ xfree (issuer);
+ xfree (subject);
+ return yes;
+}
+
+
+/* Validate a chain and optionally return the nearest expiration time
+ in R_EXPTIME */
+int
+gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, time_t *r_exptime)
+{
+ int rc = 0, depth = 0, maxdepth;
+ char *issuer = NULL;
+ char *subject = NULL;
+ KEYDB_HANDLE kh = keydb_new (0);
+ KsbaCert subject_cert = NULL, issuer_cert = NULL;
+ time_t current_time = gnupg_get_time ();
+ time_t exptime = 0;
+ 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;
+
+ if (r_exptime)
+ *r_exptime = 0;
+
+ if (opt.no_chain_validation)
+ {
+ log_info ("WARNING: bypassing certificate chain validation\n");
+ return 0;
+ }
+
+ if (!kh)
+ {
+ log_error (_("failed to allocated keyDB handle\n"));
+ rc = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+
+ if (DBG_X509)
+ gpgsm_dump_cert ("subject", cert);
+
+ subject_cert = cert;
+ maxdepth = 50;
+
+ for (;;)
+ {
+ xfree (issuer);
+ xfree (subject);
+ issuer = ksba_cert_get_issuer (subject_cert, 0);
+ subject = ksba_cert_get_subject (subject_cert, 0);
+
+ if (!issuer)
+ {
+ log_error ("no issuer found in certificate\n");
+ rc = gpg_error (GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+
+ {
+ time_t not_before, not_after;
+
+ not_before = ksba_cert_get_validity (subject_cert, 0);
+ not_after = ksba_cert_get_validity (subject_cert, 1);
+ if (not_before == (time_t)(-1) || not_after == (time_t)(-1))
+ {
+ log_error ("certificate with invalid validity\n");
+ rc = gpg_error (GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+
+ if (not_after)
+ {
+ if (!exptime)
+ exptime = not_after;
+ else if (not_after < exptime)
+ exptime = not_after;
+ }
+
+ if (not_before && current_time < not_before)
+ {
+ log_error ("certificate too young; valid from ");
+ gpgsm_dump_time (not_before);
+ log_printf ("\n");
+ rc = gpg_error (GPG_ERR_CERT_TOO_YOUNG);
+ goto leave;
+ }
+ if (not_after && current_time > not_after)
+ {
+ log_error ("certificate has expired at ");
+ gpgsm_dump_time (not_after);
+ log_printf ("\n");
+ any_expired = 1;
+ }
+ }
+
+ rc = unknown_criticals (subject_cert);
+ if (rc)
+ goto leave;
+
+ if (!opt.no_policy_check)
+ {
+ rc = check_cert_policy (subject_cert);
+ if (gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH)
+ {
+ any_no_policy_match = 1;
+ rc = 1;
+ }
+ else if (rc)
+ goto leave;
+ }
+
+ if (!opt.no_crl_check)
+ {
+ rc = gpgsm_dirmngr_isvalid (subject_cert);
+ if (rc)
+ {
+ switch (rc)
+ {
+ case GPG_ERR_CERT_REVOKED:
+ log_error (_("the certificate has been revoked\n"));
+ any_revoked = 1;
+ break;
+ case GPG_ERR_NO_CRL_KNOWN:
+ log_error (_("no CRL found for certificate\n"));
+ any_no_crl = 1;
+ break;
+ case GPG_ERR_CRL_TOO_OLD:
+ log_error (_("the available CRL is too old\n"));
+ log_info (_("please make sure that the "
+ "\"dirmngr\" is properly installed\n"));
+ any_crl_too_old = 1;
+ break;
+ default:
+ log_error (_("checking the CRL failed: %s\n"),
+ gpg_strerror (rc));
+ goto leave;
+ }
+ rc = 0;
+ }
+ }
+
+ if (subject && !strcmp (issuer, subject))
+ {
+ if (gpgsm_check_cert_sig (subject_cert, subject_cert) )
+ {
+ log_error ("selfsigned certificate has a BAD signatures\n");
+ rc = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN
+ : GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+ rc = allowed_ca (subject_cert, NULL);
+ if (rc)
+ goto leave;
+
+ rc = gpgsm_agent_istrusted (subject_cert);
+ if (!rc)
+ ;
+ else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
+ {
+ int rc2;
+
+ char *fpr = gpgsm_get_fingerprint_string (subject_cert,
+ GCRY_MD_SHA1);
+ log_info (_("root certificate is not marked trusted\n"));
+ log_info (_("fingerprint=%s\n"), fpr? fpr : "?");
+ xfree (fpr);
+ rc2 = gpgsm_agent_marktrusted (subject_cert);
+ if (!rc2)
+ {
+ log_info (_("root certificate has now"
+ " been marked as trusted\n"));
+ rc = 0;
+ }
+ else
+ {
+ gpgsm_dump_cert ("issuer", subject_cert);
+ log_info ("after checking the fingerprint, you may want "
+ "to enter it manually into "
+ "\"~/.gnupg-test/trustlist.txt\"\n");
+ }
+ }
+ else
+ {
+ log_error (_("checking the trust list failed: %s\n"),
+ gpg_strerror (rc));
+ }
+
+ break; /* okay, a self-signed certicate is an end-point */
+ }
+
+ depth++;
+ if (depth > maxdepth)
+ {
+ log_error (_("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 (kh, subject_cert, issuer);
+ 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_CERT);
+ goto leave;
+ }
+
+ ksba_cert_release (issuer_cert); issuer_cert = NULL;
+ rc = keydb_get_cert (kh, &issuer_cert);
+ if (rc)
+ {
+ log_error ("failed to get cert: rc=%d\n", rc);
+ rc = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+
+ if (DBG_X509)
+ {
+ log_debug ("got issuer's certificate:\n");
+ gpgsm_dump_cert ("issuer", issuer_cert);
+ }
+
+ if (gpgsm_check_cert_sig (issuer_cert, subject_cert) )
+ {
+ log_error ("certificate has a BAD signatures\n");
+ rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
+ goto leave;
+ }
+
+ {
+ int chainlen;
+ rc = allowed_ca (issuer_cert, &chainlen);
+ if (rc)
+ goto leave;
+ if (chainlen >= 0 && (depth - 1) > chainlen)
+ {
+ log_error (_("certificate chain longer than allowed by CA (%d)\n"),
+ chainlen);
+ rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
+ goto leave;
+ }
+ }
+
+ 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);
+ rc = 0;
+ }
+
+ if (opt.verbose)
+ log_info ("certificate is good\n");
+
+ keydb_search_reset (kh);
+ subject_cert = issuer_cert;
+ issuer_cert = NULL;
+ }
+
+ if (opt.no_policy_check)
+ log_info ("policies not checked due to --disable-policy-checks option\n");
+ if (opt.no_crl_check)
+ log_info ("CRLs not checked due to --disable-crl-checks option\n");
+
+ 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_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);
+ else if (any_expired)
+ rc = gpg_error (GPG_ERR_CERT_EXPIRED);
+ }
+
+ leave:
+ if (r_exptime)
+ *r_exptime = exptime;
+ xfree (issuer);
+ keydb_release (kh);
+ ksba_cert_release (issuer_cert);
+ if (subject_cert != cert)
+ ksba_cert_release (subject_cert);
+ 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 (KsbaCert cert)
+{
+ int rc = 0;
+ char *issuer = NULL;
+ char *subject = NULL;
+ KEYDB_HANDLE kh = keydb_new (0);
+ KsbaCert issuer_cert = NULL;
+
+ if (opt.no_chain_validation)
+ {
+ log_info ("WARNING: bypassing basic certificate checks\n");
+ return 0;
+ }
+
+ if (!kh)
+ {
+ log_error (_("failed to allocated keyDB handle\n"));
+ rc = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+
+ issuer = ksba_cert_get_issuer (cert, 0);
+ subject = ksba_cert_get_subject (cert, 0);
+ if (!issuer)
+ {
+ log_error ("no issuer found in certificate\n");
+ rc = gpg_error (GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+
+ if (subject && !strcmp (issuer, subject))
+ {
+ if (gpgsm_check_cert_sig (cert, cert) )
+ {
+ log_error ("selfsigned certificate has a BAD signatures\n");
+ rc = gpg_error (GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+ }
+ else
+ {
+ /* find the next cert up the tree */
+ keydb_search_reset (kh);
+ rc = find_up (kh, cert, issuer);
+ 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_CERT);
+ goto leave;
+ }
+
+ ksba_cert_release (issuer_cert); issuer_cert = NULL;
+ rc = keydb_get_cert (kh, &issuer_cert);
+ if (rc)
+ {
+ log_error ("failed to get cert: rc=%d\n", rc);
+ rc = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+
+ if (gpgsm_check_cert_sig (issuer_cert, cert) )
+ {
+ log_error ("certificate has a BAD signatures\n");
+ rc = gpg_error (GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+ if (opt.verbose)
+ log_info ("certificate is good\n");
+ }
+
+ leave:
+ xfree (issuer);
+ keydb_release (kh);
+ ksba_cert_release (issuer_cert);
+ return rc;
+}
+
diff --git a/sm/certcheck.c b/sm/certcheck.c
new file mode 100644
index 000000000..35509c67e
--- /dev/null
+++ b/sm/certcheck.c
@@ -0,0 +1,300 @@
+/* certcheck.c - check one certificate
+ * Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "keydb.h"
+#include "i18n.h"
+
+
+static int
+do_encode_md (gcry_md_hd_t md, int algo, unsigned int nbits,
+ gcry_mpi_t *r_val)
+{
+ int nframe = (nbits+7) / 8;
+ byte *frame;
+ int i, n;
+ byte asn[100];
+ size_t asnlen;
+ size_t len;
+
+ asnlen = DIM(asn);
+ 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 (errno);
+ 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_X509)
+ {
+ 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;
+}
+
+
+/*
+ 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 (KsbaCert issuer_cert, KsbaCert cert)
+{
+ const char *algoid;
+ gcry_md_hd_t md;
+ int rc, algo;
+ gcry_mpi_t frame;
+ KsbaSexp p;
+ size_t n;
+ gcry_sexp_t s_sig, s_hash, s_pkey;
+
+ algo = gcry_md_map_name ( (algoid=ksba_cert_get_digest_algo (cert)));
+ if (!algo)
+ {
+ log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?");
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+ rc = gcry_md_open (&md, algo, 0);
+ if (rc)
+ {
+ log_error ("md_open failed: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+ if (DBG_HASHING)
+ gcry_md_start_debug (md, "hash.cert");
+
+ rc = ksba_cert_hash (cert, 1, HASH_FNC, md);
+ if (rc)
+ {
+ log_error ("ksba_cert_hash failed: %s\n", ksba_strerror (rc));
+ gcry_md_close (md);
+ return map_ksba_err (rc);
+ }
+ gcry_md_final (md);
+
+ p = ksba_cert_get_sig_val (cert);
+ n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error ("libksba did not return a proper S-Exp\n");
+ gcry_md_close (md);
+ ksba_free (p);
+ return gpg_error (GPG_ERR_BUG);
+ }
+ if (DBG_X509)
+ {
+ int j;
+ log_debug ("signature value:");
+ for (j=0; j < n; j++)
+ log_printf (" %02X", p[j]);
+ log_printf ("\n");
+ }
+
+ rc = gcry_sexp_sscan ( &s_sig, NULL, p, n);
+ ksba_free (p);
+ if (rc)
+ {
+ log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
+ gcry_md_close (md);
+ return rc;
+ }
+
+ p = ksba_cert_get_public_key (issuer_cert);
+ n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error ("libksba did not return a proper S-Exp\n");
+ gcry_md_close (md);
+ ksba_free (p);
+ gcry_sexp_release (s_sig);
+ return gpg_error (GPG_ERR_BUG);
+ }
+ rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n);
+ ksba_free (p);
+ if (rc)
+ {
+ log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
+ gcry_md_close (md);
+ gcry_sexp_release (s_sig);
+ return rc;
+ }
+
+ rc = do_encode_md (md, algo, gcry_pk_get_nbits (s_pkey), &frame);
+ if (rc)
+ {
+ gcry_md_close (md);
+ gcry_sexp_release (s_sig);
+ gcry_sexp_release (s_pkey);
+ return rc;
+ }
+
+ /* put hash into the S-Exp s_hash */
+ if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) )
+ BUG ();
+ gcry_mpi_release (frame);
+
+
+ rc = gcry_pk_verify (s_sig, s_hash, s_pkey);
+ if (DBG_CRYPTO)
+ log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc));
+ gcry_md_close (md);
+ gcry_sexp_release (s_sig);
+ gcry_sexp_release (s_hash);
+ gcry_sexp_release (s_pkey);
+ return rc;
+}
+
+
+
+int
+gpgsm_check_cms_signature (KsbaCert cert, KsbaConstSexp sigval,
+ gcry_md_hd_t md, int algo)
+{
+ int rc;
+ KsbaSexp p;
+ gcry_mpi_t frame;
+ gcry_sexp_t s_sig, s_hash, s_pkey;
+ size_t n;
+
+ n = gcry_sexp_canon_len (sigval, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error ("libksba did not return a proper S-Exp\n");
+ return gpg_error (GPG_ERR_BUG);
+ }
+ rc = gcry_sexp_sscan (&s_sig, NULL, sigval, n);
+ if (rc)
+ {
+ log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
+ return rc;
+ }
+
+ p = ksba_cert_get_public_key (cert);
+ n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error ("libksba did not return a proper S-Exp\n");
+ ksba_free (p);
+ gcry_sexp_release (s_sig);
+ return gpg_error (GPG_ERR_BUG);
+ }
+ if (DBG_X509)
+ log_printhex ("public key: ", p, n);
+
+ rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n);
+ ksba_free (p);
+ if (rc)
+ {
+ log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
+ gcry_sexp_release (s_sig);
+ return rc;
+ }
+
+
+ rc = do_encode_md (md, algo, gcry_pk_get_nbits (s_pkey), &frame);
+ if (rc)
+ {
+ gcry_sexp_release (s_sig);
+ gcry_sexp_release (s_pkey);
+ return rc;
+ }
+ /* put hash into the S-Exp s_hash */
+ if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) )
+ BUG ();
+ gcry_mpi_release (frame);
+
+ rc = gcry_pk_verify (s_sig, s_hash, s_pkey);
+ if (DBG_CRYPTO)
+ log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc));
+ gcry_sexp_release (s_sig);
+ gcry_sexp_release (s_hash);
+ gcry_sexp_release (s_pkey);
+ return rc;
+}
+
+
+
+int
+gpgsm_create_cms_signature (KsbaCert cert, gcry_md_hd_t md, int mdalgo,
+ char **r_sigval)
+{
+ int rc;
+ char *grip;
+ size_t siglen;
+
+ grip = gpgsm_get_keygrip_hexstring (cert);
+ if (!grip)
+ return gpg_error (GPG_ERR_BAD_CERT);
+
+ rc = gpgsm_agent_pksign (grip, gcry_md_read(md, mdalgo),
+ gcry_md_get_algo_dlen (mdalgo), mdalgo,
+ r_sigval, &siglen);
+ xfree (grip);
+ return rc;
+}
+
+
+
diff --git a/sm/certdump.c b/sm/certdump.c
new file mode 100644
index 000000000..703e07186
--- /dev/null
+++ b/sm/certdump.c
@@ -0,0 +1,457 @@
+/* certdump.c - Dump a certificate for debugging
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "keydb.h"
+#include "i18n.h"
+
+struct dn_array_s {
+ char *key;
+ char *value;
+};
+
+
+/* print the first element of an S-Expression */
+void
+gpgsm_print_serial (FILE *fp, KsbaConstSexp p)
+{
+ unsigned long n;
+ KsbaConstSexp endp;
+
+ if (!p)
+ fputs (_("none"), fp);
+ else if (*p != '(')
+ fputs ("[Internal error - not an S-expression]", fp);
+ else
+ {
+ p++;
+ n = strtoul (p, (char**)&endp, 10);
+ p = endp;
+ if (*p!=':')
+ fputs ("[Internal Error - invalid S-expression]", fp);
+ else
+ {
+ for (p++; n; n--, p++)
+ fprintf (fp, "%02X", *p);
+ }
+ }
+}
+
+
+void
+gpgsm_dump_serial (KsbaConstSexp p)
+{
+ unsigned long n;
+ KsbaConstSexp endp;
+
+ if (!p)
+ log_printf ("none");
+ else if (*p != '(')
+ log_printf ("ERROR - not an S-expression");
+ else
+ {
+ p++;
+ n = strtoul (p, (char**)&endp, 10);
+ p = endp;
+ if (*p!=':')
+ log_printf ("ERROR - invalid S-expression");
+ else
+ {
+ for (p++; n; n--, p++)
+ log_printf ("%02X", *p);
+ }
+ }
+}
+
+void
+gpgsm_print_time (FILE *fp, time_t t)
+{
+ if (!t)
+ fputs (_("none"), fp);
+ else if ( t == (time_t)(-1) )
+ fputs ("[Error - Invalid time]", fp);
+ else
+ {
+ struct tm *tp;
+
+ tp = gmtime (&t);
+ fprintf (fp, "%04d-%02d-%02d %02d:%02d:%02d Z",
+ 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
+ tp->tm_hour, tp->tm_min, tp->tm_sec);
+ assert (!tp->tm_isdst);
+ }
+}
+
+void
+gpgsm_dump_time (time_t t)
+{
+
+ if (!t)
+ log_printf (_("[none]"));
+ else if ( t == (time_t)(-1) )
+ log_printf (_("[error]"));
+ else
+ {
+ struct tm *tp;
+
+ tp = gmtime (&t);
+ log_printf ("%04d-%02d-%02d %02d:%02d:%02d",
+ 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
+ tp->tm_hour, tp->tm_min, tp->tm_sec);
+ assert (!tp->tm_isdst);
+ }
+}
+
+
+
+
+void
+gpgsm_dump_string (const char *string)
+{
+
+ if (!string)
+ log_printf ("[error]");
+ else
+ {
+ const unsigned char *s;
+
+ for (s=string; *s; s++)
+ {
+ if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0))
+ break;
+ }
+ if (!*s && *string != '[')
+ log_printf ("%s", string);
+ else
+ {
+ log_printf ( "[ ");
+ log_printhex (NULL, string, strlen (string));
+ log_printf ( " ]");
+ }
+ }
+}
+
+
+void
+gpgsm_dump_cert (const char *text, KsbaCert cert)
+{
+ KsbaSexp sexp;
+ unsigned char *p;
+ char *dn;
+ time_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");
+
+ t = ksba_cert_get_validity (cert, 0);
+ log_debug (" notBefore: ");
+ gpgsm_dump_time (t);
+ log_printf ("\n");
+ t = ksba_cert_get_validity (cert, 1);
+ log_debug (" notAfter: ");
+ gpgsm_dump_time (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");
+}
+
+
+
+/* helper for the rfc2253 string parser */
+static const unsigned char *
+parse_dn_part (struct dn_array_s *array, const unsigned char *string)
+{
+ const unsigned char *s, *s1;
+ size_t n;
+ unsigned char *p;
+
+ /* parse attributeType */
+ for (s = string+1; *s && *s != '='; s++)
+ ;
+ if (!*s)
+ return NULL; /* error */
+ n = s - string;
+ if (!n)
+ return NULL; /* empty key */
+ array->key = p = xtrymalloc (n+1);
+ if (!array->key)
+ return NULL;
+ memcpy (p, string, n);
+ p[n] = 0;
+ trim_trailing_spaces (p);
+ if ( !strcmp (p, "1.2.840.113549.1.9.1") )
+ strcpy (p, "EMail");
+ string = s + 1;
+
+ if (*string == '#')
+ { /* hexstring */
+ string++;
+ for (s=string; hexdigitp (s); s++)
+ s++;
+ n = s - string;
+ if (!n || (n & 1))
+ return NULL; /* empty or odd number of digits */
+ n /= 2;
+ array->value = p = xtrymalloc (n+1);
+ if (!p)
+ return NULL;
+ for (s1=string; n; s1 += 2, n--)
+ *p++ = xtoi_2 (s1);
+ *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 == '#' || *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))
+ {
+ *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);
+ arrayidx++;
+ if (!string)
+ goto failure;
+ while (*string == ' ')
+ string++;
+ 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;
+}
+
+
+static void
+print_dn_part (FILE *fp, struct dn_array_s *dn, const char *key)
+{
+ int any = 0;
+
+ for (; dn->key; dn++)
+ {
+ if (!strcmp (dn->key, key) && dn->value && *dn->value)
+ {
+ putc ('/', fp);
+ if (any)
+ fputs (" + ", fp);
+ else
+ fprintf (fp, "%s=", key);
+ print_sanitized_utf8_string (fp, dn->value, '/');
+ any = 1;
+ }
+ }
+}
+
+/* Print all parts of a DN in a "standard" sequence. We first print
+ all the known parts, followed by the uncommon ones */
+static void
+print_dn_parts (FILE *fp, struct dn_array_s *dn)
+{
+ const char *stdpart[] = {
+ "CN", "OU", "O", "STREET", "L", "ST", "C", "EMail", NULL
+ };
+ int i;
+
+ for (i=0; stdpart[i]; i++)
+ print_dn_part (fp, dn, stdpart[i]);
+
+ /* now print the rest without any specific ordering */
+ for (; dn->key; dn++)
+ {
+ for (i=0; stdpart[i]; i++)
+ {
+ if (!strcmp (dn->key, stdpart[i]))
+ break;
+ }
+ if (!stdpart[i])
+ print_dn_part (fp, dn, dn->key);
+ }
+}
+
+
+
+void
+gpgsm_print_name (FILE *fp, const char *name)
+{
+ const unsigned char *s;
+ int i;
+
+ s = name;
+ if (!s)
+ {
+ fputs (_("[Error - No name]"), fp);
+ }
+ else if (*s == '<')
+ {
+ const unsigned char *s2 = strchr (s+1, '>');
+ if (s2)
+ print_sanitized_utf8_buffer (fp, s + 1, s2 - s - 1, 0);
+ }
+ else if (*s == '(')
+ fputs (_("[Error - unknown encoding]"), fp);
+ else if (!((*s >= '0' && *s < '9')
+ || (*s >= 'A' && *s <= 'Z')
+ || (*s >= 'a' && *s <= 'z')))
+ fputs (_("[Error - invalid encoding]"), fp);
+ else
+ {
+ struct dn_array_s *dn = parse_dn (s);
+ if (!dn)
+ fputs (_("[Error - invalid DN]"), fp);
+ else
+ {
+ print_dn_parts (fp, dn);
+ for (i=0; dn[i].key; i++)
+ {
+ xfree (dn[i].key);
+ xfree (dn[i].value);
+ }
+ xfree (dn);
+ }
+ }
+}
+
+
+
diff --git a/sm/certlist.c b/sm/certlist.c
new file mode 100644
index 000000000..eedc99025
--- /dev/null
+++ b/sm/certlist.c
@@ -0,0 +1,315 @@
+/* certlist.c - build list of certificates
+ * Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "keydb.h"
+#include "i18n.h"
+
+/* Return 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) */
+static int
+cert_usage_p (KsbaCert cert, int mode)
+{
+ KsbaError err;
+ unsigned int use;
+
+ err = ksba_cert_get_key_usage (cert, &use);
+ if (err == KSBA_No_Data)
+ {
+ if (opt.verbose && mode < 2)
+ log_info (mode?
+ _("no key usage specified - accepted for encryption\n"):
+ _("no key usage specified - accepted for signing\n"));
+ return 0;
+ }
+ if (err)
+ {
+ log_error (_("error getting key usage information: %s\n"),
+ ksba_strerror (err));
+ return map_ksba_err (err);
+ }
+
+ if (mode == 4)
+ {
+ if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN)))
+ return 0;
+ log_info ( _("certificate should have not been used certification\n"));
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+ }
+
+ if ((use & ((mode&1)?
+ (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT):
+ (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
+ )
+ return 0;
+ log_info (mode==3? _("certificate should have not been used for encryption\n"):
+ mode==2? _("certificate should have not been used for signing\n"):
+ mode==1? _("certificate is not usable for encryption\n"):
+ _("certificate is not usable for signing\n"));
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+}
+
+
+/* Return 0 if the cert is usable for signing */
+int
+gpgsm_cert_use_sign_p (KsbaCert cert)
+{
+ return cert_usage_p (cert, 0);
+}
+
+
+/* Return 0 if the cert is usable for encryption */
+int
+gpgsm_cert_use_encrypt_p (KsbaCert cert)
+{
+ return cert_usage_p (cert, 1);
+}
+
+int
+gpgsm_cert_use_verify_p (KsbaCert cert)
+{
+ return cert_usage_p (cert, 2);
+}
+
+int
+gpgsm_cert_use_decrypt_p (KsbaCert cert)
+{
+ return cert_usage_p (cert, 3);
+}
+
+int
+gpgsm_cert_use_cert_p (KsbaCert cert)
+{
+ return cert_usage_p (cert, 4);
+}
+
+
+static int
+same_subject_issuer (const char *subject, const char *issuer, KsbaCert cert)
+{
+ char *subject2 = ksba_cert_get_subject (cert, 0);
+ char *issuer2 = ksba_cert_get_subject (cert, 0);
+ int tmp;
+
+ tmp = (subject && subject2
+ && !strcmp (subject, subject2)
+ && issuer && issuer2
+ && !strcmp (issuer, issuer2));
+ xfree (subject2);
+ xfree (issuer2);
+ return tmp;
+}
+
+
+
+/* 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
+ avaibale for the certificate. */
+int
+gpgsm_add_to_certlist (CTRL ctrl, const char *name, int secret,
+ CERTLIST *listaddr)
+{
+ int rc;
+ KEYDB_SEARCH_DESC desc;
+ KEYDB_HANDLE kh = NULL;
+ KsbaCert cert = NULL;
+
+ rc = keydb_classify_name (name, &desc);
+ if (!rc)
+ {
+ kh = keydb_new (0);
+ if (!kh)
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ else
+ {
+ int wrong_usage = 0;
+ char *subject = NULL;
+ char *issuer = NULL;
+
+ get_next:
+ rc = keydb_search (kh, &desc, 1);
+ if (!rc)
+ rc = keydb_get_cert (kh, &cert);
+ if (!rc)
+ {
+ rc = secret? gpgsm_cert_use_sign_p (cert)
+ : gpgsm_cert_use_encrypt_p (cert);
+ if (gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE)
+ {
+ /* There might be another certificate with the
+ correct usage, so we try again */
+ if (!wrong_usage)
+ { /* save the first match */
+ wrong_usage = rc;
+ subject = ksba_cert_get_subject (cert, 0);
+ issuer = ksba_cert_get_subject (cert, 0);
+ ksba_cert_release (cert);
+ cert = NULL;
+ goto get_next;
+ }
+ else if (same_subject_issuer (subject, 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)
+ {
+ next_ambigious:
+ rc = keydb_search (kh, &desc, 1);
+ if (rc == -1)
+ rc = 0;
+ else if (!rc)
+ {
+ KsbaCert cert2 = NULL;
+
+ /* We have to ignore ambigious names as long as
+ there only fault is a bad key usage */
+ if (!keydb_get_cert (kh, &cert2))
+ {
+ int tmp = (same_subject_issuer (subject, issuer, cert2)
+ && ((gpg_err_code (
+ secret? gpgsm_cert_use_sign_p (cert2)
+ : gpgsm_cert_use_encrypt_p (cert2)
+ )
+ ) == GPG_ERR_WRONG_KEY_USAGE));
+ ksba_cert_release (cert2);
+ if (tmp)
+ goto next_ambigious;
+ }
+ rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
+ }
+ }
+ xfree (subject);
+ xfree (issuer);
+
+ if (!rc && secret)
+ {
+ char *p;
+
+ rc = gpg_error (GPG_ERR_NO_SECKEY);
+ p = gpgsm_get_keygrip_hexstring (cert);
+ if (p)
+ {
+ if (!gpgsm_agent_havekey (p))
+ rc = 0;
+ xfree (p);
+ }
+ }
+ if (!rc)
+ rc = gpgsm_validate_chain (ctrl, cert, NULL);
+ if (!rc)
+ {
+ CERTLIST cl = xtrycalloc (1, sizeof *cl);
+ if (!cl)
+ rc = OUT_OF_CORE (errno);
+ else
+ {
+ cl->cert = cert; cert = NULL;
+ cl->next = *listaddr;
+ *listaddr = cl;
+ }
+ }
+ }
+ }
+
+ keydb_release (kh);
+ ksba_cert_release (cert);
+ return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc;
+}
+
+void
+gpgsm_release_certlist (CERTLIST list)
+{
+ while (list)
+ {
+ CERTLIST 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 */
+int
+gpgsm_find_cert (const char *name, KsbaCert *r_cert)
+{
+ int rc;
+ KEYDB_SEARCH_DESC desc;
+ KEYDB_HANDLE kh = NULL;
+
+ *r_cert = NULL;
+ rc = keydb_classify_name (name, &desc);
+ if (!rc)
+ {
+ kh = keydb_new (0);
+ if (!kh)
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ else
+ {
+ rc = keydb_search (kh, &desc, 1);
+ if (!rc)
+ rc = keydb_get_cert (kh, r_cert);
+ if (!rc)
+ {
+ rc = keydb_search (kh, &desc, 1);
+ if (rc == -1)
+ rc = 0;
+ else
+ {
+ if (!rc)
+ 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.c b/sm/certreqgen.c
new file mode 100644
index 000000000..0dd4fdde9
--- /dev/null
+++ b/sm/certreqgen.c
@@ -0,0 +1,699 @@
+/* certreqgen.c - Generate a key and a certification request
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+/*
+The format of the native parameter file is follows:
+ o Text only, line length is limited to about 1000 chars.
+ o You must use UTF-8 encoding to specify non-ascii characters.
+ o Empty lines are ignored.
+ o Leading and trailing spaces are ignored.
+ o A hash sign as the first non white space character is a comment line.
+ o Control statements are indicated by a leading percent sign, the
+ arguments are separated by white space from the keyword.
+ o Parameters are specified by a keyword, followed by a colon. Arguments
+ are separated by white space.
+ o The first parameter must be "Key-Type", control statements
+ may be placed anywhere.
+ o Key generation takes place when either the end of the parameter file
+ is reached, the next "Key-Type" parameter is encountered or at the
+ controlstatement "%commit"
+ o Control statements:
+ %echo <text>
+ Print <text>.
+ %dry-run
+ Suppress actual key generation (useful for syntax checking).
+ %commit
+ Perform the key generation. Note that an implicit commit is done
+ at the next "Key-Type" parameter.
+ %certfile <filename>
+ Do not write the certificate to the keyDB but to <filename>.
+ This must be given before the first
+ commit to take place, duplicate specification of the same filename
+ is ignored, the last filename before a commit is used.
+ The filename is used until a new filename is used (at commit points)
+ and all keys are written to that file. If a new filename is given,
+ this file is created (and overwrites an existing one).
+ Both control statements must be given.
+ o The order of the parameters does not matter except for "Key-Type"
+ which must be the first parameter. The parameters are only for the
+ generated keyblock and parameters from previous key generations are not
+ used. Some syntactically checks may be performed.
+ The currently defined parameters are:
+ Key-Type: <algo>
+ Starts a new parameter block by giving the type of the
+ primary key. The algorithm must be capable of signing.
+ This is a required parameter. For now the only supported
+ algorithm is "rsa".
+ Key-Length: <length-in-bits>
+ Length of the key in bits. Default is 1024.
+ Key-Usage: <usage-list>
+ Space or comma delimited list of key usage, allowed values are
+ "encrypt" and "sign". This is used to generate the KeyUsage extension.
+ Please make sure that the algorithm is capable of this usage. Default
+ is to allow encrypt and sign.
+ Name-DN: subject name
+ This is the DN name of the subject in rfc2253 format.
+ Name-Email: <string>
+ The ist the email address
+
+Here is an example:
+$ cat >foo <<EOF
+%echo Generating a standard key
+Key-Type: RSA
+Key-Length: 1024
+Name-DN: CN=test cert 1,OU=Aegypten Project,O=g10 Code GmbH,L=Düsseldorf,C=DE
+Name-Email: joe@foo.bar
+# Do a commit here, so that we can later print "done" :-)
+%commit
+%echo done
+EOF
+*/
+
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "keydb.h"
+#include "i18n.h"
+
+
+enum para_name {
+ pKEYTYPE,
+ pKEYLENGTH,
+ pKEYUSAGE,
+ pNAMEDN,
+ pNAMEEMAIL
+};
+
+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;
+ KsbaWriter writer;
+};
+
+
+static int proc_parameters (struct para_data_s *para,
+ struct reqgen_ctrl_s *outctrl);
+static int create_request (struct para_data_s *para,
+ KsbaConstSexp public,
+ struct reqgen_ctrl_s *outctrl);
+
+
+
+static void
+release_parameter_list (struct para_data_s *r)
+{
+ struct para_data_s *r2;
+
+ for (; r ; r = r2)
+ {
+ r2 = r->next;
+ xfree(r);
+ }
+}
+
+static struct para_data_s *
+get_parameter (struct para_data_s *para, enum para_name key)
+{
+ struct para_data_s *r;
+
+ for (r = para; r && r->key != key; r = r->next)
+ ;
+ return r;
+}
+
+static const char *
+get_parameter_value (struct para_data_s *para, enum para_name key)
+{
+ struct para_data_s *r = get_parameter (para, key);
+ return (r && *r->u.value)? r->u.value : NULL;
+}
+
+static int
+get_parameter_algo (struct para_data_s *para, enum para_name key)
+{
+ struct para_data_s *r = get_parameter (para, key);
+ 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);
+ char *p, *pn;
+ unsigned int use;
+
+ if (!r)
+ return 0; /* none (this is an optional parameter)*/
+
+ use = 0;
+ pn = r->u.value;
+ while ( (p = strsep (&pn, " \t,")) )
+ {
+ if (!*p)
+ ;
+ else if ( !ascii_strcasecmp (p, "sign") )
+ use |= GCRY_PK_USAGE_SIGN;
+ else if ( !ascii_strcasecmp (p, "encrypt") )
+ use |= GCRY_PK_USAGE_ENCR;
+ else
+ {
+ log_error ("line %d: invalid usage list\n", r->lnr);
+ return -1; /* error */
+ }
+ }
+ r->u.usage = use;
+ return 0;
+}
+
+
+static unsigned int
+get_parameter_uint (struct para_data_s *para, enum para_name key)
+{
+ struct para_data_s *r = get_parameter (para, key);
+
+ if (!r)
+ return 0;
+
+ 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 (FILE *fp, KsbaWriter writer)
+{
+ static struct {
+ const char *name;
+ enum para_name key;
+ } keywords[] = {
+ { "Key-Type", pKEYTYPE},
+ { "Key-Length", pKEYLENGTH },
+ { "Key-Usage", pKEYUSAGE },
+ { "Name-DN", pNAMEDN },
+ { "Name-Email", pNAMEEMAIL },
+ { NULL, 0 }
+ };
+ char line[1024], *p;
+ const char *err = NULL;
+ struct para_data_s *para, *r;
+ int i, rc = 0, any = 0;
+ struct reqgen_ctrl_s outctrl;
+
+ memset (&outctrl, 0, sizeof (outctrl));
+ outctrl.writer = writer;
+
+ err = NULL;
+ para = NULL;
+ while (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 (; !spacep (p); p++)
+ ;
+ if (*p)
+ *p++ = 0;
+ for (; spacep (p); p++)
+ ;
+ value = p;
+ trim_trailing_spaces (value);
+
+ if (!ascii_strcasecmp (keyword, "%echo"))
+ log_info ("%s\n", value);
+ else if (!ascii_strcasecmp (keyword, "%dry-run"))
+ outctrl.dryrun = 1;
+ else if (!ascii_strcasecmp( keyword, "%commit"))
+ {
+ rc = proc_parameters (para, &outctrl);
+ if (rc)
+ goto leave;
+ any = 1;
+ release_parameter_list (para);
+ para = NULL;
+ }
+ else
+ log_info ("skipping control `%s' (%s)\n", keyword, value);
+
+ continue;
+ }
+
+
+ if (!(p = strchr (p, ':')) || p == keyword)
+ {
+ err = "missing colon";
+ break;
+ }
+ if (*p)
+ *p++ = 0;
+ for (; spacep (p); p++)
+ ;
+ if (!*p)
+ {
+ err = "missing argument";
+ break;
+ }
+ value = p;
+ trim_trailing_spaces (value);
+
+ for (i=0; (keywords[i].name
+ && ascii_strcasecmp (keywords[i].name, keyword)); i++)
+ ;
+ if (!keywords[i].name)
+ {
+ err = "unknown keyword";
+ break;
+ }
+ if (keywords[i].key != pKEYTYPE && !para)
+ {
+ err = "parameter block does not start with \"Key-Type\"";
+ break;
+ }
+
+ if (keywords[i].key == pKEYTYPE && para)
+ {
+ rc = proc_parameters (para, &outctrl);
+ if (rc)
+ goto leave;
+ any = 1;
+ release_parameter_list (para);
+ para = NULL;
+ }
+ else
+ {
+ 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 (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 (para, &outctrl);
+ if (rc)
+ goto leave;
+ any = 1;
+ }
+
+ if (!rc && !any)
+ rc = gpg_error (GPG_ERR_NO_DATA);
+
+ leave:
+ release_parameter_list (para);
+ return rc;
+}
+
+/* check whether there are invalid characters in the email address S */
+static int
+has_invalid_email_chars (const char *s)
+{
+ int at_seen=0;
+ static char valid_chars[] = "01234567890_-."
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ for (; *s; s++)
+ {
+ if (*s & 0x80)
+ return 1;
+ if (*s == '@')
+ at_seen++;
+ else if (!at_seen && !( !!strchr (valid_chars, *s) || *s == '+'))
+ return 1;
+ else if (at_seen && !strchr (valid_chars, *s))
+ return 1;
+ }
+ return at_seen != 1;
+}
+
+
+/* Check that all required parameters are given and perform the action */
+static int
+proc_parameters (struct para_data_s *para, struct reqgen_ctrl_s *outctrl)
+{
+ struct para_data_s *r;
+ const char *s;
+ int i;
+ unsigned int nbits;
+ char numbuf[20];
+ unsigned char keyparms[100];
+ int rc;
+ KsbaSexp public;
+
+ /* check that we have all required parameters */
+ assert (get_parameter (para, pKEYTYPE));
+
+ /* We can only use RSA for now. There is a with pkcs-10 on how to
+ use ElGamal becuase it is expected that a PK algorithm can always
+ be used for signing. */
+ i = get_parameter_algo (para, pKEYTYPE);
+ if (i < 1 || i != GCRY_PK_RSA )
+ {
+ r = get_parameter (para, pKEYTYPE);
+ log_error ("line %d: invalid algorithm\n", r->lnr);
+ return gpg_error (GPG_ERR_INV_PARAMETER);
+ }
+
+ /* check the keylength */
+ if (!get_parameter (para, pKEYLENGTH))
+ nbits = 1024;
+ else
+ nbits = get_parameter_uint (para, pKEYLENGTH);
+ if (nbits < 512 || nbits > 4096)
+ {
+ r = get_parameter (para, pKEYTYPE);
+ log_error ("line %d: invalid key length %u (valid are 512 to 4096)\n",
+ r->lnr, nbits);
+ return gpg_error (GPG_ERR_INV_PARAMETER);
+ }
+
+ /* check the usage */
+ if (parse_parameter_usage (para, pKEYUSAGE))
+ 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)))
+ {
+ r = get_parameter (para, pKEYTYPE);
+ log_error ("line %d: no subject name given\n", r->lnr);
+ return gpg_error (GPG_ERR_INV_PARAMETER);
+ }
+ /* fixme check s */
+
+ /* check that the optional email address is okay */
+ if ((s=get_parameter_value (para, pNAMEEMAIL)))
+ {
+ if (has_invalid_email_chars (s)
+ || *s == '@'
+ || s[strlen(s)-1] == '@'
+ || s[strlen(s)-1] == '.'
+ || strstr(s, ".."))
+ {
+ r = get_parameter (para, pKEYTYPE);
+ log_error ("line %d: not a valid email address\n", r->lnr);
+ return gpg_error (GPG_ERR_INV_PARAMETER);
+ }
+ }
+
+ sprintf (numbuf, "%u", nbits);
+ snprintf (keyparms, DIM (keyparms)-1,
+ "(6:genkey(3:rsa(5:nbits%d:%s)))", strlen (numbuf), numbuf);
+ rc = gpgsm_agent_genkey (keyparms, &public);
+ if (rc)
+ {
+ r = get_parameter (para, pKEYTYPE);
+ log_error ("line %d: key generation failed: %s\n",
+ r->lnr, gpg_strerror (rc));
+ return rc;
+ }
+
+ rc = create_request (para, public, outctrl);
+ xfree (public);
+
+ return rc;
+}
+
+
+/* Parameters are checked, the key pair has been created. Now
+ generate the request and write it out */
+static int
+create_request (struct para_data_s *para, KsbaConstSexp public,
+ struct reqgen_ctrl_s *outctrl)
+{
+ KsbaCertreq cr;
+ KsbaError err;
+ gcry_md_hd_t md;
+ KsbaStopReason stopreason;
+ int rc = 0;
+ const char *s;
+
+ cr = ksba_certreq_new ();
+ if (!cr)
+ return gpg_error (GPG_ERR_ENOMEM);
+
+ rc = gcry_md_open (&md, GCRY_MD_SHA1, 0);
+ if (rc)
+ {
+ log_error ("md_open failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ if (DBG_HASHING)
+ gcry_md_start_debug (md, "cr.cri");
+
+ ksba_certreq_set_hash_function (cr, HASH_FNC, md);
+ ksba_certreq_set_writer (cr, outctrl->writer);
+
+ err = ksba_certreq_add_subject (cr, get_parameter_value (para, pNAMEDN));
+ if (err)
+ {
+ log_error ("error setting the subject's name: %s\n",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+
+ s = get_parameter_value (para, pNAMEEMAIL);
+ if (s)
+ {
+ char *buf;
+
+ buf = xtrymalloc (strlen (s) + 3);
+ if (!buf)
+ {
+ rc = OUT_OF_CORE (errno);
+ 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",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+ }
+
+
+ err = ksba_certreq_set_public_key (cr, public);
+ if (err)
+ {
+ log_error ("error setting the public key: %s\n",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+
+ do
+ {
+ err = ksba_certreq_build (cr, &stopreason);
+ if (err)
+ {
+ log_error ("ksba_certreq_build failed: %s\n", ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+ if (stopreason == KSBA_SR_NEED_SIG)
+ {
+ gcry_sexp_t s_pkey;
+ size_t n;
+ unsigned char grip[20], hexgrip[41];
+ char *sigval;
+ size_t siglen;
+
+ n = gcry_sexp_canon_len (public, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error ("libksba did not return a proper S-Exp\n");
+ err = gpg_error (GPG_ERR_BUG);
+ goto leave;
+ }
+ rc = gcry_sexp_sscan (&s_pkey, NULL, public, n);
+ if (rc)
+ {
+ log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ if ( !gcry_pk_get_keygrip (s_pkey, grip) )
+ {
+ rc = gpg_error (GPG_ERR_GENERAL);
+ log_error ("can't figure out the keygrip\n");
+ gcry_sexp_release (s_pkey);
+ goto leave;
+ }
+ gcry_sexp_release (s_pkey);
+ for (n=0; n < 20; n++)
+ sprintf (hexgrip+n*2, "%02X", grip[n]);
+
+ rc = gpgsm_agent_pksign (hexgrip,
+ gcry_md_read(md, GCRY_MD_SHA1),
+ gcry_md_get_algo_dlen (GCRY_MD_SHA1),
+ GCRY_MD_SHA1,
+ &sigval, &siglen);
+ if (rc)
+ {
+ log_error ("signing failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ err = ksba_certreq_set_sig_val (cr, sigval);
+ xfree (sigval);
+ if (err)
+ {
+ log_error ("failed to store the sig_val: %s\n",
+ ksba_strerror (err));
+ rc = map_ksba_err (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_fd. Multiple
+ keys may be created */
+int
+gpgsm_genkey (CTRL ctrl, int in_fd, FILE *out_fp)
+{
+ int rc;
+ FILE *in_fp;
+ Base64Context b64writer = NULL;
+ KsbaWriter writer;
+
+ in_fp = fdopen (dup (in_fd), "rb");
+ if (!in_fp)
+ {
+ gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("fdopen() failed: %s\n", strerror (errno));
+ return tmperr;
+ }
+
+ ctrl->pem_name = "NEW CERTIFICATE REQUEST";
+ rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer);
+ if (rc)
+ {
+ log_error ("can't create writer: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ rc = read_parameters (in_fp, writer);
+ if (rc)
+ {
+ log_error ("error creating certificate request: %s\n",
+ gpg_strerror (rc));
+ goto leave;
+ }
+
+ rc = gpgsm_finish_writer (b64writer);
+ if (rc)
+ {
+ log_error ("write failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ gpgsm_status (ctrl, STATUS_KEY_CREATED, "P");
+ log_info ("certificate request created\n");
+
+ leave:
+ gpgsm_destroy_writer (b64writer);
+ fclose (in_fp);
+ return rc;
+}
+
diff --git a/sm/decrypt.c b/sm/decrypt.c
new file mode 100644
index 000000000..17483aa49
--- /dev/null
+++ b/sm/decrypt.c
@@ -0,0 +1,506 @@
+/* decrypt.c - Decrypt a message
+ * Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "keydb.h"
+#include "i18n.h"
+
+struct decrypt_filter_parm_s {
+ int algo;
+ int mode;
+ int blklen;
+ gcry_cipher_hd_t hd;
+ char iv[16];
+ size_t ivlen;
+ int any_data; /* dod we push anything through the filter at all? */
+ unsigned char lastblock[16]; /* to strip the padding we have to
+ keep this one */
+ char helpblock[16]; /* needed because there is no block buffering in
+ libgcrypt (yet) */
+ int helpblocklen;
+};
+
+
+
+/* decrypt the session key and fill in the parm structure. The
+ algo and the IV is expected to be already in PARM. */
+static int
+prepare_decryption (const char *hexkeygrip, KsbaConstSexp enc_val,
+ struct decrypt_filter_parm_s *parm)
+{
+ char *seskey = NULL;
+ size_t n, seskeylen;
+ int rc;
+
+ rc = gpgsm_agent_pkdecrypt (hexkeygrip, enc_val,
+ &seskey, &seskeylen);
+ if (rc)
+ {
+ log_error ("error decrypting session key: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ if (DBG_CRYPTO)
+ log_printhex ("pkcs1 encoded session key:", seskey, seskeylen);
+
+ n=0;
+ if (seskeylen == 24)
+ {
+ /* Smells like a 3-des key. This might happen because a SC has
+ already done the unpacking. fixme! */
+ }
+ 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 rightway and therefore we have to skip the zero. This
+ should be fixed in gpg-agent of course. */
+ if (!seskey[n])
+ n++;
+
+ if (seskey[n] != 2 ) /* wrong block type version */
+ {
+ rc = gpg_error (GPG_ERR_INV_SESSION_KEY);
+ goto leave;
+ }
+
+ for (n++; n < seskeylen && seskey[n]; n++) /* skip the random bytes */
+ ;
+ n++; /* and the zero byte */
+ if (n >= seskeylen )
+ {
+ rc = gpg_error (GPG_ERR_INV_SESSION_KEY);
+ goto leave;
+ }
+ }
+
+ if (DBG_CRYPTO)
+ log_printhex ("session key:", seskey+n, seskeylen-n);
+
+ rc = gcry_cipher_open (&parm->hd, parm->algo, parm->mode, 0);
+ if (rc)
+ {
+ log_error ("error creating decryptor: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ rc = gcry_cipher_setkey (parm->hd, seskey+n, seskeylen-n);
+ if (gpg_err_code (rc) == GPG_ERR_WEAK_KEY)
+ {
+ log_info (_("WARNING: message was encrypted with "
+ "a weak key in the symmetric cipher.\n"));
+ rc = 0;
+ }
+ if (rc)
+ {
+ log_error("key setup failed: %s\n", gpg_strerror(rc) );
+ goto leave;
+ }
+
+ gcry_cipher_setiv (parm->hd, parm->iv, parm->ivlen);
+
+ leave:
+ xfree (seskey);
+ return rc;
+}
+
+
+/* This function is called by the KSBA writer just before the actual
+ write is done. The function must take INLEN bytes from INBUF,
+ decrypt it and store it inoutbuf which has a maximum size of
+ maxoutlen. The valid bytes in outbuf should be return in outlen.
+ Due to different buffer sizes or different length of input and
+ output, it may happen that fewer bytes are process or fewer bytes
+ are written. */
+static KsbaError
+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 KSBA_Bug;
+
+ if (maxoutlen < 2*parm->blklen)
+ return KSBA_Bug;
+ /* make some space becuase 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 KSBA_Bug;
+ if (i < blklen)
+ {
+ parm->helpblocklen = i;
+ *outlen = 0;
+ }
+ else
+ {
+ parm->helpblocklen = 0;
+ if (parm->any_data)
+ {
+ memcpy (outbuf, parm->lastblock, blklen);
+ *outlen =blklen;
+ }
+ else
+ *outlen = 0;
+ gcry_cipher_decrypt (parm->hd, parm->lastblock, blklen,
+ parm->helpblock, blklen);
+ parm->any_data = 1;
+ }
+ *inused = orig_inlen - inlen;
+ return 0;
+ }
+
+
+ if (inlen > maxoutlen)
+ inlen = maxoutlen;
+ if (inlen % blklen)
+ { /* store the remainder away */
+ parm->helpblocklen = inlen%blklen;
+ inlen = inlen/blklen*blklen;
+ memcpy (parm->helpblock, (const char*)inbuf+inlen, parm->helpblocklen);
+ }
+
+ *inused = inlen + parm->helpblocklen;
+ if (inlen)
+ {
+ assert (inlen >= blklen);
+ if (parm->any_data)
+ {
+ gcry_cipher_decrypt (parm->hd, (char*)outbuf+blklen, inlen,
+ inbuf, inlen);
+ memcpy (outbuf, parm->lastblock, blklen);
+ memcpy (parm->lastblock,(char*)outbuf+inlen, blklen);
+ *outlen = inlen;
+ }
+ else
+ {
+ gcry_cipher_decrypt (parm->hd, outbuf, inlen, inbuf, inlen);
+ memcpy (parm->lastblock, (char*)outbuf+inlen-blklen, blklen);
+ *outlen = inlen - blklen;
+ parm->any_data = 1;
+ }
+ }
+ else
+ *outlen = 0;
+ return 0;
+}
+
+
+
+/* Perform a decrypt operation. */
+int
+gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp)
+{
+ int rc;
+ KsbaError err;
+ Base64Context b64reader = NULL;
+ Base64Context b64writer = NULL;
+ KsbaReader reader;
+ KsbaWriter writer;
+ KsbaCMS cms = NULL;
+ KsbaStopReason stopreason;
+ KEYDB_HANDLE kh;
+ int recp;
+ FILE *in_fp = NULL;
+ struct decrypt_filter_parm_s dfparm;
+
+ memset (&dfparm, 0, sizeof dfparm);
+
+ kh = keydb_new (0);
+ if (!kh)
+ {
+ log_error (_("failed to allocated keyDB handle\n"));
+ rc = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+
+
+ in_fp = fdopen ( dup (in_fd), "rb");
+ if (!in_fp)
+ {
+ rc = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("fdopen() failed: %s\n", strerror (errno));
+ goto leave;
+ }
+
+ rc = gpgsm_create_reader (&b64reader, ctrl, in_fp, &reader);
+ if (rc)
+ {
+ log_error ("can't create reader: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer);
+ if (rc)
+ {
+ log_error ("can't create writer: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ cms = ksba_cms_new ();
+ if (!cms)
+ {
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ goto leave;
+ }
+
+ err = ksba_cms_set_reader_writer (cms, reader, writer);
+ if (err)
+ {
+ log_debug ("ksba_cms_set_reader_writer failed: %s\n",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+
+ /* parser loop */
+ do
+ {
+ err = ksba_cms_parse (cms, &stopreason);
+ if (err)
+ {
+ log_debug ("ksba_cms_parse failed: %s\n", ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+
+ if (stopreason == KSBA_SR_BEGIN_DATA
+ || stopreason == KSBA_SR_DETACHED_DATA)
+ {
+ int algo, mode;
+ const char *algoid;
+ int any_key = 0;
+
+ 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);
+ }
+
+ goto leave;
+ }
+ 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", ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+
+ for (recp=0; !any_key; recp++)
+ {
+ char *issuer;
+ KsbaSexp serial;
+ KsbaSexp enc_val;
+ char *hexkeygrip = NULL;
+
+ err = ksba_cms_get_issuer_serial (cms, recp, &issuer, &serial);
+ if (err == -1 && recp)
+ break; /* no more recipients */
+ if (err)
+ log_error ("recp %d - error getting info: %s\n",
+ recp, ksba_strerror (err));
+ else
+ {
+ KsbaCert cert = NULL;
+
+ log_debug ("recp %d - issuer: `%s'\n",
+ recp, issuer? issuer:"[NONE]");
+ log_debug ("recp %d - serial: ", recp);
+ gpgsm_dump_serial (serial);
+ log_printf ("\n");
+
+ keydb_search_reset (kh);
+ rc = keydb_search_issuer_sn (kh, issuer, serial);
+ if (rc)
+ {
+ log_error ("failed to find the certificate: %s\n",
+ gpg_strerror(rc));
+ goto oops;
+ }
+
+ rc = keydb_get_cert (kh, &cert);
+ if (rc)
+ {
+ log_error ("failed to get cert: %s\n", gpg_strerror (rc));
+ goto oops;
+ }
+ /* 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);
+
+ oops:
+ xfree (issuer);
+ xfree (serial);
+ ksba_cert_release (cert);
+ }
+
+ if (!hexkeygrip)
+ ;
+ else if (!(enc_val = ksba_cms_get_enc_val (cms, recp)))
+ log_error ("recp %d - error getting encrypted session key\n",
+ recp);
+ else
+ {
+ rc = prepare_decryption (hexkeygrip, enc_val, &dfparm);
+ xfree (enc_val);
+ if (rc)
+ {
+ log_debug ("decrypting session key failed: %s\n",
+ gpg_strerror (rc));
+ }
+ else
+ { /* setup the bulk decrypter */
+ any_key = 1;
+ ksba_writer_set_filter (writer,
+ decrypt_filter,
+ &dfparm);
+ }
+ }
+ }
+ if (!any_key)
+ {
+ rc = gpg_error (GPG_ERR_NO_SECKEY);
+ goto leave;
+ }
+ }
+ else if (stopreason == KSBA_SR_END_DATA)
+ {
+ ksba_writer_set_filter (writer, NULL, NULL);
+ if (dfparm.any_data)
+ { /* write the last block with padding removed */
+ int i, npadding = dfparm.lastblock[dfparm.blklen-1];
+ if (!npadding || npadding > dfparm.blklen)
+ {
+ log_error ("invalid padding with value %d\n", npadding);
+ rc = gpg_error (GPG_ERR_INV_DATA);
+ goto leave;
+ }
+ rc = ksba_writer_write (writer,
+ dfparm.lastblock,
+ dfparm.blklen - npadding);
+ if (rc)
+ {
+ rc = map_ksba_err (rc);
+ goto leave;
+ }
+ for (i=dfparm.blklen - npadding; i < dfparm.blklen; i++)
+ {
+ if (dfparm.lastblock[i] != npadding)
+ {
+ log_error ("inconsistent padding\n");
+ rc = gpg_error (GPG_ERR_INV_DATA);
+ goto leave;
+ }
+ }
+ }
+ }
+
+ }
+ while (stopreason != KSBA_SR_READY);
+
+ rc = gpgsm_finish_writer (b64writer);
+ if (rc)
+ {
+ log_error ("write failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ gpgsm_status (ctrl, STATUS_DECRYPTION_OKAY, NULL);
+
+
+ leave:
+ if (rc)
+ gpgsm_status (ctrl, STATUS_DECRYPTION_FAILED, NULL);
+ ksba_cms_release (cms);
+ gpgsm_destroy_reader (b64reader);
+ gpgsm_destroy_writer (b64writer);
+ keydb_release (kh);
+ if (in_fp)
+ fclose (in_fp);
+ if (dfparm.hd)
+ gcry_cipher_close (dfparm.hd);
+ return rc;
+}
+
+
diff --git a/sm/delete.c b/sm/delete.c
new file mode 100644
index 000000000..53eff864c
--- /dev/null
+++ b/sm/delete.c
@@ -0,0 +1,165 @@
+/* delete.c
+ * Copyright (C) 2002 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "keydb.h"
+#include "i18n.h"
+
+
+/* Delete a certificate or an secret key from a key database. */
+static int
+delete_one (CTRL ctrl, const char *username)
+{
+ int rc = 0;
+ KEYDB_SEARCH_DESC desc;
+ KEYDB_HANDLE kh = NULL;
+ KsbaCert cert = NULL;
+ int duplicates = 0;
+
+ rc = keydb_classify_name (username, &desc);
+ if (rc)
+ {
+ log_error (_("certificate `%s' not found: %s\n"),
+ username, gpg_strerror (rc));
+ gpgsm_status2 (ctrl, STATUS_DELETE_PROBLEM, "1", NULL);
+ goto leave;
+ }
+
+ kh = keydb_new (0);
+ if (!kh)
+ {
+ log_error ("keydb_new failed\n");
+ goto leave;
+ }
+
+
+ rc = keydb_search (kh, &desc, 1);
+ if (!rc)
+ rc = keydb_get_cert (kh, &cert);
+ if (!rc)
+ {
+ char fpr[20];
+
+ gpgsm_get_fingerprint (cert, 0, fpr, NULL);
+
+ next_ambigious:
+ rc = keydb_search (kh, &desc, 1);
+ if (rc == -1)
+ rc = 0;
+ else if (!rc)
+ {
+ KsbaCert cert2 = NULL;
+ 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. */
+ do
+ {
+ keydb_search_reset (kh);
+ rc = keydb_search (kh, &desc, 1);
+ if (rc)
+ {
+ log_error ("problem re-searching certificate: %s\n",
+ gpg_strerror (rc));
+ goto leave;
+ }
+
+ rc = keydb_delete (kh);
+ 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 ctrl, STRLIST 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 000000000..725a81b70
--- /dev/null
+++ b/sm/encrypt.c
@@ -0,0 +1,550 @@
+/* encrypt.c - Encrypt a message
+ * Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "keydb.h"
+#include "i18n.h"
+
+
+struct dek_s {
+ const char *algoid;
+ int algo;
+ gcry_cipher_hd_t chd;
+ char key[32];
+ int keylen;
+ char iv[32];
+ int ivlen;
+};
+typedef struct dek_s *DEK;
+
+struct encrypt_cb_parm_s {
+ FILE *fp;
+ DEK dek;
+ int eof_seen;
+ int ready;
+ int readerror;
+ int bufsize;
+ unsigned char *buffer;
+ int buflen;
+};
+
+
+
+
+
+/* initialize the data encryptionkey (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);
+ }
+
+ 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);
+
+ if (dek->keylen < 100/8)
+ { /* make sure we don't use weak keys */
+ 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_randomize (dek->iv, dek->ivlen, GCRY_STRONG_RANDOM);
+ 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;
+}
+
+
+/* Encode the session key. NBITS is the number of bits which should be
+ used for packing the session key. returns: An mpi with the session
+ key (caller must free) */
+static gcry_mpi_t
+encode_session_key (DEK dek, unsigned int nbits)
+{
+ int nframe = (nbits+7) / 8;
+ byte *p;
+ byte *frame;
+ int i,n;
+ gcry_mpi_t a;
+
+ if (dek->keylen + 7 > nframe || !nframe)
+ log_bug ("can't encode a %d bit key in a %d bits frame\n",
+ dek->keylen*8, nbits );
+
+ /* We encode the session key in this way:
+ *
+ * 0 2 RND(n bytes) 0 KEY(k bytes)
+ *
+ * (But how can we store the leading 0 - the external representaion
+ * of MPIs doesn't allow leading zeroes =:-)
+ *
+ * RND are non-zero random bytes.
+ * KEY is the encryption key (session key)
+ */
+
+ frame = gcry_xmalloc_secure (nframe);
+ n = 0;
+ frame[n++] = 0;
+ frame[n++] = 2;
+ i = nframe - 3 - dek->keylen;
+ assert (i > 0);
+ p = gcry_random_bytes_secure (i, GCRY_STRONG_RANDOM);
+ /* replace zero bytes by new values */
+ for (;;)
+ {
+ int j, k;
+ byte *pp;
+
+ /* count the zero bytes */
+ for(j=k=0; j < i; j++ )
+ {
+ if( !p[j] )
+ k++;
+ }
+ if( !k )
+ break; /* okay: no zero bytes */
+
+ k += k/128; /* better get some more */
+ pp = gcry_random_bytes_secure (k, GCRY_STRONG_RANDOM);
+ for (j=0; j < i && k; j++)
+ {
+ if( !p[j] )
+ p[j] = pp[--k];
+ }
+ xfree (pp);
+ }
+ memcpy (frame+n, p, i);
+ xfree (p);
+
+ n += i;
+ frame[n++] = 0;
+ memcpy (frame+n, dek->key, dek->keylen);
+ n += dek->keylen;
+ assert (n == nframe);
+ if (gcry_mpi_scan (&a, GCRYMPI_FMT_USG, frame, n, &nframe) )
+ BUG ();
+ gcry_free(frame);
+
+ return a;
+}
+
+
+
+/* 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, KsbaCert cert, char **encval)
+{
+ gcry_sexp_t s_ciph, s_data, s_pkey;
+ int rc;
+ KsbaSexp 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, 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 */
+ {
+ /* fixme: actually the pkcs-1 encoding should go into libgcrypt */
+ gcry_mpi_t data = encode_session_key (dek, gcry_pk_get_nbits (s_pkey));
+ if (!data)
+ {
+ gcry_mpi_release (data);
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+ if (gcry_sexp_build (&s_data, NULL, "%m", data))
+ BUG ();
+ gcry_mpi_release (data);
+ }
+
+ /* 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 */
+ len = gcry_sexp_sprint (s_ciph, GCRYSEXP_FMT_CANON, NULL, 0);
+ assert (len);
+ buf = xtrymalloc (len);
+ if (!buf)
+ {
+ gpg_error_t tmperr = OUT_OF_CORE (errno);
+ gcry_sexp_release (s_ciph);
+ return tmperr;
+ }
+ len = gcry_sexp_sprint (s_ciph, GCRYSEXP_FMT_CANON, buf, len);
+ assert (len);
+
+ *encval = buf;
+ return 0;
+}
+
+
+
+/* do the actual encryption */
+static int
+encrypt_cb (void *cb_value, char *buffer, size_t count, size_t *nread)
+{
+ struct encrypt_cb_parm_s *parm = cb_value;
+ int blklen = parm->dek->ivlen;
+ unsigned char *p;
+ size_t n;
+
+ *nread = 0;
+ if (!buffer)
+ return -1; /* not supported */
+
+ if (parm->ready)
+ return -1;
+
+ if (count < blklen)
+ BUG ();
+
+ if (!parm->eof_seen)
+ { /* fillup the buffer */
+ p = parm->buffer;
+ for (n=parm->buflen; n < parm->bufsize; n++)
+ {
+ int c = getc (parm->fp);
+ if (c == EOF)
+ {
+ if (ferror (parm->fp))
+ {
+ parm->readerror = errno;
+ return -1;
+ }
+ parm->eof_seen = 1;
+ break;
+ }
+ p[n] = c;
+ }
+ parm->buflen = n;
+ }
+
+ n = parm->buflen < count? parm->buflen : count;
+ n = n/blklen * blklen;
+ if (n)
+ { /* encrypt the stuff */
+ gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n);
+ *nread = n;
+ /* Who cares about cycles, take the easy way and shift the buffer */
+ parm->buflen -= n;
+ memmove (parm->buffer, parm->buffer+n, parm->buflen);
+ }
+ else if (parm->eof_seen)
+ { /* no complete block but eof: add padding */
+ /* fixme: we should try to do this also in the above code path */
+ int i, npad = blklen - (parm->buflen % blklen);
+ p = parm->buffer;
+ for (n=parm->buflen, i=0; n < parm->bufsize && i < npad; n++, i++)
+ p[n] = npad;
+ gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n);
+ *nread = n;
+ parm->ready = 1;
+ }
+
+ return 0;
+}
+
+
+
+
+/* Perform an encrypt operation.
+
+ Encrypt the data received on DATA-FD and write it to OUT_FP. The
+ recipients are take from the certificate given in recplist; if this
+ is NULL it will be encrypted for a default recipient */
+int
+gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int data_fd, FILE *out_fp)
+{
+ int rc = 0;
+ Base64Context b64writer = NULL;
+ KsbaError err;
+ KsbaWriter writer;
+ KsbaReader reader = NULL;
+ KsbaCMS cms = NULL;
+ KsbaStopReason stopreason;
+ KEYDB_HANDLE kh = NULL;
+ struct encrypt_cb_parm_s encparm;
+ DEK dek = NULL;
+ int recpno;
+ FILE *data_fp = NULL;
+ CERTLIST cl;
+
+ memset (&encparm, 0, sizeof encparm);
+
+ if (!recplist)
+ {
+ log_error(_("no valid recipients given\n"));
+ gpgsm_status (ctrl, STATUS_NO_RECP, "0");
+ rc = gpg_error (GPG_ERR_NO_PUBKEY);
+ goto leave;
+ }
+
+ kh = keydb_new (0);
+ if (!kh)
+ {
+ log_error (_("failed to allocated keyDB handle\n"));
+ rc = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+
+ data_fp = fdopen ( dup (data_fd), "rb");
+ if (!data_fp)
+ {
+ rc = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("fdopen() failed: %s\n", strerror (errno));
+ goto leave;
+ }
+
+ reader = ksba_reader_new ();
+ if (!reader)
+ rc = KSBA_Out_Of_Core;
+ if (!rc)
+ rc = ksba_reader_set_cb (reader, encrypt_cb, &encparm);
+ if (rc)
+ {
+ rc = map_ksba_err (rc);
+ goto leave;
+ }
+ encparm.fp = data_fp;
+
+ ctrl->pem_name = "ENCRYPTED MESSAGE";
+ rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer);
+ if (rc)
+ {
+ log_error ("can't create writer: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ cms = ksba_cms_new ();
+ if (!cms)
+ {
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ goto leave;
+ }
+
+ err = ksba_cms_set_reader_writer (cms, reader, writer);
+ if (err)
+ {
+ log_debug ("ksba_cms_set_reader_writer failed: %s\n",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+
+ /* 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",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+
+ /* create a session key */
+ dek = xtrycalloc (1, sizeof *dek); /* hmmm: should we put it into secmem?*/
+ if (!dek)
+ rc = OUT_OF_CORE (errno);
+ 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",
+ ksba_strerror (err));
+ rc = map_ksba_err (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 (errno);
+ goto leave;
+ }
+
+ /* 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)
+ {
+ char *encval;
+
+ rc = encrypt_dek (dek, cl->cert, &encval);
+ if (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)
+ {
+ log_error ("ksba_cms_add_recipient failed: %s\n",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ xfree (encval);
+ goto leave;
+ }
+
+ err = ksba_cms_set_enc_val (cms, recpno, encval);
+ xfree (encval);
+ if (err)
+ {
+ log_error ("ksba_cms_set_enc_val failed: %s\n",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+ }
+
+ /* main control loop for encryption */
+ recpno = 0;
+ do
+ {
+ err = ksba_cms_build (cms, &stopreason);
+ if (err)
+ {
+ log_debug ("ksba_cms_build failed: %s\n", ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+ }
+ while (stopreason != KSBA_SR_READY);
+
+ if (encparm.readerror)
+ {
+ log_error ("error reading input: %s\n", strerror (encparm.readerror));
+ rc = gpg_error (gpg_err_code_from_errno (encparm.readerror));
+ goto leave;
+ }
+
+
+ rc = gpgsm_finish_writer (b64writer);
+ if (rc)
+ {
+ log_error ("write failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ log_info ("encrypted data created\n");
+
+ leave:
+ ksba_cms_release (cms);
+ gpgsm_destroy_writer (b64writer);
+ ksba_reader_release (reader);
+ keydb_release (kh);
+ xfree (dek);
+ if (data_fp)
+ fclose (data_fp);
+ xfree (encparm.buffer);
+ return rc;
+}
diff --git a/sm/export.c b/sm/export.c
new file mode 100644
index 000000000..93a55debc
--- /dev/null
+++ b/sm/export.c
@@ -0,0 +1,249 @@
+/* export.c
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#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"
+
+static void print_short_info (KsbaCert cert, FILE *fp);
+
+
+
+/* Export all certificates or just those given in NAMES. */
+void
+gpgsm_export (CTRL ctrl, STRLIST names, FILE *fp)
+{
+ KEYDB_HANDLE hd;
+ KEYDB_SEARCH_DESC *desc = NULL;
+ int ndesc;
+ Base64Context b64writer = NULL;
+ KsbaWriter writer;
+ STRLIST sl;
+ KsbaCert cert = NULL;
+ int rc=0;
+ int count = 0;
+ int i;
+
+ hd = keydb_new (0);
+ if (!hd)
+ {
+ log_error ("keydb_new failed\n");
+ goto leave;
+ }
+
+ if (!names)
+ ndesc = 1;
+ else
+ {
+ for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
+ ;
+ }
+
+ desc = xtrycalloc (ndesc, sizeof *desc);
+ if (!ndesc)
+ {
+ log_error ("allocating memory for export failed: %s\n",
+ gpg_strerror (OUT_OF_CORE (errno)));
+ goto leave;
+ }
+
+ if (!names)
+ desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
+ else
+ {
+ for (ndesc=0, sl=names; sl; sl = sl->next)
+ {
+ rc = keydb_classify_name (sl->d, desc+ndesc);
+ if (rc)
+ {
+ log_error ("key `%s' not found: %s\n",
+ sl->d, gpg_strerror (rc));
+ rc = 0;
+ }
+ else
+ ndesc++;
+ }
+ }
+
+ /* If all specifications are done by fingerprint, we switch to
+ ephemeral mode so that _all_ currently available and matching
+ certificates are exported.
+
+ fixme: we should in this case keep a list of certificates to
+ avoid accidential export of duplicate certificates. */
+ 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)); i++)
+ ;
+ if (i == ndesc)
+ keydb_set_ephemeral (hd, 1);
+ }
+
+ while (!(rc = keydb_search (hd, desc, ndesc)))
+ {
+ const unsigned char *image;
+ size_t imagelen;
+
+ 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;
+ }
+
+ 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)
+ putc ('\n', fp);
+ print_short_info (cert, fp);
+ putc ('\n', fp);
+ }
+ count++;
+
+ if (!b64writer)
+ {
+ ctrl->pem_name = "CERTIFICATE";
+ rc = gpgsm_create_writer (&b64writer, ctrl, fp, &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", ksba_strerror (rc));
+ goto leave;
+ }
+
+ if (ctrl->create_pem)
+ {
+ /* We want one certificate per PEM block */
+ rc = gpgsm_finish_writer (b64writer);
+ if (rc)
+ {
+ log_error ("write failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ gpgsm_destroy_writer (b64writer);
+ b64writer = NULL;
+ }
+
+ ksba_cert_release (cert);
+ cert = NULL;
+ }
+ if (rc && rc != -1)
+ log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
+ else if (b64writer)
+ {
+ rc = gpgsm_finish_writer (b64writer);
+ if (rc)
+ {
+ log_error ("write failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ }
+
+ leave:
+ gpgsm_destroy_writer (b64writer);
+ ksba_cert_release (cert);
+ xfree (desc);
+ keydb_release (hd);
+}
+
+
+/* Print some info about the certifciate CERT to FP */
+static void
+print_short_info (KsbaCert cert, FILE *fp)
+{
+ char *p;
+ KsbaSexp sexp;
+ int idx;
+
+ for (idx=0; (p = ksba_cert_get_issuer (cert, idx)); idx++)
+ {
+ fputs (!idx? "Issuer ...: "
+ : "\n aka ...: ", fp);
+ gpgsm_print_name (fp, p);
+ xfree (p);
+ }
+ putc ('\n', fp);
+
+ fputs ("Serial ...: ", fp);
+ 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 == ':')
+ for (s++; len; len--, s++)
+ fprintf (fp, "%02X", *s);
+ }
+ xfree (sexp);
+ }
+ putc ('\n', fp);
+
+ for (idx=0; (p = ksba_cert_get_subject (cert, idx)); idx++)
+ {
+ fputs (!idx? "Subject ..: "
+ : "\n aka ..: ", fp);
+ gpgsm_print_name (fp, p);
+ xfree (p);
+ }
+ putc ('\n', fp);
+}
+
+
+
+
+
+
diff --git a/sm/fingerprint.c b/sm/fingerprint.c
new file mode 100644
index 000000000..028c08aab
--- /dev/null
+++ b/sm/fingerprint.c
@@ -0,0 +1,271 @@
+/* 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <ksba.h>
+
+/* Return the fingerprint of the certificate (we can't put this into
+ libksba because we need libgcrypt support). The caller must
+ provide an array of sufficient length or NULL so that the function
+ allocates the array. If r_len is not NULL, the length of the
+ digest is returned; well, this can also be done by using
+ gcry_md_get_algo_dlen(). If algo is 0, a SHA-1 will be used.
+
+ If there is a problem , the function does never return NULL but a
+ digest of all 0xff.
+ */
+char *
+gpgsm_get_fingerprint (KsbaCert cert, int algo, 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;
+
+ 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", ksba_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 );
+ return array;
+}
+
+
+/* Return an allocated buffer with the formatted fingerprint */
+char *
+gpgsm_get_fingerprint_string (KsbaCert cert, int algo)
+{
+ unsigned char digest[MAX_DIGEST_LEN];
+ char *buf;
+ int len, i;
+
+ 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);
+ *buf = 0;
+ for (i=0; i < len; i++ )
+ sprintf (buf+strlen(buf), i? ":%02X":"%02X", digest[i]);
+ return buf;
+}
+
+/* Return an allocated buffer with the formatted fingerprint as one
+ large hexnumber */
+char *
+gpgsm_get_fingerprint_hexstring (KsbaCert cert, int algo)
+{
+ unsigned char digest[MAX_DIGEST_LEN];
+ char *buf;
+ int len, i;
+
+ 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);
+ *buf = 0;
+ for (i=0; i < len; i++ )
+ sprintf (buf+strlen(buf), "%02X", digest[i]);
+ return buf;
+}
+
+/* Return a certificate ID. These are the last 4 bytes of the SHA-1
+ fingerprint. */
+unsigned long
+gpgsm_get_short_fingerprint (KsbaCert cert)
+{
+ unsigned char digest[20];
+
+ gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL);
+ return ((digest[16]<<24)|(digest[17]<<16)|(digest[18]<< 8)|digest[19]);
+}
+
+
+/* Return the so called KEYGRIP which is the SHA-1 hash of the public
+ key parameters expressed as an canoncial encoded S-Exp. array must
+ be 20 bytes long. returns the array or a newly allocated one if the
+ passed one was NULL */
+char *
+gpgsm_get_keygrip (KsbaCert cert, char *array)
+{
+ gcry_sexp_t s_pkey;
+ int rc;
+ KsbaSexp 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: %s\n", p);
+ 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, p, n);
+ xfree (p);
+ if (rc)
+ {
+ log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
+ return NULL;
+ }
+ array = gcry_pk_get_keygrip (s_pkey, array);
+ gcry_sexp_release (s_pkey);
+ if (!array)
+ {
+ rc = gpg_error (GPG_ERR_GENERAL);
+ log_error ("can't calculate keygrip\n");
+ return NULL;
+ }
+ if (DBG_X509)
+ log_printhex ("keygrip=", array, 20);
+
+ return array;
+}
+
+/* Return an allocated buffer with the keygrip of CERT in from of an
+ hexstring. NULL is returned in case of error */
+char *
+gpgsm_get_keygrip_hexstring (KsbaCert cert)
+{
+ unsigned char grip[20];
+ char *buf, *p;
+ int i;
+
+ gpgsm_get_keygrip (cert, grip);
+ buf = p = xmalloc (20*2+1);
+ for (i=0; i < 20; i++, p += 2 )
+ sprintf (p, "%02X", grip[i]);
+ return buf;
+}
+
+
+
+/* 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
+ delimted from the has by a dot.
+
+ The caller must free the string.
+*/
+char *
+gpgsm_get_certid (KsbaCert cert)
+{
+ KsbaSexp serial;
+ unsigned 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 = 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", p[i]);
+ *endp = 0;
+
+ xfree (serial);
+ return certid;
+}
+
+
+
+
+
+
diff --git a/sm/gpgsm.c b/sm/gpgsm.c
new file mode 100644
index 000000000..c392886ba
--- /dev/null
+++ b/sm/gpgsm.c
@@ -0,0 +1,1458 @@
+/* gpgsm.c - GnuPG for S/MIME
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <assuan.h> /* malloc hooks */
+
+#include "../kbx/keybox.h" /* malloc hooks */
+#include "i18n.h"
+#include "keydb.h"
+#include "sysutils.h"
+
+enum cmd_and_opt_values {
+ aNull = 0,
+ oArmor = 'a',
+ aDetachedSign = 'b',
+ aSym = 'c',
+ aDecrypt = 'd',
+ aEncr = 'e',
+ oInteractive = 'i',
+ oKOption = 'k',
+ oDryRun = 'n',
+ oOutput = 'o',
+ oQuiet = 'q',
+ oRecipient = 'r',
+ aSign = 's',
+ oTextmodeShort= 't',
+ oUser = 'u',
+ oVerbose = 'v',
+ oCompress = 'z',
+ oNotation = 'N',
+ oBatch = 500,
+ aClearsign,
+ aStore,
+ aKeygen,
+ aSignEncr,
+ aSignKey,
+ aLSignKey,
+ aListPackets,
+ aEditKey,
+ aDeleteKey,
+ aImport,
+ aVerify,
+ aVerifyFiles,
+ aListKeys,
+ aListExternalKeys,
+ aListSigs,
+ aListSecretKeys,
+ aSendKeys,
+ aRecvKeys,
+ aExport,
+ aCheckKeys, /* nyi */
+ aServer,
+ aLearnCard,
+ aCallDirmngr,
+ aCallProtectTool,
+ aPasswd,
+
+ oOptions,
+ oDebug,
+ oDebugAll,
+ oDebugWait,
+ oDebugNoChainValidation,
+ oLogFile,
+
+ oEnableSpecialFilenames,
+
+ oAgentProgram,
+ oDisplay,
+ oTTYname,
+ oTTYtype,
+ oLCctype,
+ oLCmessages,
+
+ oDirmngrProgram,
+ oFakedSystemTime,
+
+
+ oAssumeArmor,
+ oAssumeBase64,
+ oAssumeBinary,
+
+ oBase64,
+ oNoArmor,
+
+ oDisableCRLChecks,
+ oEnableCRLChecks,
+
+ oIncludeCerts,
+ oPolicyFile,
+ oDisablePolicyChecks,
+ oEnablePolicyChecks,
+ oAutoIssuerKeyRetrieve,
+
+
+ oTextmode,
+ oFingerprint,
+ oWithFingerprint,
+ oAnswerYes,
+ oAnswerNo,
+ oKeyring,
+ oSecretKeyring,
+ oDefaultKey,
+ oDefRecipient,
+ oDefRecipientSelf,
+ oNoDefRecipient,
+ oStatusFD,
+ oNoComment,
+ oNoVersion,
+ oEmitVersion,
+ oCompletesNeeded,
+ oMarginalsNeeded,
+ oMaxCertDepth,
+ oLoadExtension,
+ oRFC1991,
+ oOpenPGP,
+ oCipherAlgo,
+ oDigestAlgo,
+ oCompressAlgo,
+ oCommandFD,
+ oNoVerbose,
+ oTrustDBName,
+ oNoSecmemWarn,
+ oNoDefKeyring,
+ oNoGreeting,
+ oNoTTY,
+ oNoOptions,
+ oNoBatch,
+ oHomedir,
+ oWithColons,
+ oWithKeyData,
+ oSkipVerify,
+ oCompressKeys,
+ oCompressSigs,
+ oAlwaysTrust,
+ oRunAsShmCP,
+ oSetFilename,
+ oSetPolicyURL,
+ oUseEmbeddedFilename,
+ oComment,
+ oDefaultComment,
+ oThrowKeyid,
+ oForceV3Sigs,
+ oForceMDC,
+ oS2KMode,
+ oS2KDigest,
+ oS2KCipher,
+ oCharset,
+ oNotDashEscaped,
+ oEscapeFrom,
+ oLockOnce,
+ oLockMultiple,
+ oLockNever,
+ oKeyServer,
+ oEncryptTo,
+ oNoEncryptTo,
+ oLoggerFD,
+ oUtf8Strings,
+ oNoUtf8Strings,
+ oDisableCipherAlgo,
+ oDisablePubkeyAlgo,
+ oAllowNonSelfsignedUID,
+ oAllowFreeformUID,
+ oNoLiteral,
+ oSetFilesize,
+ oHonorHttpProxy,
+ oFastListMode,
+ oListOnly,
+ oIgnoreTimeConflict,
+ oNoRandomSeedFile,
+ oNoAutoKeyRetrieve,
+ oUseAgent,
+ oMergeOnly,
+ oTryAllSecrets,
+ oTrustedKey,
+ oEmuMDEncodeBug,
+ aDummy
+ };
+
+
+static ARGPARSE_OPTS opts[] = {
+
+ { 300, NULL, 0, N_("@Commands:\n ") },
+
+ { aSign, "sign", 256, N_("|[file]|make a signature")},
+ { aClearsign, "clearsign", 256, N_("|[file]|make a clear text signature") },
+ { aDetachedSign, "detach-sign", 256, N_("make a detached signature")},
+ { aEncr, "encrypt", 256, N_("encrypt data")},
+ { aSym, "symmetric", 256, N_("encryption only with symmetric cipher")},
+ { aDecrypt, "decrypt", 256, N_("decrypt data (default)")},
+ { aVerify, "verify" , 256, N_("verify a signature")},
+ { aVerifyFiles, "verify-files" , 256, "@" },
+ { aListKeys, "list-keys", 256, N_("list keys")},
+ { aListExternalKeys, "list-external-keys", 256, N_("list external keys")},
+ { aListSecretKeys, "list-secret-keys", 256, N_("list secret keys")},
+ { aListSigs, "list-sigs", 256, N_("list certificate chain")},
+ { aListSigs, "check-sigs",256, "@"},
+ { oFingerprint, "fingerprint", 256, N_("list keys and fingerprints")},
+ { aKeygen, "gen-key", 256, N_("generate a new key pair")},
+ { aDeleteKey, "delete-key",256, N_("remove key from the public keyring")},
+ { aSendKeys, "send-keys" , 256, N_("export keys to a key server") },
+ { aRecvKeys, "recv-keys" , 256, N_("import keys from a key server") },
+ { aImport, "import", 256 , N_("import certificates")},
+ { aExport, "export", 256 , N_("export certificates")},
+ { aLearnCard, "learn-card", 256 ,N_("register a smartcard")},
+ { aServer, "server", 256, N_("run in server mode")},
+ { aCallDirmngr, "call-dirmngr", 256, N_("pass a command to the dirmngr")},
+ { aCallProtectTool, "call-protect-tool", 256,
+ N_("invoke gpg-protect-tool")},
+ { aPasswd, "passwd", 256, N_("change a passphrase")},
+
+ { 301, NULL, 0, N_("@\nOptions:\n ") },
+
+ { oArmor, "armor", 0, N_("create ascii armored output")},
+ { oArmor, "armour", 0, "@" },
+ { oBase64, "base64", 0, N_("create base-64 encoded output")},
+
+ { oAssumeArmor, "assume-armor", 0, N_("assume input is in PEM format")},
+ { oAssumeBase64, "assume-base64", 0,
+ N_("assume input is in base-64 format")},
+ { oAssumeBinary, "assume-binary", 0,
+ N_("assume input is in binary format")},
+
+ { oRecipient, "recipient", 2, N_("|NAME|encrypt for NAME")},
+
+
+ { oDisableCRLChecks, "disable-crl-checks", 0, N_("never consult a CRL")},
+ { oEnableCRLChecks, "enable-crl-checks", 0, "@"},
+
+ { oIncludeCerts, "include-certs", 1,
+ N_("|N|number of certificates to include") },
+
+ { oPolicyFile, "policy-file", 2,
+ N_("|FILE|take policy information from FILE") },
+
+ { oDisablePolicyChecks, "disable-policy-checks", 0,
+ N_("do not check certificate policies")},
+ { oEnablePolicyChecks, "enable-policy-checks", 0, "@"},
+
+ { oAutoIssuerKeyRetrieve, "auto-issuer-key-retrieve", 0,
+ N_("fetch missing issuer certificates")},
+
+#if 0
+ { oDefRecipient, "default-recipient" ,2,
+ N_("|NAME|use NAME as default recipient")},
+ { oDefRecipientSelf, "default-recipient-self" ,0,
+ N_("use the default key as default recipient")},
+ { oNoDefRecipient, "no-default-recipient", 0, "@" },
+ { oEncryptTo, "encrypt-to", 2, "@" },
+ { oNoEncryptTo, "no-encrypt-to", 0, "@" },
+
+#endif
+ { oUser, "local-user",2, N_("use this user-id to sign or decrypt")},
+
+#if 0
+ { oCompress, NULL, 1, N_("|N|set compress level N (0 disables)") },
+ { oTextmodeShort, NULL, 0, "@"},
+ { oTextmode, "textmode", 0, N_("use canonical text mode")},
+#endif
+
+ { oOutput, "output", 2, N_("use as output file")},
+ { oVerbose, "verbose", 0, N_("verbose") },
+ { oQuiet, "quiet", 0, N_("be somewhat more quiet") },
+ { oNoTTY, "no-tty", 0, N_("don't use the terminal at all") },
+ { oLogFile, "log-file" ,2, N_("use a log file for the server")},
+#if 0
+ { oForceV3Sigs, "force-v3-sigs", 0, N_("force v3 signatures") },
+ { oForceMDC, "force-mdc", 0, N_("always use a MDC for encryption") },
+#endif
+ { oDryRun, "dry-run", 0, N_("do not make any changes") },
+ /*{ oInteractive, "interactive", 0, N_("prompt before overwriting") }, */
+ /*{ oUseAgent, "use-agent",0, N_("use the gpg-agent")},*/
+ { oBatch, "batch", 0, N_("batch mode: never ask")},
+ { oAnswerYes, "yes", 0, N_("assume yes on most questions")},
+ { oAnswerNo, "no", 0, N_("assume no on most questions")},
+
+ { oKeyring, "keyring" ,2, N_("add this keyring to the list of keyrings")},
+ { oSecretKeyring, "secret-keyring" ,2, N_("add this secret keyring to the list")},
+ { oDefaultKey, "default-key" ,2, N_("|NAME|use NAME as default secret key")},
+ { oKeyServer, "keyserver",2, N_("|HOST|use this keyserver to lookup keys")},
+ { oCharset, "charset" , 2, N_("|NAME|set terminal charset to NAME") },
+ { oOptions, "options" , 2, N_("read options from file")},
+
+ { oDebug, "debug" ,4|16, "@"},
+ { oDebugAll, "debug-all" ,0, "@"},
+ { oDebugWait, "debug-wait" ,1, "@"},
+ { oDebugNoChainValidation, "debug-no-chain-validation" ,0, "@"},
+ { oStatusFD, "status-fd" ,1, N_("|FD|write status info to this FD") },
+ { aDummy, "no-comment", 0, "@"},
+ { aDummy, "completes-needed", 1, "@"},
+ { aDummy, "marginals-needed", 1, "@"},
+ { oMaxCertDepth, "max-cert-depth", 1, "@" },
+ { aDummy, "trusted-key", 2, "@"},
+ { oLoadExtension, "load-extension" ,2,
+ N_("|FILE|load extension module FILE")},
+ { aDummy, "rfc1991", 0, "@"},
+ { aDummy, "openpgp", 0, "@"},
+ { aDummy, "s2k-mode", 1, "@"},
+ { aDummy, "s2k-digest-algo",2, "@"},
+ { aDummy, "s2k-cipher-algo",2, "@"},
+ { oCipherAlgo, "cipher-algo", 2 , N_("|NAME|use cipher algorithm NAME")},
+ { oDigestAlgo, "digest-algo", 2 ,
+ N_("|NAME|use message digest algorithm NAME")},
+#if 0
+ { oCompressAlgo, "compress-algo", 1 , N_("|N|use compress algorithm N")},
+#endif
+ { aDummy, "throw-keyid", 0, "@"},
+ { aDummy, "notation-data", 2, "@"},
+
+ { 302, NULL, 0, N_(
+ "@\n(See the man page for a complete listing of all commands and options)\n"
+ )},
+
+ { 303, NULL, 0, N_("@\nExamples:\n\n"
+ " -se -r Bob [file] sign and encrypt for user Bob\n"
+ " --clearsign [file] make a clear text signature\n"
+ " --detach-sign [file] make a detached signature\n"
+ " --list-keys [names] show keys\n"
+ " --fingerprint [names] show fingerprints\n" ) },
+
+ /* hidden options */
+ { oNoVerbose, "no-verbose", 0, "@"},
+
+ { oEnableSpecialFilenames, "enable-special-filenames", 0, "@" },
+
+
+ { oTrustDBName, "trustdb-name", 2, "@" },
+ { oNoSecmemWarn, "no-secmem-warning", 0, "@" },
+ { oNoArmor, "no-armor", 0, "@"},
+ { oNoArmor, "no-armour", 0, "@"},
+ { oNoDefKeyring, "no-default-keyring", 0, "@" },
+ { oNoGreeting, "no-greeting", 0, "@" },
+ { oNoOptions, "no-options", 0, "@" }, /* shortcut for --options /dev/null */
+ { oHomedir, "homedir", 2, "@" }, /* defaults to "~/.gnupg" */
+ { oAgentProgram, "agent-program", 2 , "@" },
+ { oDisplay, "display", 2, "@" },
+ { oTTYname, "ttyname", 2, "@" },
+ { oTTYtype, "ttytype", 2, "@" },
+ { oLCctype, "lc-ctype", 2, "@" },
+ { oLCmessages, "lc-messages", 2, "@" },
+ { oDirmngrProgram, "dirmngr-program", 2 , "@" },
+ { oFakedSystemTime, "faked-system-time", 4, "@" }, /* (epoch time) */
+
+
+ { oNoBatch, "no-batch", 0, "@" },
+ { oWithColons, "with-colons", 0, "@"},
+ { oWithKeyData,"with-key-data", 0, "@"},
+ { aListKeys, "list-key", 0, "@" }, /* alias */
+ { aListSigs, "list-sig", 0, "@" }, /* alias */
+ { aListSigs, "check-sig",0, "@" }, /* alias */
+ { oSkipVerify, "skip-verify",0, "@" },
+ { oCompressKeys, "compress-keys",0, "@"},
+ { oCompressSigs, "compress-sigs",0, "@"},
+ { oAlwaysTrust, "always-trust", 0, "@"},
+ { oNoVersion, "no-version", 0, "@"},
+ { oLockOnce, "lock-once", 0, "@" },
+ { oLockMultiple, "lock-multiple", 0, "@" },
+ { oLockNever, "lock-never", 0, "@" },
+ { oLoggerFD, "logger-fd",1, "@" },
+ { oWithFingerprint, "with-fingerprint", 0, "@" },
+ { oDisableCipherAlgo, "disable-cipher-algo", 2, "@" },
+ { oDisablePubkeyAlgo, "disable-pubkey-algo", 2, "@" },
+ { oHonorHttpProxy,"honor-http-proxy", 0, "@" },
+ { oListOnly, "list-only", 0, "@"},
+ { oIgnoreTimeConflict, "ignore-time-conflict", 0, "@" },
+ { oNoRandomSeedFile, "no-random-seed-file", 0, "@" },
+{0} };
+
+
+
+int gpgsm_errors_seen = 0;
+
+/* It is possible that we are currentlu running under setuid permissions */
+static int maybe_setuid = 1;
+
+/* Option --enable-special-filenames */
+static int allow_special_filenames;
+
+
+static char *build_list (const char *text,
+ const char *(*mapf)(int), int (*chkf)(int));
+static void set_cmd (enum cmd_and_opt_values *ret_cmd,
+ enum cmd_and_opt_values new_cmd );
+
+static void emergency_cleanup (void);
+static int check_special_filename (const char *fname);
+static int open_read (const char *filename);
+static FILE *open_fwrite (const char *filename);
+static void run_protect_tool (int argc, char **argv);
+
+
+static int
+our_pk_test_algo (int algo)
+{
+ return 1;
+}
+
+static int
+our_cipher_test_algo (int algo)
+{
+ return 1;
+}
+
+static int
+our_md_test_algo (int algo)
+{
+ return 1;
+}
+
+static const char *
+my_strusage( int level )
+{
+ static char *digests, *pubkeys, *ciphers;
+ const char *p;
+
+ switch (level)
+ {
+ case 11: p = "gpgsm (GnuPG)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\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 31: p = "\nHome: "; break;
+ case 32: p = opt.homedir; break;
+ case 33: p = _("\nSupported algorithms:\n"); break;
+ case 34:
+ if (!ciphers)
+ ciphers = build_list ("Cipher: ", gcry_cipher_algo_name,
+ our_cipher_test_algo );
+ p = ciphers;
+ break;
+ case 35:
+ if (!pubkeys)
+ pubkeys = build_list ("Pubkey: ", gcry_pk_algo_name,
+ our_pk_test_algo );
+ p = pubkeys;
+ break;
+ case 36:
+ if (!digests)
+ digests = build_list("Hash: ", gcry_md_algo_name, our_md_test_algo );
+ p = digests;
+ break;
+
+ default: p = NULL; break;
+ }
+ return p;
+}
+
+
+static char *
+build_list (const char *text, const char * (*mapf)(int), int (*chkf)(int))
+{
+ int i;
+ size_t n=strlen(text)+2;
+ char *list, *p;
+
+ if (maybe_setuid) {
+ gcry_control (GCRYCTL_DROP_PRIVS); /* drop setuid */
+ }
+
+ for (i=1; i < 110; i++ )
+ if (!chkf(i))
+ n += strlen(mapf(i)) + 2;
+ list = xmalloc (21 + n);
+ *list = 0;
+ for (p=NULL, i=1; i < 110; i++)
+ {
+ if (!chkf(i))
+ {
+ if( !p )
+ p = stpcpy (list, text );
+ else
+ p = stpcpy (p, ", ");
+ p = stpcpy (p, mapf(i) );
+ }
+ }
+ if (p)
+ p = stpcpy(p, "\n" );
+ return list;
+}
+
+
+static void
+i18n_init(void)
+{
+#ifdef USE_SIMPLE_GETTEXT
+ set_gettext_file (PACKAGE);
+#else
+# ifdef ENABLE_NLS
+# ifdef HAVE_LC_MESSAGES
+ setlocale (LC_TIME, "");
+ setlocale (LC_MESSAGES, "");
+# else
+ setlocale (LC_ALL, "" );
+# endif
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+# endif
+#endif
+}
+
+
+static void
+wrong_args (const char *text)
+{
+ fputs (_("usage: gpgsm [options] "), stderr);
+ fputs (text, stderr);
+ putc ('\n', stderr);
+ gpgsm_exit (2);
+}
+
+
+static void
+set_debug(void)
+{
+ 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);
+}
+
+
+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;
+}
+
+
+int
+main ( int argc, char **argv)
+{
+ ARGPARSE_ARGS pargs;
+ int orig_argc;
+ char **orig_argv;
+ const char *fname;
+ /* char *username;*/
+ int may_coredump;
+ STRLIST sl, remusr= NULL, locusr=NULL;
+ STRLIST nrings=NULL;
+ int detached_sig = 0;
+ FILE *configfp = NULL;
+ char *configname = NULL;
+ unsigned configlineno;
+ int parse_debug = 0;
+ int no_more_options = 0;
+ int default_config =1;
+ int default_keyring = 1;
+ char *logfile = NULL;
+ int greeting = 0;
+ int nogreeting = 0;
+ int debug_wait = 0;
+ int use_random_seed = 1;
+ int with_fpr = 0;
+ char *def_digest_string = NULL;
+ enum cmd_and_opt_values cmd = 0;
+ struct server_control_s ctrl;
+ CERTLIST recplist = NULL;
+ CERTLIST signerlist = NULL;
+
+ /* trap_unaligned ();*/
+ set_strusage (my_strusage);
+ gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+ /* We don't need any locking in libgcrypt unless we use any kind of
+ threading. */
+ gcry_control (GCRYCTL_DISABLE_INTERNAL_LOCKING);
+
+ /* Please note that we may running SUID(ROOT), so be very CAREFUL
+ when adding any stuff between here and the call to secmem_init()
+ somewhere after the option parsing */
+ log_set_prefix ("gpgsm", 1);
+ /* check that the libraries are suitable. Do it here because the
+ option parse may need services of the library */
+ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
+ {
+ log_fatal( _("libgcrypt is too old (need %s, have %s)\n"),
+ NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
+ }
+ if (!ksba_check_version (NEED_KSBA_VERSION) )
+ {
+ log_fatal( _("libksba is too old (need %s, have %s)\n"),
+ NEED_KSBA_VERSION, ksba_check_version (NULL) );
+ }
+
+ gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
+
+ may_coredump = disable_core_dumps ();
+
+ gnupg_init_signals (0, emergency_cleanup);
+
+ create_dotlock (NULL); /* register locking cleanup */
+ i18n_init();
+
+ opt.def_cipher_algoid = "1.2.840.113549.3.7"; /*des-EDE3-CBC*/
+#ifdef __MINGW32__
+ opt.homedir = read_w32_registry_string ( NULL,
+ "Software\\GNU\\GnuPG", "HomeDir" );
+#else
+ opt.homedir = getenv ("GNUPGHOME");
+#endif
+ if (!opt.homedir || !*opt.homedir )
+ opt.homedir = GNUPG_DEFAULT_HOMEDIR;
+
+ /* first check whether we have a config file on the commandline */
+ orig_argc = argc;
+ orig_argv = argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
+ while (arg_parse( &pargs, opts))
+ {
+ if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
+ parse_debug++;
+ else if (pargs.r_opt == oOptions)
+ { /* yes there is one, so we do not try the default one but
+ read the config file when it is encountered at the
+ commandline */
+ default_config = 0;
+ }
+ else if (pargs.r_opt == oNoOptions)
+ default_config = 0; /* --no-options */
+ else if (pargs.r_opt == oHomedir)
+ opt.homedir = pargs.r.ret_str;
+ else if (pargs.r_opt == aCallProtectTool)
+ break; /* This break makes sure that --version and --help are
+ passed to the protect-tool. */
+ }
+
+
+ /* initialize the secure memory. */
+ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+ maybe_setuid = 0;
+
+ /*
+ Now we are now working under our real uid
+ */
+
+ ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free );
+ assuan_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
+ keybox_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
+
+ /* Setup a default control structure for command line mode */
+ memset (&ctrl, 0, sizeof ctrl);
+ gpgsm_init_default_ctrl (&ctrl);
+ ctrl.no_server = 1;
+ ctrl.status_fd = -1; /* not status output */
+ ctrl.autodetect_encoding = 1;
+
+ /* set the default option file */
+ if (default_config )
+ configname = make_filename (opt.homedir, "gpgsm.conf", NULL);
+ /* cet the default policy file */
+ opt.policy_file = make_filename (opt.homedir, "policies.txt", NULL);
+
+ argc = orig_argc;
+ argv = orig_argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags = 1; /* do not remove the args */
+
+ next_pass:
+ if (configname) {
+ configlineno = 0;
+ configfp = fopen (configname, "r");
+ if (!configfp)
+ {
+ if (default_config)
+ {
+ if (parse_debug)
+ log_info (_("NOTE: no default option file `%s'\n"), configname);
+ }
+ else
+ {
+ log_error (_("option file `%s': %s\n"), configname, strerror(errno));
+ gpgsm_exit(2);
+ }
+ xfree(configname);
+ configname = NULL;
+ }
+ if (parse_debug && configname)
+ log_info (_("reading options from `%s'\n"), configname);
+ default_config = 0;
+ }
+
+ while (!no_more_options
+ && optfile_parse (configfp, configname, &configlineno, &pargs, opts))
+ {
+ switch (pargs.r_opt)
+ {
+ case aServer:
+ opt.batch = 1;
+ set_cmd (&cmd, aServer);
+ break;
+ case aCallDirmngr:
+ opt.batch = 1;
+ set_cmd (&cmd, aCallDirmngr);
+ break;
+
+ case aCallProtectTool:
+ opt.batch = 1;
+ set_cmd (&cmd, aCallProtectTool);
+ no_more_options = 1; /* Stop parsing. */
+ break;
+
+ case aCheckKeys: set_cmd (&cmd, aCheckKeys); break;
+ case aImport: set_cmd (&cmd, aImport); break;
+ case aSendKeys: set_cmd (&cmd, aSendKeys); break;
+ case aRecvKeys: set_cmd (&cmd, aRecvKeys); break;
+ case aExport: set_cmd (&cmd, aExport); break;
+ case aListKeys: set_cmd (&cmd, aListKeys); break;
+ case aListExternalKeys: set_cmd (&cmd, aListExternalKeys); break;
+ case aListSecretKeys: set_cmd (&cmd, aListSecretKeys); break;
+ case aListSigs: set_cmd (&cmd, aListSigs); break;
+
+ case aLearnCard: set_cmd (&cmd, aLearnCard); break;
+
+ case aPasswd: set_cmd (&cmd, aPasswd); break;
+
+ case aDeleteKey:
+ set_cmd (&cmd, aDeleteKey);
+ /*greeting=1;*/
+ break;
+
+ case aDetachedSign:
+ detached_sig = 1;
+ set_cmd (&cmd, aSign );
+ break;
+
+ case aSym: set_cmd (&cmd, aSym); break;
+ case aDecrypt: set_cmd (&cmd, aDecrypt); break;
+ case aEncr: set_cmd (&cmd, aEncr); break;
+ case aSign: set_cmd (&cmd, aSign ); break;
+ case aKeygen: set_cmd (&cmd, aKeygen); greeting=1; break;
+ case aClearsign: set_cmd (&cmd, aClearsign); break;
+ case aVerify: set_cmd (&cmd, aVerify); 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;
+
+ /* 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 oIncludeCerts: ctrl.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 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: opt.debug |= pargs.r.ret_ulong; break;
+ case oDebugAll: opt.debug = ~0; break;
+ case oDebugWait: debug_wait = pargs.r.ret_int; break;
+ case oDebugNoChainValidation: opt.no_chain_validation = 1; break;
+
+ case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break;
+ case oLoggerFD: log_set_fd (pargs.r.ret_int ); break;
+ case oWithFingerprint:
+ with_fpr=1; /*fall thru*/
+ case oFingerprint:
+ opt.fingerprint++;
+ break;
+
+ case oOptions:
+ /* config files may not be nested (silently ignore them) */
+ if (!configfp)
+ {
+ xfree(configname);
+ configname = xstrdup (pargs.r.ret_str);
+ goto next_pass;
+ }
+ break;
+ case oNoOptions: break; /* no-options */
+ case oHomedir: opt.homedir = pargs.r.ret_str; break;
+ case oAgentProgram: opt.agent_program = pargs.r.ret_str; break;
+ case oDisplay: opt.display = xstrdup (pargs.r.ret_str); break;
+ case oTTYname: opt.ttyname = xstrdup (pargs.r.ret_str); break;
+ case oTTYtype: opt.ttytype = xstrdup (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 oFakedSystemTime:
+ gnupg_set_time ( (time_t)pargs.r.ret_ulong, 0);
+ break;
+
+ case oNoDefKeyring: default_keyring = 0; break;
+ case oNoGreeting: nogreeting = 1; break;
+
+ case oDefaultKey:
+ /* fixme:opt.def_secret_key = pargs.r.ret_str;*/
+ break;
+ case oDefRecipient:
+ if (*pargs.r.ret_str)
+ opt.def_recipient = xstrdup (pargs.r.ret_str);
+ break;
+ case oDefRecipientSelf:
+ xfree (opt.def_recipient);
+ opt.def_recipient = NULL;
+ opt.def_recipient_self = 1;
+ break;
+ case oNoDefRecipient:
+ xfree (opt.def_recipient);
+ opt.def_recipient = NULL;
+ opt.def_recipient_self = 0;
+ break;
+
+ case oWithKeyData: opt.with_key_data=1; /* fall thru */
+ case oWithColons: ctrl.with_colons = 1; break;
+
+ case oSkipVerify: opt.skip_verify=1; break;
+
+ case oNoEncryptTo: /*fixme: 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 oTextmodeShort: /*fixme:opt.textmode = 2;*/ break;
+ case oTextmode: /*fixme:opt.textmode=1;*/ break;
+
+ case oUser: /* store the local users, the first one is the default */
+ if (!opt.local_user)
+ opt.local_user = 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 oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break;
+ case oNoRandomSeedFile: use_random_seed = 0; break;
+
+ case oEnableSpecialFilenames: allow_special_filenames =1; break;
+
+
+ case aDummy:
+ break;
+ default:
+ pargs.err = configfp? 1:2;
+ break;
+ }
+ }
+
+ if (configfp)
+ {
+ fclose (configfp);
+ configfp = NULL;
+ xfree (configname);
+ configname = NULL;
+ goto next_pass;
+ }
+
+ xfree (configname);
+ configname = NULL;
+
+ if (log_get_errorcount(0))
+ gpgsm_exit(2);
+
+ if (nogreeting)
+ greeting = 0;
+
+ if (greeting)
+ {
+ fprintf(stderr, "%s %s; %s\n",
+ strusage(11), strusage(13), strusage(14) );
+ fprintf(stderr, "%s\n", strusage(15) );
+ }
+# ifdef IS_DEVELOPMENT_VERSION
+ if (!opt.batch)
+ {
+ log_info ("NOTE: THIS IS A DEVELOPMENT VERSION!\n");
+ log_info ("It is only intended for test purposes and should NOT be\n");
+ log_info ("used in a production environment or with production keys!\n");
+ }
+# endif
+
+ if (may_coredump && !opt.quiet)
+ log_info (_("WARNING: program may create a core file!\n"));
+
+ if (logfile && cmd == aServer)
+ {
+ log_set_file (logfile);
+ log_set_prefix (NULL, 1|2|4);
+ }
+
+ if (gnupg_faked_time_p ())
+ {
+ log_info (_("WARNING: running with faked system time: "));
+ gpgsm_dump_time (gnupg_get_time ());
+ log_printf ("\n");
+ }
+
+/*FIXME if (opt.batch) */
+/* tty_batchmode (1); */
+
+ gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
+
+ set_debug ();
+
+ /* FIXME: should set filenames of libgcrypt explicitly
+ * gpg_opt_homedir = opt.homedir; */
+
+ /* must do this after dropping setuid, because the mapping functions
+ may try to load an module and we may have disabled an algorithm */
+ 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 (def_digest_string)
+ {
+ opt.def_digest_algo = gcry_md_map_name (def_digest_string);
+ xfree (def_digest_string);
+ def_digest_string = NULL;
+ if (our_md_test_algo(opt.def_digest_algo) )
+ log_error (_("selected digest algorithm is invalid\n"));
+ }
+
+ if (log_get_errorcount(0))
+ gpgsm_exit(2);
+
+ /* set the random seed file */
+ if (use_random_seed) {
+ char *p = make_filename (opt.homedir, "random_seed", NULL);
+ gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p);
+ xfree(p);
+ }
+
+
+ if (!cmd && opt.fingerprint && !with_fpr)
+ set_cmd (&cmd, aListKeys);
+
+ if (!nrings && default_keyring) /* add default keybox */
+ keydb_add_resource ("pubring.kbx", 0, 0);
+ for (sl = nrings; sl; sl = sl->next)
+ keydb_add_resource (sl->d, 0, 0);
+ FREE_STRLIST(nrings);
+
+
+ for (sl = locusr; sl; sl = sl->next)
+ {
+ int rc = gpgsm_add_to_certlist (&ctrl, sl->d, 1, &signerlist);
+ if (rc)
+ {
+ log_error (_("can't sign using `%s': %s\n"),
+ sl->d, gpg_strerror (rc));
+ gpgsm_status2 (&ctrl, STATUS_INV_RECP,
+ gpg_err_code (rc) == -1? "1":
+ gpg_err_code (rc) == GPG_ERR_NO_PUBKEY? "1":
+ gpg_err_code (rc) == GPG_ERR_AMBIGUOUS_NAME? "2":
+ gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE? "3":
+ gpg_err_code (rc) == GPG_ERR_CERT_REVOKED? "4":
+ gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED? "5":
+ gpg_err_code (rc) == GPG_ERR_NO_CRL_KNOWN? "6":
+ gpg_err_code (rc) == GPG_ERR_CRL_TOO_OLD? "7":
+ gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH? "8":
+ gpg_err_code (rc) == GPG_ERR_NO_SECKEY? "9":
+ "0",
+ sl->d, NULL);
+ }
+ }
+ for (sl = remusr; sl; sl = sl->next)
+ {
+ int rc = gpgsm_add_to_certlist (&ctrl, sl->d, 0, &recplist);
+ if (rc)
+ {
+ log_error (_("can't encrypt to `%s': %s\n"),
+ sl->d, gpg_strerror (rc));
+ gpgsm_status2 (&ctrl, STATUS_INV_RECP,
+ gpg_err_code (rc) == -1? "1":
+ gpg_err_code (rc) == GPG_ERR_NO_PUBKEY? "1":
+ gpg_err_code (rc) == GPG_ERR_AMBIGUOUS_NAME? "2":
+ gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE? "3":
+ gpg_err_code (rc) == GPG_ERR_CERT_REVOKED? "4":
+ gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED? "5":
+ gpg_err_code (rc) == GPG_ERR_NO_CRL_KNOWN? "6":
+ gpg_err_code (rc) == GPG_ERR_CRL_TOO_OLD? "7":
+ gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH? "8":
+ "0",
+ sl->d, NULL);
+ }
+ }
+ if (log_get_errorcount(0))
+ gpgsm_exit(1); /* must stop for invalid recipients */
+
+
+
+ fname = argc? *argv : NULL;
+
+ switch (cmd)
+ {
+ case aServer:
+ if (debug_wait)
+ {
+ log_debug ("waiting for debugger - my pid is %u .....\n",
+ (unsigned int)getpid());
+ sleep (debug_wait);
+ log_debug ("... okay\n");
+ }
+ gpgsm_server ();
+ 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 */
+ if (!argc)
+ gpgsm_encrypt (&ctrl, recplist, 0, stdout); /* from stdin */
+ else if (argc == 1)
+ gpgsm_encrypt (&ctrl, recplist, open_read (*argv), stdout); /* from file */
+ else
+ wrong_args (_("--encrypt [datafile]"));
+ break;
+
+ case aSign: /* sign the given file */
+ /* FIXME: We don't handle --output yet. We should also allow
+ to concatenate multiple files for signing because that is
+ what gpg does.*/
+ if (!argc)
+ gpgsm_sign (&ctrl, signerlist,
+ 0, detached_sig, stdout); /* create from stdin */
+ else if (argc == 1)
+ gpgsm_sign (&ctrl, signerlist,
+ open_read (*argv), detached_sig, stdout); /* from file */
+ else
+ wrong_args (_("--sign [datafile]"));
+ break;
+
+ case aSignEncr: /* sign and encrypt the given file */
+ log_error ("this command has not yet been implemented\n");
+ break;
+
+ case aClearsign: /* make a clearsig */
+ log_error ("this command has not yet been implemented\n");
+ break;
+
+ case aVerify:
+ {
+ FILE *fp = NULL;
+
+ if (argc == 2 && opt.outfile)
+ log_info ("option --output ignored for a detached signature\n");
+ else if (opt.outfile)
+ fp = open_fwrite (opt.outfile);
+
+ if (!argc)
+ gpgsm_verify (&ctrl, 0, -1, fp); /* normal signature from stdin */
+ else if (argc == 1)
+ gpgsm_verify (&ctrl, open_read (*argv), -1, fp); /* std signature */
+ else if (argc == 2) /* detached signature (sig, detached) */
+ gpgsm_verify (&ctrl, open_read (*argv), open_read (argv[1]), NULL);
+ else
+ wrong_args (_("--verify [signature [detached_data]]"));
+
+ if (fp && fp != stdout)
+ fclose (fp);
+ }
+ break;
+
+ case aVerifyFiles:
+ log_error ("this command has not yet been implemented\n");
+ break;
+
+ case aDecrypt:
+ if (!argc)
+ gpgsm_decrypt (&ctrl, 0, stdout); /* from stdin */
+ else if (argc == 1)
+ gpgsm_decrypt (&ctrl, open_read (*argv), stdout); /* from file */
+ else
+ wrong_args (_("--decrypt [filename]"));
+ break;
+
+ case aDeleteKey:
+ for (sl=NULL; argc; argc--, argv++)
+ add_to_strlist (&sl, *argv);
+ gpgsm_delete (&ctrl, sl);
+ free_strlist(sl);
+ break;
+
+ case aListSigs:
+ ctrl.with_chain = 1;
+ case aListKeys:
+ for (sl=NULL; argc; argc--, argv++)
+ add_to_strlist (&sl, *argv);
+ gpgsm_list_keys (&ctrl, sl, stdout, (0 | (1<<6)));
+ free_strlist(sl);
+ break;
+
+ case aListExternalKeys:
+ for (sl=NULL; argc; argc--, argv++)
+ add_to_strlist (&sl, *argv);
+ gpgsm_list_keys (&ctrl, sl, stdout, (0 | (1<<7)));
+ free_strlist(sl);
+ break;
+
+ case aListSecretKeys:
+ for (sl=NULL; argc; argc--, argv++)
+ add_to_strlist (&sl, *argv);
+ gpgsm_list_keys (&ctrl, sl, stdout, (2 | (1<<6)));
+ free_strlist(sl);
+ break;
+
+ case aKeygen: /* generate a key */
+ log_error ("this function is not yet available from the commandline\n");
+ break;
+
+ case aImport:
+ gpgsm_import_files (&ctrl, argc, argv, open_read);
+ break;
+
+ case aExport:
+ for (sl=NULL; argc; argc--, argv++)
+ add_to_strlist (&sl, *argv);
+ gpgsm_export (&ctrl, sl, stdout);
+ free_strlist(sl);
+ 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 ();
+ if (rc)
+ log_error ("error learning card: %s\n", gpg_strerror (rc));
+ }
+ break;
+
+ case aPasswd:
+ if (argc != 1)
+ wrong_args ("--passwd <key-Id>");
+ else
+ {
+ int rc;
+ KsbaCert cert = NULL;
+ char *grip = NULL;
+
+ rc = gpgsm_find_cert (*argv, &cert);
+ if (rc)
+ ;
+ else if (!(grip = gpgsm_get_keygrip_hexstring (cert)))
+ rc = gpg_error (GPG_ERR_BUG);
+ else
+ rc = gpgsm_agent_passwd (grip);
+ if (rc)
+ log_error ("error changing passphrase: %s\n", gpg_strerror (rc));
+ xfree (grip);
+ ksba_cert_release (cert);
+ }
+ break;
+
+ default:
+ log_error ("invalid command (there is no implicit command)\n");
+ break;
+ }
+
+ /* cleanup */
+ gpgsm_release_certlist (recplist);
+ gpgsm_release_certlist (signerlist);
+ FREE_STRLIST(remusr);
+ FREE_STRLIST(locusr);
+ gpgsm_exit(0);
+ return 8; /*NEVER REACHED*/
+}
+
+/* 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 = 1; /* only include the signer's cert */
+}
+
+
+
+/* Check whether the filename has the form "-&nnnn", where n is a
+ non-zero number. Returns this number or -1 if it is not the case. */
+static int
+check_special_filename (const char *fname)
+{
+ if (allow_special_filenames
+ && fname && *fname == '-' && fname[1] == '&' ) {
+ int i;
+
+ fname += 2;
+ for (i=0; isdigit (fname[i]); i++ )
+ ;
+ if ( !fname[i] )
+ return atoi (fname);
+ }
+ return -1;
+}
+
+
+
+/* Open the FILENAME for read and return the filedescriptor. Stop
+ with an error message in case of problems. "-" denotes stdin and
+ if special filenames are allowed the given fd is opened instead. */
+static int
+open_read (const char *filename)
+{
+ int fd;
+
+ if (filename[0] == '-' && !filename[1])
+ return 0; /* stdin */
+ fd = check_special_filename (filename);
+ if (fd != -1)
+ return fd;
+ fd = open (filename, O_RDONLY);
+ if (fd == -1)
+ {
+ log_error (_("can't open `%s': %s\n"), filename, strerror (errno));
+ gpgsm_exit (2);
+ }
+ return fd;
+}
+
+/* Open FILENAME for fwrite and return the stream. Stop with an error
+ message in case of problems. "-" denotes stdout and if special
+ filenames are allowed the given fd is opened instead. Caller must
+ close the returned stream unless it is stdout. */
+static FILE *
+open_fwrite (const char *filename)
+{
+ int fd;
+ FILE *fp;
+
+ if (filename[0] == '-' && !filename[1])
+ return stdout;
+
+ fd = check_special_filename (filename);
+ if (fd != -1)
+ {
+ fp = fdopen (dup (fd), "wb");
+ if (!fp)
+ {
+ log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno));
+ gpgsm_exit (2);
+ }
+ return fp;
+ }
+ fp = 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)
+{
+ char *pgm = GNUPG_PROTECT_TOOL;
+ char **av;
+ int i;
+
+ av = xcalloc (argc+2, sizeof *av);
+ av[0] = strrchr (pgm, '/');
+ if (!av[0])
+ av[0] = 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));
+ gpgsm_exit (2);
+}
+
diff --git a/sm/gpgsm.h b/sm/gpgsm.h
new file mode 100644
index 000000000..f996d578c
--- /dev/null
+++ b/sm/gpgsm.h
@@ -0,0 +1,274 @@
+/* gpgsm.h - Global definitions for GpgSM
+ * Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#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/errors.h"
+
+#define OUT_OF_CORE(a) (gpg_error (gpg_err_code_from_errno ((a))))
+
+#define MAX_DIGEST_LEN 24
+
+/* A large struct name "opt" to keep global flags */
+struct {
+ unsigned int debug; /* debug flags (DBG_foo_VALUE) */
+ int verbose; /* verbosity level */
+ int quiet; /* be as quiet as possible */
+ int batch; /* run in batch mode, i.e w/o any user interaction */
+ int answer_yes; /* assume yes on most questions */
+ int answer_no; /* assume no on most questions */
+ int dry_run; /* don't change any persistent data */
+
+ const char *homedir; /* configuration directory name */
+ const char *agent_program;
+ char *display;
+ char *ttyname;
+ char *ttytype;
+ char *lc_ctype;
+ char *lc_messages;
+
+ const char *dirmngr_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 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 *def_cipher_algoid; /* cipher algorithm to use if
+ nothing else is specified */
+
+ int def_digest_algo; /* Ditto for hash algorithm */
+ int def_compress_algo; /* Ditto for compress algorithm */
+
+ char *def_recipient; /* userID of the default recipient */
+ int def_recipient_self; /* The default recipient is the default key */
+
+ char *local_user; /* NULL or argument to -u */
+
+ 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 */
+
+ 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 auto_issuer_key_retrieve; /* try to retrieve a missing issuer key. */
+} opt;
+
+
+#define DBG_X509_VALUE 1 /* debug x.509 data reading/writing */
+#define DBG_MPI_VALUE 2 /* debug mpi details */
+#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
+#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
+#define DBG_CACHE_VALUE 64 /* debug the caching */
+#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
+#define DBG_HASHING_VALUE 512 /* debug hashing operations */
+#define DBG_ASSUAN_VALUE 1024 /* debug assuan communication */
+
+#define DBG_X509 (opt.debug & DBG_X509_VALUE)
+#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
+#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE)
+#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
+#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
+#define DBG_ASSUAN (opt.debug & DBG_ASSUAN_VALUE)
+
+struct server_local_s;
+
+/* Note that the default values for this 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;
+ int with_colons; /* use column delimited output format */
+ int with_chain; /* include the certifying certs in a listing */
+
+ 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) */
+};
+typedef struct server_control_s *CTRL;
+
+/* data structure used in base64.c */
+typedef struct base64_context_s *Base64Context;
+
+
+struct certlist_s {
+ struct certlist_s *next;
+ KsbaCert cert;
+};
+typedef struct certlist_s *CERTLIST;
+
+/*-- gpgsm.c --*/
+void gpgsm_exit (int rc);
+void gpgsm_init_default_ctrl (struct server_control_s *ctrl);
+
+/*-- server.c --*/
+void gpgsm_server (void);
+void gpgsm_status (CTRL ctrl, int no, const char *text);
+void gpgsm_status2 (CTRL ctrl, int no, ...);
+void gpgsm_status_with_err_code (CTRL ctrl, int no, const char *text,
+ gpg_err_code_t ec);
+
+/*-- fingerprint --*/
+char *gpgsm_get_fingerprint (KsbaCert cert, int algo, char *array, int *r_len);
+char *gpgsm_get_fingerprint_string (KsbaCert cert, int algo);
+char *gpgsm_get_fingerprint_hexstring (KsbaCert cert, int algo);
+unsigned long gpgsm_get_short_fingerprint (KsbaCert cert);
+char *gpgsm_get_keygrip (KsbaCert cert, char *array);
+char *gpgsm_get_keygrip_hexstring (KsbaCert cert);
+char *gpgsm_get_certid (KsbaCert cert);
+
+
+/*-- base64.c --*/
+int gpgsm_create_reader (Base64Context *ctx,
+ CTRL ctrl, FILE *fp, KsbaReader *r_reader);
+void gpgsm_destroy_reader (Base64Context ctx);
+int gpgsm_create_writer (Base64Context *ctx,
+ CTRL ctrl, FILE *fp, KsbaWriter *r_writer);
+int gpgsm_finish_writer (Base64Context ctx);
+void gpgsm_destroy_writer (Base64Context ctx);
+
+
+/*-- certdump.c --*/
+void gpgsm_print_serial (FILE *fp, KsbaConstSexp p);
+void gpgsm_print_time (FILE *fp, time_t t);
+void gpgsm_print_name (FILE *fp, const char *string);
+
+void gpgsm_dump_cert (const char *text, KsbaCert cert);
+void gpgsm_dump_serial (KsbaConstSexp p);
+void gpgsm_dump_time (time_t t);
+void gpgsm_dump_string (const char *string);
+
+
+
+/*-- certcheck.c --*/
+int gpgsm_check_cert_sig (KsbaCert issuer_cert, KsbaCert cert);
+int gpgsm_check_cms_signature (KsbaCert cert, KsbaConstSexp sigval,
+ gcry_md_hd_t md, int hash_algo);
+/* fixme: move create functions to another file */
+int gpgsm_create_cms_signature (KsbaCert cert, gcry_md_hd_t md, int mdalgo,
+ char **r_sigval);
+
+
+/*-- certchain.c --*/
+int gpgsm_walk_cert_chain (KsbaCert start, KsbaCert *r_next);
+int gpgsm_is_root_cert (KsbaCert cert);
+int gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, time_t *r_exptime);
+int gpgsm_basic_cert_check (KsbaCert cert);
+
+/*-- certlist.c --*/
+int gpgsm_cert_use_sign_p (KsbaCert cert);
+int gpgsm_cert_use_encrypt_p (KsbaCert cert);
+int gpgsm_cert_use_verify_p (KsbaCert cert);
+int gpgsm_cert_use_decrypt_p (KsbaCert cert);
+int gpgsm_cert_use_cert_p (KsbaCert cert);
+int gpgsm_add_to_certlist (CTRL ctrl, const char *name, int secret,
+ CERTLIST *listaddr);
+void gpgsm_release_certlist (CERTLIST list);
+int gpgsm_find_cert (const char *name, KsbaCert *r_cert);
+
+/*-- keylist.c --*/
+void gpgsm_list_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode);
+
+/*-- import.c --*/
+int gpgsm_import (CTRL ctrl, int in_fd);
+int gpgsm_import_files (CTRL ctrl, int nfiles, char **files,
+ int (*of)(const char *fname));
+
+/*-- export.c --*/
+void gpgsm_export (CTRL ctrl, STRLIST names, FILE *fp);
+
+/*-- delete.c --*/
+int gpgsm_delete (CTRL ctrl, STRLIST names);
+
+/*-- verify.c --*/
+int gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp);
+
+/*-- sign.c --*/
+int gpgsm_get_default_cert (KsbaCert *r_cert);
+int gpgsm_sign (CTRL ctrl, CERTLIST signerlist,
+ int data_fd, int detached, FILE *out_fp);
+
+/*-- encrypt.c --*/
+int gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int in_fd, FILE *out_fp);
+
+/*-- decrypt.c --*/
+int gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp);
+
+/*-- certreqgen.c --*/
+int gpgsm_genkey (CTRL ctrl, int in_fd, FILE *out_fp);
+
+/*-- call-agent.c --*/
+int gpgsm_agent_pksign (const char *keygrip,
+ unsigned char *digest,
+ size_t digestlen,
+ int digestalgo,
+ char **r_buf, size_t *r_buflen);
+int gpgsm_agent_pkdecrypt (const char *keygrip,
+ KsbaConstSexp ciphertext,
+ char **r_buf, size_t *r_buflen);
+int gpgsm_agent_genkey (KsbaConstSexp keyparms, KsbaSexp *r_pubkey);
+int gpgsm_agent_istrusted (KsbaCert cert);
+int gpgsm_agent_havekey (const char *hexkeygrip);
+int gpgsm_agent_marktrusted (KsbaCert cert);
+int gpgsm_agent_learn (void);
+int gpgsm_agent_passwd (const char *hexkeygrip);
+
+/*-- call-dirmngr.c --*/
+int gpgsm_dirmngr_isvalid (KsbaCert cert);
+int gpgsm_dirmngr_lookup (CTRL ctrl, STRLIST names,
+ void (*cb)(void*, KsbaCert), void *cb_value);
+int gpgsm_dirmngr_run_command (CTRL ctrl, const char *command,
+ int argc, char **argv);
+
+
+
+
+
+#endif /*GPGSM_H*/
diff --git a/sm/import.c b/sm/import.c
new file mode 100644
index 000000000..17dc3d66c
--- /dev/null
+++ b/sm/import.c
@@ -0,0 +1,349 @@
+/* import.c - Import certificates
+ * Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "keydb.h"
+#include "i18n.h"
+
+struct stats_s {
+ unsigned long count;
+ unsigned long imported;
+ unsigned long unchanged;
+ unsigned long not_imported;
+};
+
+
+
+static void
+print_imported_status (CTRL ctrl, KsbaCert cert)
+{
+ char *fpr;
+
+ fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
+ gpgsm_status2 (ctrl, STATUS_IMPORTED, fpr, "[X.509]", 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 ctrl, KsbaCert 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 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->not_imported)
+ log_info (_(" not imported: %lu\n"), stats->not_imported);
+ }
+
+ sprintf (buf, "%lu 0 %lu 0 %lu 0 0 0 0 0 0 0 0 %lu",
+ stats->count,
+ stats->imported,
+ stats->unchanged,
+ stats->not_imported
+ );
+ gpgsm_status (ctrl, STATUS_IMPORT_RES, buf);
+}
+
+
+
+static void
+check_and_store (CTRL ctrl, struct stats_s *stats, KsbaCert cert, int depth)
+{
+ int rc;
+
+ stats->count++;
+ if ( depth >= 50 )
+ {
+ log_error (_("certificate chain too long\n"));
+ stats->not_imported++;
+ print_import_problem (ctrl, cert, 3);
+ return;
+ }
+
+ rc = gpgsm_basic_cert_check (cert);
+ if (!rc)
+ {
+ int existed;
+
+ if (!keydb_store_cert (cert, 0, &existed))
+ {
+ KsbaCert next = NULL;
+
+ if (!existed)
+ {
+ print_imported_status (ctrl, cert);
+ stats->imported++;
+ }
+ else
+ 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.*/
+ else if (!gpgsm_walk_cert_chain (cert, &next))
+ {
+ check_and_store (ctrl, stats, next, depth+1);
+ ksba_cert_release (next);
+ }
+ }
+ else
+ {
+ log_error (_("error storing certificate\n"));
+ stats->not_imported++;
+ print_import_problem (ctrl, cert, 4);
+ }
+ }
+ else
+ {
+ log_error (_("basic certificate checks failed - not imported\n"));
+ stats->not_imported++;
+ print_import_problem (ctrl, cert,
+ gpg_err_code (rc) == GPG_ERR_MISSING_CERT? 2 :
+ gpg_err_code (rc) == GPG_ERR_BAD_CERT? 1 : 0);
+ }
+}
+
+
+
+
+static int
+import_one (CTRL ctrl, struct stats_s *stats, int in_fd)
+{
+ int rc;
+ Base64Context b64reader = NULL;
+ KsbaReader reader;
+ KsbaCert cert = NULL;
+ KsbaCMS cms = NULL;
+ FILE *fp = NULL;
+ KsbaContentType ct;
+
+ fp = fdopen ( dup (in_fd), "rb");
+ if (!fp)
+ {
+ rc = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("fdopen() failed: %s\n", strerror (errno));
+ goto leave;
+ }
+
+ rc = gpgsm_create_reader (&b64reader, ctrl, fp, &reader);
+ if (rc)
+ {
+ log_error ("can't create reader: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ ct = ksba_cms_identify (reader);
+ if (ct == KSBA_CT_SIGNED_DATA)
+ { /* This is probably a signed-only message - import the certs */
+ KsbaStopReason stopreason;
+ int i;
+
+ cms = ksba_cms_new ();
+ if (!cms)
+ {
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ goto leave;
+ }
+
+ rc = ksba_cms_set_reader_writer (cms, reader, NULL);
+ if (rc)
+ {
+ log_error ("ksba_cms_set_reader_writer failed: %s\n",
+ ksba_strerror (rc));
+ rc = map_ksba_err (rc);
+ goto leave;
+ }
+
+
+ do
+ {
+ rc = ksba_cms_parse (cms, &stopreason);
+ if (rc)
+ {
+ log_error ("ksba_cms_parse failed: %s\n", ksba_strerror (rc));
+ rc = map_ksba_err (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 if (ct == KSBA_CT_NONE)
+ { /* Failed to identify this message - assume a certificate */
+
+ cert = ksba_cert_new ();
+ if (!cert)
+ {
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ goto leave;
+ }
+
+ rc = ksba_cert_read_der (cert, reader);
+ if (rc)
+ {
+ rc = map_ksba_err (rc);
+ goto leave;
+ }
+
+ check_and_store (ctrl, stats, cert, 0);
+ }
+ else
+ {
+ log_error ("can't extract certificates from input\n");
+ rc = gpg_error (GPG_ERR_NO_DATA);
+ }
+
+ leave:
+ ksba_cms_release (cms);
+ ksba_cert_release (cert);
+ gpgsm_destroy_reader (b64reader);
+ if (fp)
+ fclose (fp);
+ return rc;
+}
+
+
+int
+gpgsm_import (CTRL ctrl, int in_fd)
+{
+ int rc;
+ struct stats_s stats;
+
+ memset (&stats, 0, sizeof stats);
+ 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 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;
+}
+
+
diff --git a/sm/keydb.c b/sm/keydb.c
new file mode 100644
index 000000000..fe6556549
--- /dev/null
+++ b/sm/keydb.c
@@ -0,0 +1,1282 @@
+/* keydb.c - key database dispatcher
+ * Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "gpgsm.h"
+#include "../kbx/keybox.h"
+#include "keydb.h"
+#include "i18n.h"
+
+#define DIRSEP_C '/'
+
+static int active_handles;
+
+typedef enum {
+ KEYDB_RESOURCE_TYPE_NONE = 0,
+ KEYDB_RESOURCE_TYPE_KEYBOX
+} KeydbResourceType;
+#define MAX_KEYDB_RESOURCES 20
+
+struct resource_item {
+ KeydbResourceType type;
+ union {
+ KEYBOX_HANDLE kr;
+ } u;
+ void *token;
+ int secret;
+ DOTLOCK lockhandle;
+};
+
+static struct resource_item all_resources[MAX_KEYDB_RESOURCES];
+static int used_resources;
+
+struct keydb_handle {
+ int locked;
+ int found;
+ int current;
+ int is_ephemeral;
+ int used; /* items in active */
+ struct resource_item active[MAX_KEYDB_RESOURCES];
+};
+
+
+static int lock_all (KEYDB_HANDLE hd);
+static void unlock_all (KEYDB_HANDLE hd);
+
+
+/*
+ * Register a resource (which currently may only be a keybox file).
+ * The first keybox which is added by this function is
+ * created if it does not exist.
+ * Note: this function may be called before secure memory is
+ * available.
+ */
+int
+keydb_add_resource (const char *url, int force, int secret)
+{
+ static int any_secret, any_public;
+ const char *resname = url;
+ char *filename = NULL;
+ int rc = 0;
+ FILE *fp;
+ KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
+ const char *created_fname = NULL;
+
+ /* Do we have an URL?
+ gnupg-kbx:filename := this is a plain keybox
+ filename := See what is is, but create as plain keybox.
+ */
+ if (strlen (resname) > 10)
+ {
+ if (!strncmp (resname, "gnupg-kbx:", 10) )
+ {
+ rt = KEYDB_RESOURCE_TYPE_KEYBOX;
+ resname += 10;
+ }
+#if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__)
+ else if (strchr (resname, ':'))
+ {
+ log_error ("invalid key resource URL `%s'\n", url );
+ rc = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+#endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */
+ }
+
+ if (*resname != DIRSEP_C )
+ { /* do tilde expansion etc */
+ if (strchr(resname, DIRSEP_C) )
+ filename = make_filename (resname, NULL);
+ else
+ filename = make_filename (opt.homedir, resname, NULL);
+ }
+ else
+ filename = xstrdup (resname);
+
+ if (!force)
+ force = secret? !any_secret : !any_public;
+
+ /* see whether we can determine the filetype */
+ if (rt == KEYDB_RESOURCE_TYPE_NONE)
+ {
+ FILE *fp2 = fopen( filename, "rb" );
+
+ if (fp2) {
+ u32 magic;
+
+ /* FIXME: check for the keybox magic */
+ if (fread( &magic, 4, 1, fp2) == 1 )
+ {
+ if (magic == 0x13579ace || magic == 0xce9a5713)
+ ; /* GDBM magic - no more support */
+ else
+ rt = KEYDB_RESOURCE_TYPE_KEYBOX;
+ }
+ else /* maybe empty: assume ring */
+ rt = KEYDB_RESOURCE_TYPE_KEYBOX;
+ fclose (fp2);
+ }
+ else /* no file yet: create ring */
+ rt = KEYDB_RESOURCE_TYPE_KEYBOX;
+ }
+
+ switch (rt)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ log_error ("unknown type of key resource `%s'\n", url );
+ rc = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ fp = fopen (filename, "rb");
+ if (!fp && !force)
+ {
+ rc = gpg_error (gpg_err_code_from_errno (errno));
+ goto leave;
+ }
+
+ if (!fp)
+ { /* no file */
+#if 0 /* no autocreate of the homedirectory yet */
+ {
+ char *last_slash_in_filename;
+
+ last_slash_in_filename = strrchr (filename, DIRSEP_C);
+ *last_slash_in_filename = 0;
+ if (access (filename, F_OK))
+ { /* on the first time we try to create the default
+ homedir and in this case the process will be
+ terminated, so that on the next invocation can
+ read the options file in on startup */
+ try_make_homedir (filename);
+ rc = gpg_error (GPG_ERR_FILE_OPEN_ERROR);
+ *last_slash_in_filename = DIRSEP_C;
+ goto leave;
+ }
+ *last_slash_in_filename = DIRSEP_C;
+ }
+#endif
+ fp = fopen (filename, "w");
+ if (!fp)
+ {
+ rc = gpg_error (gpg_err_code_from_errno (errno));
+ log_error (_("error creating keybox `%s': %s\n"),
+ filename, strerror(errno));
+ goto leave;
+ }
+
+ if (!opt.quiet)
+ log_info (_("keybox `%s' created\n"), filename);
+ created_fname = filename;
+ }
+ fclose (fp);
+ fp = NULL;
+ /* now register the file */
+ {
+
+ void *token = keybox_register_file (filename, secret);
+ if (!token)
+ ; /* already registered - ignore it */
+ else if (used_resources >= MAX_KEYDB_RESOURCES)
+ rc = gpg_error (GPG_ERR_RESOURCE_LIMIT);
+ else
+ {
+ all_resources[used_resources].type = rt;
+ all_resources[used_resources].u.kr = NULL; /* Not used here */
+ all_resources[used_resources].token = token;
+ all_resources[used_resources].secret = secret;
+
+ all_resources[used_resources].lockhandle
+ = create_dotlock (filename);
+ if (!all_resources[used_resources].lockhandle)
+ log_fatal ( _("can't create lock for `%s'\n"), filename);
+
+ used_resources++;
+ }
+ }
+ break;
+ default:
+ log_error ("resource type of `%s' not supported\n", url);
+ rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ goto leave;
+ }
+
+ /* fixme: check directory permissions and print a warning */
+
+ leave:
+ if (rc)
+ log_error ("keyblock resource `%s': %s\n", filename, gpg_strerror(rc));
+ else if (secret)
+ any_secret = 1;
+ else
+ any_public = 1;
+ xfree (filename);
+ return rc;
+}
+
+
+KEYDB_HANDLE
+keydb_new (int secret)
+{
+ KEYDB_HANDLE hd;
+ int i, j;
+
+ hd = xcalloc (1, sizeof *hd);
+ hd->found = -1;
+
+ assert (used_resources <= MAX_KEYDB_RESOURCES);
+ for (i=j=0; i < used_resources; i++)
+ {
+ if (!all_resources[i].secret != !secret)
+ continue;
+ switch (all_resources[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ hd->active[j].type = all_resources[i].type;
+ hd->active[j].token = all_resources[i].token;
+ hd->active[j].secret = all_resources[i].secret;
+ hd->active[j].lockhandle = all_resources[i].lockhandle;
+ hd->active[j].u.kr = keybox_new (all_resources[i].token, secret);
+ if (!hd->active[j].u.kr)
+ {
+ xfree (hd);
+ return NULL; /* fixme: release all previously allocated handles*/
+ }
+ j++;
+ break;
+ }
+ }
+ hd->used = j;
+
+ active_handles++;
+ return hd;
+}
+
+void
+keydb_release (KEYDB_HANDLE hd)
+{
+ int i;
+
+ if (!hd)
+ return;
+ assert (active_handles > 0);
+ active_handles--;
+
+ unlock_all (hd);
+ for (i=0; i < hd->used; i++)
+ {
+ switch (hd->active[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ keybox_release (hd->active[i].u.kr);
+ break;
+ }
+ }
+
+ xfree (hd);
+}
+
+
+/* Return the name of the current resource. This is function first
+ looks for the last found found, then for the current search
+ position, and last returns the first available resource. The
+ returned string is only valid as long as the handle exists. This
+ function does only return NULL if no handle is specified, in all
+ other error cases an empty string is returned. */
+const char *
+keydb_get_resource_name (KEYDB_HANDLE hd)
+{
+ int idx;
+ const char *s = NULL;
+
+ if (!hd)
+ return NULL;
+
+ if ( hd->found >= 0 && hd->found < hd->used)
+ idx = hd->found;
+ else if ( hd->current >= 0 && hd->current < hd->used)
+ idx = hd->current;
+ else
+ idx = 0;
+
+ switch (hd->active[idx].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ s = NULL;
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ s = keybox_get_resource_name (hd->active[idx].u.kr);
+ break;
+ }
+
+ return s? s: "";
+}
+
+/* Switch the handle into ephemeral mode and return the orginal value. */
+int
+keydb_set_ephemeral (KEYDB_HANDLE hd, int yes)
+{
+ int i;
+
+ if (!hd)
+ return 0;
+
+ yes = !!yes;
+ if (hd->is_ephemeral != yes)
+ {
+ for (i=0; i < hd->used; i++)
+ {
+ switch (hd->active[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ keybox_set_ephemeral (hd->active[i].u.kr, yes);
+ break;
+ }
+ }
+ }
+
+ i = hd->is_ephemeral;
+ hd->is_ephemeral = yes;
+ return i;
+}
+
+
+
+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 sequence by all processes. We are
+ cuurently only allowing one resource so it is not a problem. */
+ for (i=0; i < hd->used; i++)
+ {
+ switch (hd->active[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ if (hd->active[i].lockhandle)
+ rc = make_dotlock (hd->active[i].lockhandle, -1);
+ break;
+ }
+ if (rc)
+ break;
+ }
+
+ if (rc)
+ {
+ /* revert the already set locks */
+ for (i--; i >= 0; i--)
+ {
+ switch (hd->active[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ if (hd->active[i].lockhandle)
+ release_dotlock (hd->active[i].lockhandle);
+ break;
+ }
+ }
+ }
+ else
+ hd->locked = 1;
+
+ return rc;
+}
+
+static void
+unlock_all (KEYDB_HANDLE hd)
+{
+ int i;
+
+ if (!hd->locked)
+ return;
+
+ for (i=hd->used-1; i >= 0; i--)
+ {
+ switch (hd->active[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ if (hd->active[i].lockhandle)
+ release_dotlock (hd->active[i].lockhandle);
+ break;
+ }
+ }
+ hd->locked = 0;
+}
+
+
+#if 0
+/*
+ * Return the last found keybox. Caller must free it.
+ * The returned keyblock has the kbode flag bit 0 set for the node with
+ * the public key used to locate the keyblock or flag bit 1 set for
+ * the user ID node.
+ */
+int
+keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
+{
+ int rc = 0;
+
+ if (!hd)
+ return G10ERR_INV_ARG;
+
+ if ( hd->found < 0 || hd->found >= hd->used)
+ return -1; /* nothing found */
+
+ switch (hd->active[hd->found].type) {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ rc = G10ERR_GENERAL; /* oops */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ rc = keybox_get_keyblock (hd->active[hd->found].u.kr, ret_kb);
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * update the current keyblock with KB
+ */
+int
+keydb_update_keyblock (KEYDB_HANDLE hd, KBNODE kb)
+{
+ int rc = 0;
+
+ if (!hd)
+ return G10ERR_INV_ARG;
+
+ if ( hd->found < 0 || hd->found >= hd->used)
+ return -1; /* nothing found */
+
+ if( opt.dry_run )
+ return 0;
+
+ rc = lock_all (hd);
+ if (rc)
+ return rc;
+
+ switch (hd->active[hd->found].type) {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ rc = G10ERR_GENERAL; /* oops */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ rc = keybox_update_keyblock (hd->active[hd->found].u.kr, kb);
+ break;
+ }
+
+ unlock_all (hd);
+ return rc;
+}
+
+
+/*
+ * Insert a new KB into one of the resources.
+ */
+int
+keydb_insert_keyblock (KEYDB_HANDLE hd, KBNODE kb)
+{
+ int rc = -1;
+ int idx;
+
+ if (!hd)
+ return G10ERR_INV_ARG;
+
+ if( opt.dry_run )
+ return 0;
+
+ if ( hd->found >= 0 && hd->found < hd->used)
+ idx = hd->found;
+ else if ( hd->current >= 0 && hd->current < hd->used)
+ idx = hd->current;
+ else
+ return G10ERR_GENERAL;
+
+ rc = lock_all (hd);
+ if (rc)
+ return rc;
+
+ switch (hd->active[idx].type) {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ rc = G10ERR_GENERAL; /* oops */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ rc = keybox_insert_keyblock (hd->active[idx].u.kr, kb);
+ break;
+ }
+
+ unlock_all (hd);
+ return rc;
+}
+
+#endif /*disabled code*/
+
+
+
+/*
+ Return the last found keybox. Caller must free it. The returned
+ keyblock has the kbode flag bit 0 set for the node with the public
+ key used to locate the keyblock or flag bit 1 set for the user ID
+ node. */
+int
+keydb_get_cert (KEYDB_HANDLE hd, KsbaCert *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;
+}
+
+/*
+ * Insert a new Certificate into one of the resources.
+ */
+int
+keydb_insert_cert (KEYDB_HANDLE hd, KsbaCert cert)
+{
+ int rc = -1;
+ int idx;
+ 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);
+
+ rc = lock_all (hd);
+ if (rc)
+ return rc;
+
+ gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/
+
+ switch (hd->active[idx].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ rc = gpg_error (GPG_ERR_GENERAL);
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ rc = keybox_insert_cert (hd->active[idx].u.kr, cert, digest);
+ break;
+ }
+
+ unlock_all (hd);
+ return rc;
+}
+
+
+
+/* update the current keyblock with KB */
+int
+keydb_update_cert (KEYDB_HANDLE hd, KsbaCert cert)
+{
+ int rc = 0;
+ char digest[20];
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ if ( hd->found < 0 || hd->found >= hd->used)
+ return -1; /* nothing found */
+
+ if (opt.dry_run)
+ return 0;
+
+ rc = lock_all (hd);
+ if (rc)
+ return rc;
+
+ gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/
+
+ switch (hd->active[hd->found].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ rc = gpg_error (GPG_ERR_GENERAL); /* oops */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ rc = keybox_update_cert (hd->active[hd->found].u.kr, cert, digest);
+ break;
+ }
+
+ unlock_all (hd);
+ return rc;
+}
+
+
+/*
+ * The current keyblock or cert will be deleted.
+ */
+int
+keydb_delete (KEYDB_HANDLE hd)
+{
+ int 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;
+
+ rc = lock_all (hd);
+ if (rc)
+ return rc;
+
+ 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;
+ }
+
+ 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;
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ rc = keydb_search_reset (hd); /* this does reset hd->current */
+ if (rc)
+ return rc;
+
+ for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++)
+ {
+ switch (hd->active[hd->current].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ BUG();
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ if (keybox_is_writable (hd->active[hd->current].token))
+ return 0; /* found (hd->current is set to it) */
+ break;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * Rebuild the caches of all key resources.
+ */
+void
+keydb_rebuild_caches (void)
+{
+ int i;
+
+ for (i=0; i < used_resources; i++)
+ {
+ if (all_resources[i].secret)
+ continue;
+ switch (all_resources[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+/* rc = keybox_rebuild_cache (all_resources[i].token); */
+/* if (rc) */
+/* log_error (_("failed to rebuild keybox cache: %s\n"), */
+/* g10_errstr (rc)); */
+ break;
+ }
+ }
+}
+
+
+
+/*
+ * Start the next search on this handle right at the beginning
+ */
+int
+keydb_search_reset (KEYDB_HANDLE hd)
+{
+ int i, rc = 0;
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ hd->current = 0;
+ hd->found = -1;
+ /* and reset all resources */
+ for (i=0; !rc && i < hd->used; i++)
+ {
+ switch (hd->active[i].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ rc = keybox_search_reset (hd->active[i].u.kr);
+ break;
+ }
+ }
+ return rc; /* fixme: we need to map error codes or share them with
+ all modules*/
+}
+
+/*
+ * Search through all keydb resources, starting at the current position,
+ * for a keyblock which contains one of the keys described in the DESC array.
+ */
+int
+keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc)
+{
+ int rc = -1;
+
+ if (!hd)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ while (rc == -1 && hd->current >= 0 && hd->current < hd->used)
+ {
+ switch (hd->active[hd->current].type)
+ {
+ case KEYDB_RESOURCE_TYPE_NONE:
+ BUG(); /* we should never see it here */
+ break;
+ case KEYDB_RESOURCE_TYPE_KEYBOX:
+ rc = keybox_search (hd->active[hd->current].u.kr, desc, ndesc);
+ break;
+ }
+ if (rc == -1) /* EOF -> switch to next resource */
+ hd->current++;
+ else if (!rc)
+ hd->found = hd->current;
+ }
+
+ return rc;
+}
+
+
+int
+keydb_search_first (KEYDB_HANDLE hd)
+{
+ KEYDB_SEARCH_DESC desc;
+
+ memset (&desc, 0, sizeof desc);
+ desc.mode = KEYDB_SEARCH_MODE_FIRST;
+ return keydb_search (hd, &desc, 1);
+}
+
+int
+keydb_search_next (KEYDB_HANDLE hd)
+{
+ KEYDB_SEARCH_DESC desc;
+
+ memset (&desc, 0, sizeof desc);
+ desc.mode = KEYDB_SEARCH_MODE_NEXT;
+ return keydb_search (hd, &desc, 1);
+}
+
+int
+keydb_search_kid (KEYDB_HANDLE hd, u32 *kid)
+{
+ KEYDB_SEARCH_DESC desc;
+
+ memset (&desc, 0, sizeof desc);
+ desc.mode = KEYDB_SEARCH_MODE_LONG_KID;
+/* desc.u.kid[0] = kid[0]; */
+/* desc.u.kid[1] = kid[1]; */
+ return keydb_search (hd, &desc, 1);
+}
+
+int
+keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr)
+{
+ KEYDB_SEARCH_DESC desc;
+
+ memset (&desc, 0, sizeof desc);
+ desc.mode = KEYDB_SEARCH_MODE_FPR;
+ memcpy (desc.u.fpr, fpr, 20);
+ return keydb_search (hd, &desc, 1);
+}
+
+int
+keydb_search_issuer (KEYDB_HANDLE hd, const char *issuer)
+{
+ KEYDB_SEARCH_DESC desc;
+ int rc;
+
+ memset (&desc, 0, sizeof desc);
+ desc.mode = KEYDB_SEARCH_MODE_ISSUER;
+ desc.u.name = issuer;
+ rc = keydb_search (hd, &desc, 1);
+ return rc;
+}
+
+int
+keydb_search_issuer_sn (KEYDB_HANDLE hd,
+ const char *issuer, KsbaConstSexp serial)
+{
+ KEYDB_SEARCH_DESC desc;
+ int rc;
+ const unsigned char *s;
+
+ memset (&desc, 0, sizeof desc);
+ desc.mode = KEYDB_SEARCH_MODE_ISSUER_SN;
+ s = serial;
+ if (*s !='(')
+ return gpg_error (GPG_ERR_INV_VALUE);
+ s++;
+ for (desc.snlen = 0; digitp (s); s++)
+ desc.snlen = 10*desc.snlen + atoi_1 (s);
+ if (*s !=':')
+ return gpg_error (GPG_ERR_INV_VALUE);
+ desc.sn = s+1;
+ desc.u.name = issuer;
+ rc = keydb_search (hd, &desc, 1);
+ return rc;
+}
+
+int
+keydb_search_subject (KEYDB_HANDLE hd, const char *name)
+{
+ KEYDB_SEARCH_DESC desc;
+ int rc;
+
+ memset (&desc, 0, sizeof desc);
+ desc.mode = KEYDB_SEARCH_MODE_SUBJECT;
+ desc.u.name = name;
+ rc = keydb_search (hd, &desc, 1);
+ return rc;
+}
+
+
+static int
+hextobyte (const unsigned char *s)
+{
+ int c;
+
+ if( *s >= '0' && *s <= '9' )
+ c = 16 * (*s - '0');
+ else if ( *s >= 'A' && *s <= 'F' )
+ c = 16 * (10 + *s - 'A');
+ else if ( *s >= 'a' && *s <= 'f' )
+ c = 16 * (10 + *s - 'a');
+ else
+ return -1;
+ s++;
+ if ( *s >= '0' && *s <= '9' )
+ c += *s - '0';
+ else if ( *s >= 'A' && *s <= 'F' )
+ c += 10 + *s - 'A';
+ else if ( *s >= 'a' && *s <= 'f' )
+ c += 10 + *s - 'a';
+ else
+ return -1;
+ return c;
+}
+
+
+static int
+classify_user_id (const char *name,
+ KEYDB_SEARCH_DESC *desc,
+ int *force_exact )
+{
+ const char *s;
+ int hexprefix = 0;
+ int hexlength;
+ int mode = 0;
+
+ /* clear the structure so that the mode field is set to zero unless
+ * we set it to the correct value right at the end of this function */
+ memset (desc, 0, sizeof *desc);
+ *force_exact = 0;
+ /* skip leading spaces. Fixme: what about trailing white space? */
+ for(s = name; *s && spacep (s); s++ )
+ ;
+
+ switch (*s)
+ {
+ case 0: /* empty string is an error */
+ return 0;
+
+ case '.': /* an email address, compare from end */
+ mode = KEYDB_SEARCH_MODE_MAILEND;
+ s++;
+ desc->u.name = s;
+ break;
+
+ case '<': /* an email address */
+ mode = KEYDB_SEARCH_MODE_MAIL;
+ s++;
+ desc->u.name = s;
+ break;
+
+ case '@': /* part of an email address */
+ mode = KEYDB_SEARCH_MODE_MAILSUB;
+ s++;
+ desc->u.name = s;
+ break;
+
+ case '=': /* exact compare */
+ mode = KEYDB_SEARCH_MODE_EXACT;
+ s++;
+ desc->u.name = s;
+ break;
+
+ case '*': /* case insensitive substring search */
+ mode = KEYDB_SEARCH_MODE_SUBSTR;
+ s++;
+ desc->u.name = s;
+ break;
+
+ case '+': /* compare individual words */
+ mode = KEYDB_SEARCH_MODE_WORDS;
+ s++;
+ desc->u.name = s;
+ break;
+
+ case '/': /* subject's DN */
+ s++;
+ if (!*s || spacep (s))
+ return 0; /* no DN or prefixed with a space */
+ desc->u.name = s;
+ mode = KEYDB_SEARCH_MODE_SUBJECT;
+ break;
+
+ case '#':
+ {
+ const char *si;
+
+ s++;
+ if ( *s == '/')
+ { /* "#/" indicates an issuer's DN */
+ s++;
+ if (!*s || spacep (s))
+ return 0; /* no DN or prefixed with a space */
+ desc->u.name = s;
+ mode = KEYDB_SEARCH_MODE_ISSUER;
+ }
+ else
+ { /* serialnumber + optional issuer ID */
+ for (si=s; *si && *si != '/'; si++)
+ {
+ if (!strchr("01234567890abcdefABCDEF", *si))
+ return 0; /* invalid digit in serial number*/
+ }
+ desc->sn = s;
+ desc->snlen = -1;
+ if (!*si)
+ mode = KEYDB_SEARCH_MODE_SN;
+ else
+ {
+ s = si+1;
+ if (!*s || spacep (s))
+ return 0; /* no DN or prefixed with a space */
+ desc->u.name = s;
+ mode = KEYDB_SEARCH_MODE_ISSUER_SN;
+ }
+ }
+ }
+ break;
+
+ case ':': /*Unified fingerprint */
+ {
+ const char *se, *si;
+ int i;
+
+ se = strchr (++s,':');
+ if (!se)
+ return 0;
+ for (i=0,si=s; si < se; si++, i++ )
+ {
+ if (!strchr("01234567890abcdefABCDEF", *si))
+ return 0; /* invalid digit */
+ }
+ if (i != 32 && i != 40)
+ return 0; /* invalid length of fpr*/
+ for (i=0,si=s; si < se; i++, si +=2)
+ desc->u.fpr[i] = hextobyte(si);
+ for (; i < 20; i++)
+ desc->u.fpr[i]= 0;
+ s = se + 1;
+ mode = KEYDB_SEARCH_MODE_FPR;
+ }
+ break;
+
+ default:
+ if (s[0] == '0' && s[1] == 'x')
+ {
+ hexprefix = 1;
+ s += 2;
+ }
+
+ hexlength = strspn(s, "0123456789abcdefABCDEF");
+ if (hexlength >= 8 && s[hexlength] =='!')
+ {
+ *force_exact = 1;
+ hexlength++; /* just for the following check */
+ }
+
+ /* check if a hexadecimal number is terminated by EOS or blank */
+ if (hexlength && s[hexlength] && !spacep (s+hexlength))
+ {
+ if (hexprefix) /* a "0x" prefix without correct */
+ return 0; /* termination is an error */
+ /* The first chars looked like a hex number, but really is
+ not */
+ hexlength = 0;
+ }
+
+ if (*force_exact)
+ hexlength--; /* remove the bang */
+
+ if (hexlength == 8
+ || (!hexprefix && hexlength == 9 && *s == '0'))
+ { /* short keyid */
+ unsigned long kid;
+ if (hexlength == 9)
+ s++;
+ kid = strtoul( s, NULL, 16 );
+ desc->u.kid[4] = kid >> 24;
+ desc->u.kid[5] = kid >> 16;
+ desc->u.kid[6] = kid >> 8;
+ desc->u.kid[7] = kid;
+ mode = KEYDB_SEARCH_MODE_SHORT_KID;
+ }
+ else if (hexlength == 16
+ || (!hexprefix && hexlength == 17 && *s == '0'))
+ { /* complete keyid */
+ unsigned long kid0, kid1;
+ char buf[9];
+ if (hexlength == 17)
+ s++;
+ mem2str(buf, s, 9 );
+ kid0 = strtoul (buf, NULL, 16);
+ kid1 = strtoul (s+8, NULL, 16);
+ desc->u.kid[0] = kid0 >> 24;
+ desc->u.kid[1] = kid0 >> 16;
+ desc->u.kid[2] = kid0 >> 8;
+ desc->u.kid[3] = kid0;
+ desc->u.kid[4] = kid1 >> 24;
+ desc->u.kid[5] = kid1 >> 16;
+ desc->u.kid[6] = kid1 >> 8;
+ desc->u.kid[7] = kid1;
+ mode = KEYDB_SEARCH_MODE_LONG_KID;
+ }
+ else if (hexlength == 32
+ || (!hexprefix && hexlength == 33 && *s == '0'))
+ { /* md5 fingerprint */
+ int i;
+ if (hexlength == 33)
+ s++;
+ memset(desc->u.fpr+16, 0, 4);
+ for (i=0; i < 16; i++, s+=2)
+ {
+ int c = hextobyte(s);
+ if (c == -1)
+ return 0;
+ desc->u.fpr[i] = c;
+ }
+ mode = KEYDB_SEARCH_MODE_FPR16;
+ }
+ else if (hexlength == 40
+ || (!hexprefix && hexlength == 41 && *s == '0'))
+ { /* sha1/rmd160 fingerprint */
+ int i;
+ if (hexlength == 41)
+ s++;
+ for (i=0; i < 20; i++, s+=2)
+ {
+ int c = hextobyte(s);
+ if (c == -1)
+ return 0;
+ desc->u.fpr[i] = c;
+ }
+ mode = KEYDB_SEARCH_MODE_FPR20;
+ }
+ else if (!hexprefix)
+ {
+ /* The fingerprint in an X.509 listing is often delimited by
+ colons, so we try to single this case out. */
+ mode = 0;
+ hexlength = strspn (s, ":0123456789abcdefABCDEF");
+ if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength)))
+ {
+ int i;
+
+ for (i=0; i < 20; i++, s += 3)
+ {
+ int c = hextobyte(s);
+ if (c == -1 || (i < 19 && s[2] != ':'))
+ break;
+ desc->u.fpr[i] = c;
+ }
+ if (i == 20)
+ mode = KEYDB_SEARCH_MODE_FPR20;
+ }
+ if (!mode) /* default is substring search */
+ {
+ *force_exact = 0;
+ desc->u.name = s;
+ mode = KEYDB_SEARCH_MODE_SUBSTR;
+ }
+ }
+ else
+ { /* hex number with a prefix but a wrong length */
+ return 0;
+ }
+ }
+
+ desc->mode = mode;
+ return mode;
+}
+
+
+int
+keydb_classify_name (const char *name, KEYDB_SEARCH_DESC *desc)
+{
+ int dummy;
+ KEYDB_SEARCH_DESC dummy_desc;
+
+ if (!desc)
+ desc = &dummy_desc;
+
+ if (!classify_user_id (name, desc, &dummy))
+ return gpg_error (GPG_ERR_INV_NAME);
+ return 0;
+}
+
+
+/* Store the certificate in the key DB but make sure that it does not
+ already exists. We do this simply by comparing the fingerprint.
+ If EXISTED is not NULL it will be set to true if the certificate
+ was already in the DB. */
+int
+keydb_store_cert (KsbaCert cert, int ephemeral, int *existed)
+{
+ KEYDB_HANDLE kh;
+ int rc;
+ unsigned char fpr[20];
+
+ if (existed)
+ *existed = 0;
+
+ if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL))
+ {
+ log_error (_("failed to get the fingerprint\n"));
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+
+ kh = keydb_new (0);
+ if (!kh)
+ {
+ log_error (_("failed to allocate keyDB handle\n"));
+ return gpg_error (GPG_ERR_ENOMEM);;
+ }
+
+ if (ephemeral)
+ keydb_set_ephemeral (kh, 1);
+
+ rc = keydb_search_fpr (kh, fpr);
+ if (rc != -1)
+ {
+ keydb_release (kh);
+ if (!rc)
+ {
+ if (existed)
+ *existed = 1;
+ return 0; /* okay */
+ }
+ log_error (_("problem looking for existing certificate: %s\n"),
+ gpg_strerror (rc));
+ return rc;
+ }
+
+ rc = keydb_locate_writable (kh, 0);
+ if (rc)
+ {
+ log_error (_("error finding writable keyDB: %s\n"), gpg_strerror (rc));
+ keydb_release (kh);
+ return rc;
+ }
+
+ rc = keydb_insert_cert (kh, cert);
+ if (rc)
+ {
+ log_error (_("error storing certificate: %s\n"), gpg_strerror (rc));
+ keydb_release (kh);
+ return rc;
+ }
+ keydb_release (kh);
+ return 0;
+}
+
+
+
diff --git a/sm/keylist.c b/sm/keylist.c
new file mode 100644
index 000000000..634bda292
--- /dev/null
+++ b/sm/keylist.c
@@ -0,0 +1,617 @@
+/* keylist.c
+ * Copyright (C) 1998, 1999, 2000, 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+
+#include "gpgsm.h"
+
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "keydb.h"
+#include "i18n.h"
+
+struct list_external_parm_s {
+ FILE *fp;
+ int print_header;
+ int with_colons;
+ int with_chain;
+};
+
+
+
+static void
+print_key_data (KsbaCert cert, FILE *fp)
+{
+#if 0
+ int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0;
+ int i;
+
+ for(i=0; i < n; i++ )
+ {
+ fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) );
+ mpi_print(stdout, pk->pkey[i], 1 );
+ putchar(':');
+ putchar('\n');
+ }
+#endif
+}
+
+static void
+print_capabilities (KsbaCert cert, FILE *fp)
+{
+ KsbaError err;
+ unsigned int use;
+
+ err = ksba_cert_get_key_usage (cert, &use);
+ if (err == KSBA_No_Data)
+ {
+ putc ('e', fp);
+ putc ('s', fp);
+ putc ('c', fp);
+ putc ('E', fp);
+ putc ('S', fp);
+ putc ('C', fp);
+ return;
+ }
+ if (err)
+ {
+ log_error (_("error getting key usage information: %s\n"),
+ ksba_strerror (err));
+ return;
+ }
+
+ if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
+ putc ('e', fp);
+ if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
+ putc ('s', fp);
+ if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN))
+ putc ('c', fp);
+ if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
+ putc ('E', fp);
+ if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
+ putc ('S', fp);
+ if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN))
+ putc ('C', fp);
+}
+
+
+static void
+print_time (time_t t, FILE *fp)
+{
+ if (!t)
+ ;
+ else if ( t == (time_t)(-1) )
+ putc ('?', fp);
+ else
+ fprintf (fp, "%lu", (unsigned long)t);
+}
+
+
+/* return an allocated string with the email address extracted from a
+ DN */
+static char *
+email_kludge (const char *name)
+{
+ const unsigned char *p;
+ unsigned char *buf;
+ int n;
+
+ if (strncmp (name, "1.2.840.113549.1.9.1=#", 22))
+ return NULL;
+ /* 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 */
+ name += 22;
+ for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++)
+ ;
+ if (*p != '#' || !n)
+ return NULL;
+ buf = xtrymalloc (n+3);
+ if (!buf)
+ return NULL; /* oops, out of core */
+ *buf = '<';
+ for (n=1, p=name; *p != '#'; p +=2, n++)
+ buf[n] = xtoi_2 (p);
+ buf[n++] = '>';
+ buf[n] = 0;
+ return buf;
+}
+
+
+
+
+/* List one certificate in colon mode */
+static void
+list_cert_colon (KsbaCert cert, FILE *fp, int have_secret)
+{
+ int idx, trustletter = 0;
+ char *p;
+ KsbaSexp sexp;
+ char *fpr;
+
+ fputs (have_secret? "crs:":"crt:", fp);
+ trustletter = 0;
+#if 0
+ if (is_not_valid (cert))
+ putc ('i', fp);
+ else if ( is_revoked (cert) )
+ putc ('r', fp);
+ else if ( has_expired (cert))
+ putcr ('e', fp);
+ else
+#endif
+ {
+ trustletter = '?'; /*get_validity_info ( pk, NULL );*/
+ putc (trustletter, fp);
+ }
+
+ fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
+ fprintf (fp, ":%u:%d:%s:",
+ /*keylen_of_cert (cert)*/1024,
+ /* pubkey_algo_of_cert (cert)*/1,
+ fpr+24);
+
+ /* we assume --fixed-list-mode for gpgsm */
+ print_time ( ksba_cert_get_validity (cert, 0), fp);
+ putc (':', fp);
+ print_time ( ksba_cert_get_validity (cert, 1), fp);
+ 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++)
+ fprintf (fp,"%02X", *s);
+ }
+ xfree (sexp);
+ }
+ putc (':', fp);
+ /* field 9, ownertrust - not used here */
+ putc (':', fp);
+ /* field 10, old user ID - we use it here for the issuer DN */
+ if ((p = ksba_cert_get_issuer (cert,0)))
+ {
+ print_sanitized_string (fp, p, ':');
+ xfree (p);
+ }
+ putc (':', fp);
+ /* field 11, signature class - not used */
+ putc (':', fp);
+ /* field 12, capabilities: */
+ print_capabilities (cert, fp);
+ putc (':', fp);
+ putc ('\n', fp);
+
+ /* FPR record */
+ fprintf (fp, "fpr:::::::::%s:::", fpr);
+ xfree (fpr); fpr = NULL;
+ /* print chaining ID (field 13)*/
+ {
+ KsbaCert next;
+
+ if (!gpgsm_walk_cert_chain (cert, &next))
+ {
+ p = gpgsm_get_fingerprint_hexstring (next, GCRY_MD_SHA1);
+ fputs (p, fp);
+ xfree (p);
+ ksba_cert_release (next);
+ }
+ }
+ putc (':', fp);
+ putc ('\n', fp);
+
+
+ if (opt.with_key_data)
+ {
+ if ( (p = gpgsm_get_keygrip_hexstring (cert)))
+ {
+ fprintf (fp, "grp:::::::::%s:\n", p);
+ xfree (p);
+ }
+ print_key_data (cert, fp);
+ }
+
+ for (idx=0; (p = ksba_cert_get_subject (cert,idx)); idx++)
+ {
+ fprintf (fp, "uid:%c::::::::", trustletter);
+ print_sanitized_string (fp, p, ':');
+ putc (':', fp);
+ putc (':', fp);
+ 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 */
+ char *pp = email_kludge (p);
+ if (pp)
+ {
+ fprintf (fp, "uid:%c::::::::", trustletter);
+ print_sanitized_string (fp, pp, ':');
+ putc (':', fp);
+ putc (':', fp);
+ putc ('\n', fp);
+ xfree (pp);
+ }
+ }
+ xfree (p);
+ }
+}
+
+
+/* List one certificate in standard mode */
+static void
+list_cert_std (KsbaCert cert, FILE *fp, int have_secret)
+{
+ KsbaError kerr;
+ KsbaSexp sexp;
+ char *dn;
+ time_t t;
+ int idx;
+ int is_ca, chainlen;
+ unsigned int kusage;
+ char *string, *p;
+
+ sexp = ksba_cert_get_serial (cert);
+ fputs ("Serial number: ", fp);
+ gpgsm_print_serial (fp, sexp);
+ ksba_free (sexp);
+ putc ('\n', fp);
+
+ dn = ksba_cert_get_issuer (cert, 0);
+ fputs (" Issuer: ", fp);
+ gpgsm_print_name (fp, dn);
+ ksba_free (dn);
+ putc ('\n', fp);
+ for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++)
+ {
+ fputs (" aka: ", fp);
+ gpgsm_print_name (fp, dn);
+ ksba_free (dn);
+ putc ('\n', fp);
+ }
+
+ dn = ksba_cert_get_subject (cert, 0);
+ fputs (" Subject: ", fp);
+ gpgsm_print_name (fp, dn);
+ ksba_free (dn);
+ putc ('\n', fp);
+ for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++)
+ {
+ fputs (" aka: ", fp);
+ gpgsm_print_name (fp, dn);
+ ksba_free (dn);
+ putc ('\n', fp);
+ }
+
+ t = ksba_cert_get_validity (cert, 0);
+ fputs (" validity: ", fp);
+ gpgsm_print_time (fp, t);
+ fputs (" through ", fp);
+ t = ksba_cert_get_validity (cert, 1);
+ gpgsm_print_time (fp, t);
+ putc ('\n', fp);
+
+ kerr = ksba_cert_get_key_usage (cert, &kusage);
+ if (kerr != KSBA_No_Data)
+ {
+ fputs (" key usage:", fp);
+ if (kerr)
+ fprintf (fp, " [error: %s]", ksba_strerror (kerr));
+ else
+ {
+ if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE))
+ fputs (" digitalSignature", fp);
+ if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION))
+ fputs (" nonRepudiation", fp);
+ if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT))
+ fputs (" keyEncipherment", fp);
+ if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT))
+ fputs (" dataEncipherment", fp);
+ if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT))
+ fputs (" keyAgreement", fp);
+ if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN))
+ fputs (" certSign", fp);
+ if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN))
+ fputs (" crlSign", fp);
+ if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY))
+ fputs (" encipherOnly", fp);
+ if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY))
+ fputs (" decipherOnly", fp);
+ }
+ putc ('\n', fp);
+ }
+
+ kerr = ksba_cert_get_cert_policies (cert, &string);
+ if (kerr != KSBA_No_Data)
+ {
+ fputs (" policies: ", fp);
+ if (kerr)
+ fprintf (fp, "[error: %s]", ksba_strerror (kerr));
+ else
+ {
+ for (p=string; *p; p++)
+ {
+ if (*p == '\n')
+ *p = ',';
+ }
+ print_sanitized_string (fp, string, 0);
+ xfree (string);
+ }
+ putc ('\n', fp);
+ }
+
+ kerr = ksba_cert_is_ca (cert, &is_ca, &chainlen);
+ if (kerr || is_ca)
+ {
+ fputs (" chain length: ", fp);
+ if (kerr)
+ fprintf (fp, "[error: %s]", ksba_strerror (kerr));
+ else if (chainlen == -1)
+ fputs ("unlimited", fp);
+ else
+ fprintf (fp, "%d", chainlen);
+ putc ('\n', fp);
+ }
+
+
+ dn = gpgsm_get_fingerprint_string (cert, 0);
+ fprintf (fp, " fingerprint: %s\n", dn?dn:"error");
+ xfree (dn);
+}
+
+/* Same as standard mode mode list all certifying certts too */
+static void
+list_cert_chain (KsbaCert cert, FILE *fp)
+{
+ KsbaCert next = NULL;
+
+ list_cert_std (cert, fp, 0);
+ ksba_cert_ref (cert);
+ while (!gpgsm_walk_cert_chain (cert, &next))
+ {
+ ksba_cert_release (cert);
+ fputs ("Certified by\n", fp);
+ list_cert_std (next, fp, 0);
+ cert = next;
+ }
+ ksba_cert_release (cert);
+ putc ('\n', fp);
+}
+
+
+
+/* List all internal keys or just the key given as NAMES.
+ */
+static void
+list_internal_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode)
+{
+ KEYDB_HANDLE hd;
+ KEYDB_SEARCH_DESC *desc = NULL;
+ STRLIST sl;
+ int ndesc;
+ KsbaCert cert = NULL;
+ int rc=0;
+ const char *lastresname, *resname;
+ int have_secret;
+
+ hd = keydb_new (0);
+ if (!hd)
+ {
+ log_error ("keydb_new failed\n");
+ goto leave;
+ }
+
+ if (!names)
+ ndesc = 1;
+ else
+ {
+ for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
+ ;
+ }
+
+ desc = xtrycalloc (ndesc, sizeof *desc);
+ if (!ndesc)
+ {
+ log_error ("out of core\n");
+ goto leave;
+ }
+
+ if (!names)
+ desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
+ else
+ {
+ for (ndesc=0, sl=names; sl; sl = sl->next)
+ {
+ rc = keydb_classify_name (sl->d, desc+ndesc);
+ if (rc)
+ {
+ log_error ("key `%s' not found: %s\n",
+ sl->d, gpg_strerror (rc));
+ rc = 0;
+ }
+ else
+ ndesc++;
+ }
+
+ }
+
+ /* 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 */
+
+ lastresname = NULL;
+ while (!(rc = keydb_search (hd, desc, ndesc)))
+ {
+ 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;
+ }
+
+ resname = keydb_get_resource_name (hd);
+
+ if (lastresname != resname )
+ {
+ int i;
+
+ if (ctrl->no_server)
+ {
+ fprintf (fp, "%s\n", resname );
+ for (i=strlen(resname); i; i-- )
+ putchar('-');
+ putc ('\n', fp);
+ lastresname = resname;
+ }
+ }
+
+ have_secret = 0;
+ if (mode)
+ {
+ char *p = gpgsm_get_keygrip_hexstring (cert);
+ if (p)
+ {
+ if (!gpgsm_agent_havekey (p))
+ have_secret = 1;
+ xfree (p);
+ }
+ }
+
+ if (!mode
+ || ((mode & 1) && !have_secret)
+ || ((mode & 2) && have_secret) )
+ {
+ if (ctrl->with_colons)
+ list_cert_colon (cert, fp, have_secret);
+ else if (ctrl->with_chain)
+ list_cert_chain (cert, fp);
+ else
+ {
+ list_cert_std (cert, fp, have_secret);
+ putc ('\n', fp);
+ }
+ }
+ ksba_cert_release (cert);
+ cert = NULL;
+ }
+ if (rc && rc != -1)
+ log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
+
+ leave:
+ ksba_cert_release (cert);
+ xfree (desc);
+ keydb_release (hd);
+}
+
+
+
+static void
+list_external_cb (void *cb_value, KsbaCert cert)
+{
+ struct list_external_parm_s *parm = cb_value;
+
+ if (keydb_store_cert (cert, 1, NULL))
+ log_error ("error storing certificate as ephemeral\n");
+
+ if (parm->print_header)
+ {
+ const char *resname = "[external keys]";
+ int i;
+
+ fprintf (parm->fp, "%s\n", resname );
+ for (i=strlen(resname); i; i-- )
+ putchar('-');
+ putc ('\n', parm->fp);
+ parm->print_header = 0;
+ }
+
+ if (parm->with_colons)
+ list_cert_colon (cert, parm->fp, 0);
+ else if (parm->with_chain)
+ list_cert_chain (cert, parm->fp);
+ else
+ {
+ list_cert_std (cert, parm->fp, 0);
+ 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 void
+list_external_keys (CTRL ctrl, STRLIST names, FILE *fp)
+{
+ int rc;
+ struct list_external_parm_s parm;
+
+ parm.fp = fp;
+ parm.print_header = ctrl->no_server;
+ parm.with_colons = ctrl->with_colons;
+ parm.with_chain = ctrl->with_chain;
+
+ rc = gpgsm_dirmngr_lookup (ctrl, names, list_external_cb, &parm);
+ if (rc)
+ log_error ("listing external keys failed: %s\n", gpg_strerror (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
+ */
+void
+gpgsm_list_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode)
+{
+ if ((mode & (1<<6)))
+ list_internal_keys (ctrl, names, fp, (mode & 3));
+ if ((mode & (1<<7)))
+ list_external_keys (ctrl, names, fp);
+}
diff --git a/sm/server.c b/sm/server.c
new file mode 100644
index 000000000..dda150964
--- /dev/null
+++ b/sm/server.c
@@ -0,0 +1,1070 @@
+/* server.c - Server mode and main entry point
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#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 <assuan.h>
+
+#include "gpgsm.h"
+
+#define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## 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 assuan_ctx;
+ int message_fd;
+ int list_internal;
+ int list_external;
+ CERTLIST recplist;
+ CERTLIST signerlist;
+};
+
+
+
+/* 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 unsigned 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;
+}
+
+
+
+
+/* Check whether the option NAME appears in LINE */
+static int
+has_option (const char *line, const char *name)
+{
+ const char *s;
+ int n = strlen (name);
+
+ s = strstr (line, name);
+ return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n)));
+}
+
+
+static void
+close_message_fd (CTRL ctrl)
+{
+ if (ctrl->server_local->message_fd != -1)
+ {
+ close (ctrl->server_local->message_fd);
+ ctrl->server_local->message_fd = -1;
+ }
+}
+
+
+static int
+option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+
+ if (!strcmp (key, "include-certs"))
+ {
+ int i = *value? atoi (value) : -1;
+ if (ctrl->include_certs < -2)
+ return ASSUAN_Parameter_Error;
+ ctrl->include_certs = i;
+ }
+ else if (!strcmp (key, "display"))
+ {
+ if (opt.display)
+ free (opt.display);
+ opt.display = strdup (value);
+ if (!opt.display)
+ return ASSUAN_Out_Of_Core;
+ }
+ else if (!strcmp (key, "ttyname"))
+ {
+ if (opt.ttyname)
+ free (opt.ttyname);
+ opt.ttyname = strdup (value);
+ if (!opt.ttyname)
+ return ASSUAN_Out_Of_Core;
+ }
+ else if (!strcmp (key, "ttytype"))
+ {
+ if (opt.ttytype)
+ free (opt.ttytype);
+ opt.ttytype = strdup (value);
+ if (!opt.ttytype)
+ return ASSUAN_Out_Of_Core;
+ }
+ else if (!strcmp (key, "lc-ctype"))
+ {
+ if (opt.lc_ctype)
+ free (opt.lc_ctype);
+ opt.lc_ctype = strdup (value);
+ if (!opt.lc_ctype)
+ return ASSUAN_Out_Of_Core;
+ }
+ else if (!strcmp (key, "lc-messages"))
+ {
+ if (opt.lc_messages)
+ free (opt.lc_messages);
+ opt.lc_messages = strdup (value);
+ if (!opt.lc_messages)
+ return ASSUAN_Out_Of_Core;
+ }
+ 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
+ return ASSUAN_Parameter_Error;
+ }
+ else
+ return ASSUAN_Invalid_Option;
+
+ return 0;
+}
+
+
+
+
+static void
+reset_notify (ASSUAN_CONTEXT ctx)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+
+ 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);
+}
+
+
+static void
+input_notify (ASSUAN_CONTEXT ctx, const char *line)
+{
+ CTRL 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;
+}
+
+static void
+output_notify (ASSUAN_CONTEXT ctx, const char *line)
+{
+ CTRL 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 */
+}
+
+
+
+/* RECIPIENT <userID>
+
+ Set the recipient for the encryption. <userID> should be the
+ internal representation of the key; the server may accept any other
+ way of specification [we will support this]. If this is a valid and
+ trusted recipient the server does respond with OK, otherwise the
+ return is an ERR with the reason why the recipient can't be used,
+ the encryption will then not be done for this recipient. IF the
+ policy is not to encrypt at all if not all recipients are valid, the
+ client has to take care of this. All RECIPIENT commands are
+ cumulative until a RESET or an successful ENCRYPT command. */
+static int
+cmd_recipient (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+
+ rc = gpgsm_add_to_certlist (ctrl, line, 0, &ctrl->server_local->recplist);
+ if (rc)
+ {
+ gpg_err_code_t r = gpg_err_code (rc);
+ gpgsm_status2 (ctrl, STATUS_INV_RECP,
+ r == -1? "1":
+ r == GPG_ERR_NO_PUBKEY? "1":
+ r == GPG_ERR_AMBIGUOUS_NAME? "2":
+ r == GPG_ERR_WRONG_KEY_USAGE? "3":
+ r == GPG_ERR_CERT_REVOKED? "4":
+ r == GPG_ERR_CERT_EXPIRED? "5":
+ r == GPG_ERR_NO_CRL_KNOWN? "6":
+ r == GPG_ERR_CRL_TOO_OLD? "7":
+ r == GPG_ERR_NO_POLICY_MATCH? "8":
+ "0",
+ line, NULL);
+ }
+
+ return map_to_assuan_status (rc);
+}
+
+/* SIGNER <userID>
+
+ Set the signer's keys for the signature creation. <userID> should
+ be the internal representation of the key; the server may accept any
+ other way of specification [we will support this]. If this is a
+ valid and usable signing key the server does respond with OK,
+ otherwise it returns an ERR with the reason why the key can't be
+ used, the signing will then not be done for this key. If the policy
+ is not to sign at all if not all signer keys are valid, the client
+ has to take care of this. All SIGNER commands are cumulative until
+ a RESET but they are *not* reset by an SIGN command becuase it can
+ be expected that set of signers are used for more than one sign
+ operation.
+
+ Note that this command returns an INV_RECP status which is a bit
+ strange, but they are very similar. */
+static int
+cmd_signer (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+
+ rc = gpgsm_add_to_certlist (ctrl, line, 1, &ctrl->server_local->signerlist);
+ if (rc)
+ {
+ gpg_err_code_t r = gpg_err_code (rc);
+ gpgsm_status2 (ctrl, STATUS_INV_RECP,
+ r == -1? "1":
+ r == GPG_ERR_NO_PUBKEY? "1":
+ r == GPG_ERR_AMBIGUOUS_NAME? "2":
+ r == GPG_ERR_WRONG_KEY_USAGE? "3":
+ r == GPG_ERR_CERT_REVOKED? "4":
+ r == GPG_ERR_CERT_EXPIRED? "5":
+ r == GPG_ERR_NO_CRL_KNOWN? "6":
+ r == GPG_ERR_CRL_TOO_OLD? "7":
+ r == GPG_ERR_NO_POLICY_MATCH? "8":
+ r == GPG_ERR_NO_SECKEY? "9":
+ "0",
+ line, NULL);
+ }
+ return map_to_assuan_status (rc);
+}
+
+
+/* ENCRYPT
+
+ Do the actual encryption process. Takes the plaintext from the INPUT
+ command, writes to the ciphertext to the file descriptor set with
+ the OUTPUT command, take the recipients form all the recipients set
+ so far. If this command fails the clients should try to delete all
+ output currently done or otherwise mark it as invalid. GPGSM does
+ ensure that there won't be any security problem with leftover data
+ on the output in this case.
+
+ This command should in general not fail, as all necessary checks
+ have been done while setting the recipients. The input and output
+ pipes are closed. */
+static int
+cmd_encrypt (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int inp_fd, out_fd;
+ FILE *out_fp;
+ int rc;
+
+ inp_fd = assuan_get_input_fd (ctx);
+ if (inp_fd == -1)
+ return set_error (No_Input, NULL);
+ out_fd = assuan_get_output_fd (ctx);
+ if (out_fd == -1)
+ return set_error (No_Output, NULL);
+
+ out_fp = fdopen ( dup(out_fd), "w");
+ if (!out_fp)
+ return set_error (General_Error, "fdopen() failed");
+ rc = gpgsm_encrypt (assuan_get_pointer (ctx),
+ ctrl->server_local->recplist,
+ inp_fd, out_fp);
+ fclose (out_fp);
+
+ gpgsm_release_certlist (ctrl->server_local->recplist);
+ ctrl->server_local->recplist = NULL;
+ /* close and reset the fd */
+ close_message_fd (ctrl);
+ assuan_close_input_fd (ctx);
+ assuan_close_output_fd (ctx);
+ return map_to_assuan_status (rc);
+}
+
+/* DECRYPT
+
+ This performs the decrypt operation after doing some check on the
+ internal state. (e.g. that only needed data has been set). Because
+ it utilizes the GPG-Agent for the session key decryption, there is
+ no need to ask the client for a protecting passphrase - GpgAgent
+ does take care of this by requesting this from the user. */
+static int
+cmd_decrypt (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int inp_fd, out_fd;
+ FILE *out_fp;
+ int rc;
+
+ inp_fd = assuan_get_input_fd (ctx);
+ if (inp_fd == -1)
+ return set_error (No_Input, NULL);
+ out_fd = assuan_get_output_fd (ctx);
+ if (out_fd == -1)
+ return set_error (No_Output, NULL);
+
+ out_fp = fdopen ( dup(out_fd), "w");
+ if (!out_fp)
+ return set_error (General_Error, "fdopen() failed");
+ rc = gpgsm_decrypt (ctrl, inp_fd, out_fp);
+ fclose (out_fp);
+
+ /* close and reset the fd */
+ close_message_fd (ctrl);
+ assuan_close_input_fd (ctx);
+ assuan_close_output_fd (ctx);
+
+ return map_to_assuan_status (rc);
+}
+
+
+/* VERIFY
+
+ This does a verify operation on the message send to the input-FD.
+ The result is written out using status lines. If an output FD was
+ given, the signed text will be written to that.
+
+ If the signature is a detached one, the server will inquire about
+ the signed material and the client must provide it.
+ */
+static int
+cmd_verify (ASSUAN_CONTEXT ctx, char *line)
+{
+ int rc;
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int fd = assuan_get_input_fd (ctx);
+ int out_fd = assuan_get_output_fd (ctx);
+ FILE *out_fp = NULL;
+
+ if (fd == -1)
+ return set_error (No_Input, NULL);
+
+ if (out_fd != -1)
+ {
+ out_fp = fdopen ( dup(out_fd), "w");
+ if (!out_fp)
+ return set_error (General_Error, "fdopen() failed");
+ }
+
+ rc = gpgsm_verify (assuan_get_pointer (ctx), fd,
+ ctrl->server_local->message_fd, out_fp);
+ if (out_fp)
+ fclose (out_fp);
+
+ /* close and reset the fd */
+ close_message_fd (ctrl);
+ assuan_close_input_fd (ctx);
+ assuan_close_output_fd (ctx);
+
+ return map_to_assuan_status (rc);
+}
+
+
+/* SIGN [--detached]
+
+ Sign the data set with the INPUT command and write it to the sink
+ set by OUTPUT. With "--detached" specified, a detached signature is
+ created (surprise). */
+static int
+cmd_sign (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int inp_fd, out_fd;
+ FILE *out_fp;
+ int detached;
+ int rc;
+
+ inp_fd = assuan_get_input_fd (ctx);
+ if (inp_fd == -1)
+ return set_error (No_Input, NULL);
+ out_fd = assuan_get_output_fd (ctx);
+ if (out_fd == -1)
+ return set_error (No_Output, NULL);
+
+ detached = has_option (line, "--detached");
+
+ out_fp = fdopen ( dup(out_fd), "w");
+ if (!out_fp)
+ return set_error (General_Error, "fdopen() failed");
+
+ rc = gpgsm_sign (assuan_get_pointer (ctx), ctrl->server_local->signerlist,
+ inp_fd, detached, out_fp);
+ fclose (out_fp);
+
+ /* close and reset the fd */
+ close_message_fd (ctrl);
+ assuan_close_input_fd (ctx);
+ assuan_close_output_fd (ctx);
+
+ return map_to_assuan_status (rc);
+}
+
+
+/* IMPORT
+
+ Import the certificates read form the input-fd, return status
+ message for each imported one. The import checks the validity of
+ the certificate but not of the entire chain. It is possible to
+ import expired certificates. */
+static int
+cmd_import (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int rc;
+ int fd = assuan_get_input_fd (ctx);
+
+ if (fd == -1)
+ return set_error (No_Input, NULL);
+
+ rc = gpgsm_import (assuan_get_pointer (ctx), fd);
+
+ /* close and reset the fd */
+ close_message_fd (ctrl);
+ assuan_close_input_fd (ctx);
+ assuan_close_output_fd (ctx);
+
+ return map_to_assuan_status (rc);
+}
+
+
+static int
+cmd_export (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int fd = assuan_get_output_fd (ctx);
+ FILE *out_fp;
+ char *p;
+ STRLIST list, sl;
+
+ if (fd == -1)
+ return set_error (No_Output, NULL);
+
+ /* 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 ASSUAN_Out_Of_Core;
+ }
+ sl->flags = 0;
+ strcpy_escaped_plus (sl->d, line);
+ sl->next = list;
+ list = sl;
+ }
+ }
+
+ out_fp = fdopen ( dup(fd), "w");
+ if (!out_fp)
+ {
+ free_strlist (list);
+ return set_error (General_Error, "fdopen() failed");
+ }
+
+ gpgsm_export (ctrl, list, out_fp);
+ fclose (out_fp);
+ free_strlist (list);
+ /* close and reset the fd */
+ close_message_fd (ctrl);
+ assuan_close_input_fd (ctx);
+ assuan_close_output_fd (ctx);
+ return 0;
+}
+
+
+static int
+cmd_delkeys (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ char *p;
+ STRLIST list, sl;
+ int rc;
+
+ /* 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 ASSUAN_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 map_to_assuan_status (rc);
+}
+
+
+
+/* MESSAGE FD=<n>
+
+ Set the file descriptor to read a message which is used with
+ detached signatures */
+static int
+cmd_message (ASSUAN_CONTEXT ctx, char *line)
+{
+ char *endp;
+ int fd;
+ CTRL ctrl = assuan_get_pointer (ctx);
+
+ if (strncmp (line, "FD=", 3))
+ return set_error (Syntax_Error, "FD=<n> expected");
+ line += 3;
+ if (!digitp (line))
+ return set_error (Syntax_Error, "number required");
+ fd = strtoul (line, &endp, 10);
+ if (*endp)
+ return set_error (Syntax_Error, "garbage found");
+ if (fd == -1)
+ return set_error (No_Input, NULL);
+
+ ctrl->server_local->message_fd = fd;
+ return 0;
+}
+
+
+static int
+do_listkeys (ASSUAN_CONTEXT ctx, char *line, int mode)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ FILE *fp = assuan_get_data_fp (ctx);
+ char *p;
+ STRLIST list, sl;
+ unsigned int listmode;
+
+ if (!fp)
+ return set_error (General_Error, "no data stream");
+
+ /* 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 ASSUAN_Out_Of_Core;
+ }
+ sl->flags = 0;
+ strcpy_escaped_plus (sl->d, line);
+ sl->next = list;
+ list = sl;
+ }
+ }
+
+ ctrl->with_colons = 1;
+ listmode = mode;
+ if (ctrl->server_local->list_internal)
+ listmode |= (1<<6);
+ if (ctrl->server_local->list_external)
+ listmode |= (1<<7);
+ gpgsm_list_keys (assuan_get_pointer (ctx), list, fp, listmode);
+ free_strlist (list);
+ return 0;
+}
+
+static int
+cmd_listkeys (ASSUAN_CONTEXT ctx, char *line)
+{
+ return do_listkeys (ctx, line, 3);
+}
+
+static int
+cmd_listsecretkeys (ASSUAN_CONTEXT ctx, char *line)
+{
+ return do_listkeys (ctx, line, 2);
+}
+
+
+/* GENKEY
+
+ Read the parameters in native format from the input fd and write a
+ certificate request to the output.
+ */
+static int
+cmd_genkey (ASSUAN_CONTEXT ctx, char *line)
+{
+ CTRL ctrl = assuan_get_pointer (ctx);
+ int inp_fd, out_fd;
+ FILE *out_fp;
+ int rc;
+
+ inp_fd = assuan_get_input_fd (ctx);
+ if (inp_fd == -1)
+ return set_error (No_Input, NULL);
+ out_fd = assuan_get_output_fd (ctx);
+ if (out_fd == -1)
+ return set_error (No_Output, NULL);
+
+ out_fp = fdopen ( dup(out_fd), "w");
+ if (!out_fp)
+ return set_error (General_Error, "fdopen() failed");
+ rc = gpgsm_genkey (ctrl, inp_fd, out_fp);
+ fclose (out_fp);
+
+ /* close and reset the fds */
+ assuan_close_input_fd (ctx);
+ assuan_close_output_fd (ctx);
+
+ return map_to_assuan_status (rc);
+}
+
+
+
+
+
+/* Tell the assuan library about our commands */
+static int
+register_commands (ASSUAN_CONTEXT ctx)
+{
+ static struct {
+ const char *name;
+ int (*handler)(ASSUAN_CONTEXT, char *line);
+ } table[] = {
+ { "RECIPIENT", cmd_recipient },
+ { "SIGNER", cmd_signer },
+ { "ENCRYPT", cmd_encrypt },
+ { "DECRYPT", cmd_decrypt },
+ { "VERIFY", cmd_verify },
+ { "SIGN", cmd_sign },
+ { "IMPORT", cmd_import },
+ { "EXPORT", cmd_export },
+ { "INPUT", NULL },
+ { "OUTPUT", NULL },
+ { "MESSAGE", cmd_message },
+ { "LISTKEYS", cmd_listkeys },
+ { "LISTSECRETKEYS",cmd_listsecretkeys },
+ { "GENKEY", cmd_genkey },
+ { "DELKEYS", cmd_delkeys },
+ { NULL }
+ };
+ int i, rc;
+
+ for (i=0; table[i].name; i++)
+ {
+ rc = assuan_register_command (ctx, table[i].name, table[i].handler);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+/* Startup the server */
+void
+gpgsm_server (void)
+{
+ int rc;
+ int filedes[2];
+ ASSUAN_CONTEXT ctx;
+ struct server_control_s ctrl;
+
+ memset (&ctrl, 0, sizeof ctrl);
+ gpgsm_init_default_ctrl (&ctrl);
+
+ /* For now we use a simple pipe based server so that we can work
+ from scripts. We will later add options to run as a daemon and
+ wait for requests on a Unix domain socket */
+ filedes[0] = 0;
+ filedes[1] = 1;
+ rc = assuan_init_pipe_server (&ctx, filedes);
+ if (rc)
+ {
+ log_error ("failed to initialize the server: %s\n",
+ assuan_strerror(rc));
+ gpgsm_exit (2);
+ }
+ rc = register_commands (ctx);
+ if (rc)
+ {
+ log_error ("failed to the register commands with Assuan: %s\n",
+ assuan_strerror(rc));
+ gpgsm_exit (2);
+ }
+ assuan_set_hello_line (ctx, "GNU Privacy Guard's S/M server ready");
+
+ 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;
+
+ if (DBG_ASSUAN)
+ assuan_set_log_stream (ctx, log_get_stream ());
+
+ for (;;)
+ {
+ rc = assuan_accept (ctx);
+ if (rc == -1)
+ {
+ break;
+ }
+ else if (rc)
+ {
+ log_info ("Assuan accept problem: %s\n", assuan_strerror (rc));
+ break;
+ }
+
+ rc = assuan_process (ctx);
+ if (rc)
+ {
+ log_info ("Assuan processing failed: %s\n", assuan_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;
+
+ assuan_deinit_server (ctx);
+}
+
+
+static const char *
+get_status_string ( int no )
+{
+ const char *s;
+
+ switch (no)
+ {
+ case STATUS_ENTER : s = "ENTER"; break;
+ case STATUS_LEAVE : s = "LEAVE"; break;
+ case STATUS_ABORT : s = "ABORT"; break;
+ case STATUS_GOODSIG: s = "GOODSIG"; break;
+ case STATUS_SIGEXPIRED: s = "SIGEXPIRED"; break;
+ case STATUS_KEYREVOKED: s = "KEYREVOKED"; break;
+ case STATUS_BADSIG : s = "BADSIG"; break;
+ case STATUS_ERRSIG : s = "ERRSIG"; break;
+ case STATUS_BADARMOR : s = "BADARMOR"; break;
+ case STATUS_RSA_OR_IDEA : s= "RSA_OR_IDEA"; break;
+ case STATUS_TRUST_UNDEFINED: s = "TRUST_UNDEFINED"; break;
+ case STATUS_TRUST_NEVER : s = "TRUST_NEVER"; break;
+ case STATUS_TRUST_MARGINAL : s = "TRUST_MARGINAL"; break;
+ case STATUS_TRUST_FULLY : s = "TRUST_FULLY"; break;
+ case STATUS_TRUST_ULTIMATE : s = "TRUST_ULTIMATE"; break;
+ case STATUS_GET_BOOL : s = "GET_BOOL"; break;
+ case STATUS_GET_LINE : s = "GET_LINE"; break;
+ case STATUS_GET_HIDDEN : s = "GET_HIDDEN"; break;
+ case STATUS_GOT_IT : s = "GOT_IT"; break;
+ case STATUS_SHM_INFO : s = "SHM_INFO"; break;
+ case STATUS_SHM_GET : s = "SHM_GET"; break;
+ case STATUS_SHM_GET_BOOL : s = "SHM_GET_BOOL"; break;
+ case STATUS_SHM_GET_HIDDEN : s = "SHM_GET_HIDDEN"; break;
+ case STATUS_NEED_PASSPHRASE: s = "NEED_PASSPHRASE"; break;
+ case STATUS_VALIDSIG : s = "VALIDSIG"; break;
+ case STATUS_SIG_ID : s = "SIG_ID"; break;
+ case STATUS_ENC_TO : s = "ENC_TO"; break;
+ case STATUS_NODATA : s = "NODATA"; break;
+ case STATUS_BAD_PASSPHRASE : s = "BAD_PASSPHRASE"; break;
+ case STATUS_NO_PUBKEY : s = "NO_PUBKEY"; break;
+ case STATUS_NO_SECKEY : s = "NO_SECKEY"; break;
+ case STATUS_NEED_PASSPHRASE_SYM: s = "NEED_PASSPHRASE_SYM"; break;
+ case STATUS_DECRYPTION_FAILED: s = "DECRYPTION_FAILED"; break;
+ case STATUS_DECRYPTION_OKAY: s = "DECRYPTION_OKAY"; break;
+ case STATUS_MISSING_PASSPHRASE: s = "MISSING_PASSPHRASE"; break;
+ case STATUS_GOOD_PASSPHRASE : s = "GOOD_PASSPHRASE"; break;
+ case STATUS_GOODMDC : s = "GOODMDC"; break;
+ case STATUS_BADMDC : s = "BADMDC"; break;
+ case STATUS_ERRMDC : s = "ERRMDC"; break;
+ case STATUS_IMPORTED : s = "IMPORTED"; break;
+ case STATUS_IMPORT_RES : s = "IMPORT_RES"; break;
+ case STATUS_FILE_START : s = "FILE_START"; break;
+ case STATUS_FILE_DONE : s = "FILE_DONE"; break;
+ case STATUS_FILE_ERROR : s = "FILE_ERROR"; break;
+ case STATUS_BEGIN_DECRYPTION:s = "BEGIN_DECRYPTION"; break;
+ case STATUS_END_DECRYPTION : s = "END_DECRYPTION"; break;
+ case STATUS_BEGIN_ENCRYPTION:s = "BEGIN_ENCRYPTION"; break;
+ case STATUS_END_ENCRYPTION : s = "END_ENCRYPTION"; break;
+ case STATUS_DELETE_PROBLEM : s = "DELETE_PROBLEM"; break;
+ case STATUS_PROGRESS : s = "PROGRESS"; break;
+ case STATUS_SIG_CREATED : s = "SIG_CREATED"; break;
+ case STATUS_SESSION_KEY : s = "SESSION_KEY"; break;
+ case STATUS_NOTATION_NAME : s = "NOTATION_NAME" ; break;
+ case STATUS_NOTATION_DATA : s = "NOTATION_DATA" ; break;
+ case STATUS_POLICY_URL : s = "POLICY_URL" ; break;
+ case STATUS_BEGIN_STREAM : s = "BEGIN_STREAM"; break;
+ case STATUS_END_STREAM : s = "END_STREAM"; break;
+ case STATUS_KEY_CREATED : s = "KEY_CREATED"; break;
+ case STATUS_UNEXPECTED : s = "UNEXPECTED"; break;
+ case STATUS_INV_RECP : s = "INV_RECP"; break;
+ case STATUS_NO_RECP : s = "NO_RECP"; break;
+ case STATUS_ALREADY_SIGNED : s = "ALREADY_SIGNED"; break;
+ case STATUS_EXPSIG : s = "EXPSIG"; break;
+ case STATUS_EXPKEYSIG : s = "EXPKEYSIG"; break;
+ case STATUS_TRUNCATED : s = "TRUNCATED"; break;
+ case STATUS_ERROR : s = "ERROR"; break;
+ case STATUS_IMPORT_PROBLEM : s = "IMPORT_PROBLEM"; break;
+ default: s = "?"; break;
+ }
+ return s;
+}
+
+
+void
+gpgsm_status2 (CTRL ctrl, int no, ...)
+{
+ va_list arg_ptr;
+ const char *text;
+
+ va_start (arg_ptr, no);
+
+ if (ctrl->no_server)
+ {
+ if (ctrl->status_fd == -1)
+ return; /* no status wanted */
+ if (!statusfp)
+ {
+ if (ctrl->status_fd == 1)
+ statusfp = stdout;
+ else if (ctrl->status_fd == 2)
+ statusfp = stderr;
+ else
+ statusfp = fdopen (ctrl->status_fd, "w");
+
+ if (!statusfp)
+ {
+ log_fatal ("can't open fd %d for status output: %s\n",
+ ctrl->status_fd, strerror(errno));
+ }
+ }
+
+ fputs ("[GNUPG:] ", statusfp);
+ fputs (get_status_string (no), statusfp);
+
+ while ( (text = va_arg (arg_ptr, const char*) ))
+ {
+ putc ( ' ', statusfp );
+ for (; *text; text++)
+ {
+ if (*text == '\n')
+ fputs ( "\\n", statusfp );
+ else if (*text == '\r')
+ fputs ( "\\r", statusfp );
+ else
+ putc ( *(const byte *)text, statusfp );
+ }
+ }
+ putc ('\n', statusfp);
+ fflush (statusfp);
+ }
+ else
+ {
+ ASSUAN_CONTEXT ctx = ctrl->server_local->assuan_ctx;
+ char buf[950], *p;
+ size_t n;
+
+ p = buf;
+ n = 0;
+ while ( (text = va_arg (arg_ptr, const char *)) )
+ {
+ if (n)
+ {
+ *p++ = ' ';
+ n++;
+ }
+ for ( ; *text && n < DIM (buf)-2; n++)
+ *p++ = *text++;
+ }
+ *p = 0;
+ assuan_write_status (ctx, get_status_string (no), buf);
+ }
+
+ va_end (arg_ptr);
+}
+
+void
+gpgsm_status (CTRL ctrl, int no, const char *text)
+{
+ gpgsm_status2 (ctrl, no, text, NULL);
+}
+
+void
+gpgsm_status_with_err_code (CTRL ctrl, int no, const char *text,
+ gpg_err_code_t ec)
+{
+ char buf[30];
+
+ sprintf (buf, "%u", (unsigned int)ec);
+ if (text)
+ gpgsm_status2 (ctrl, no, text, buf, NULL);
+ else
+ gpgsm_status2 (ctrl, no, buf, NULL);
+}
+
+#if 0
+/*
+ * Write a status line with a buffer using %XX escapes. If WRAP is >
+ * 0 wrap the line after this length. If STRING is not NULL it will
+ * be prepended to the buffer, no escaping is done for string.
+ * A wrap of -1 forces spaces not to be encoded as %20.
+ */
+void
+write_status_text_and_buffer ( int no, const char *string,
+ const char *buffer, size_t len, int wrap )
+{
+ const char *s, *text;
+ int esc, first;
+ int lower_limit = ' ';
+ size_t n, count, dowrap;
+
+ if( !statusfp )
+ return; /* not enabled */
+
+ if (wrap == -1) {
+ lower_limit--;
+ wrap = 0;
+ }
+
+ text = get_status_string (no);
+ count = dowrap = first = 1;
+ do {
+ if (dowrap) {
+ fprintf (statusfp, "[GNUPG:] %s ", text );
+ count = dowrap = 0;
+ if (first && string) {
+ fputs (string, statusfp);
+ count += strlen (string);
+ }
+ first = 0;
+ }
+ for (esc=0, s=buffer, n=len; n && !esc; s++, n-- ) {
+ if ( *s == '%' || *(const byte*)s <= lower_limit
+ || *(const byte*)s == 127 )
+ esc = 1;
+ if ( wrap && ++count > wrap ) {
+ dowrap=1;
+ break;
+ }
+ }
+ if (esc) {
+ s--; n++;
+ }
+ if (s != buffer)
+ fwrite (buffer, s-buffer, 1, statusfp );
+ if ( esc ) {
+ fprintf (statusfp, "%%%02X", *(const byte*)s );
+ s++; n--;
+ }
+ buffer = s;
+ len = n;
+ if ( dowrap && len )
+ putc ( '\n', statusfp );
+ } while ( len );
+
+ putc ('\n',statusfp);
+ fflush (statusfp);
+}
+#endif
diff --git a/sm/sign.c b/sm/sign.c
new file mode 100644
index 000000000..0afb52b62
--- /dev/null
+++ b/sm/sign.c
@@ -0,0 +1,621 @@
+/* sign.c - Sign a message
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "keydb.h"
+#include "i18n.h"
+
+
+static void
+hash_data (int fd, gcry_md_hd_t md)
+{
+ FILE *fp;
+ char buffer[4096];
+ int nread;
+
+ fp = fdopen ( dup (fd), "rb");
+ if (!fp)
+ {
+ log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno));
+ return;
+ }
+
+ do
+ {
+ nread = fread (buffer, 1, DIM(buffer), fp);
+ gcry_md_write (md, buffer, nread);
+ }
+ while (nread);
+ if (ferror (fp))
+ log_error ("read error on fd %d: %s\n", fd, strerror (errno));
+ fclose (fp);
+}
+
+static int
+hash_and_copy_data (int fd, gcry_md_hd_t md, KsbaWriter writer)
+{
+ KsbaError err;
+ FILE *fp;
+ char buffer[4096];
+ int nread;
+ int rc = 0;
+ int any = 0;
+
+ fp = fdopen ( dup (fd), "rb");
+ if (!fp)
+ {
+ gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno));
+ return tmperr;
+ }
+
+ do
+ {
+ nread = fread (buffer, 1, DIM(buffer), fp);
+ if (nread)
+ {
+ any = 1;
+ gcry_md_write (md, buffer, nread);
+ err = ksba_writer_write_octet_string (writer, buffer, nread, 0);
+ if (err)
+ {
+ log_error ("write failed: %s\n", ksba_strerror (err));
+ rc = map_ksba_err (err);
+ }
+ }
+ }
+ while (nread && !rc);
+ if (ferror (fp))
+ {
+ rc = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("read error on fd %d: %s\n", fd, strerror (errno));
+ }
+ fclose (fp);
+ if (!any)
+ {
+ /* We can't allow to sign an empty message because it does not
+ make much sense and more seriously, ksba-cms_build has
+ already written the tag for data and now expects an octet
+ string but an octet string of zeize 0 is illegal. */
+ log_error ("cannot sign an empty message\n");
+ rc = gpg_error (GPG_ERR_NO_DATA);
+ }
+ if (!rc)
+ {
+ err = ksba_writer_write_octet_string (writer, NULL, 0, 1);
+ if (err)
+ {
+ log_error ("write failed: %s\n", ksba_strerror (err));
+ rc = map_ksba_err (err);
+ }
+ }
+
+ return rc;
+}
+
+
+/* Get the default certificate which is defined as the first one our
+ keyDB retruns and has a secret key available */
+int
+gpgsm_get_default_cert (KsbaCert *r_cert)
+{
+ KEYDB_HANDLE hd;
+ KsbaCert cert = NULL;
+ int rc;
+ char *p;
+
+ hd = keydb_new (0);
+ if (!hd)
+ return gpg_error (GPG_ERR_GENERAL);
+ rc = keydb_search_first (hd);
+ if (rc)
+ {
+ keydb_release (hd);
+ return rc;
+ }
+
+ do
+ {
+ rc = keydb_get_cert (hd, &cert);
+ if (rc)
+ {
+ log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
+ keydb_release (hd);
+ return rc;
+ }
+
+ p = gpgsm_get_keygrip_hexstring (cert);
+ if (p)
+ {
+ if (!gpgsm_agent_havekey (p))
+ {
+ xfree (p);
+ keydb_release (hd);
+ *r_cert = cert;
+ return 0; /* got it */
+ }
+ xfree (p);
+ }
+
+ ksba_cert_release (cert);
+ cert = NULL;
+ }
+ while (!(rc = keydb_search_next (hd)));
+ if (rc && rc != -1)
+ log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc));
+
+ ksba_cert_release (cert);
+ keydb_release (hd);
+ return rc;
+}
+
+
+static KsbaCert
+get_default_signer (void)
+{
+ KEYDB_SEARCH_DESC desc;
+ KsbaCert cert = NULL;
+ KEYDB_HANDLE kh = NULL;
+ int rc;
+
+ if (!opt.local_user)
+ {
+ rc = gpgsm_get_default_cert (&cert);
+ if (rc)
+ {
+ if (rc != -1)
+ log_debug ("failed to find default certificate: %s\n",
+ gpg_strerror (rc));
+ return NULL;
+ }
+ return cert;
+ }
+
+ rc = keydb_classify_name (opt.local_user, &desc);
+ if (rc)
+ {
+ log_error ("failed to find default signer: %s\n", gpg_strerror (rc));
+ return NULL;
+ }
+
+ kh = keydb_new (0);
+ if (!kh)
+ return NULL;
+
+ rc = keydb_search (kh, &desc, 1);
+ if (rc)
+ {
+ log_debug ("failed to find default certificate: rc=%d\n", rc);
+ }
+ else
+ {
+ rc = keydb_get_cert (kh, &cert);
+ if (rc)
+ {
+ log_debug ("failed to get cert: rc=%d\n", rc);
+ }
+ }
+
+ keydb_release (kh);
+ return cert;
+}
+
+/* Depending on the options in CTRL add the certificate CERT as well as
+ other certificate up in the chain to the Root-CA to the CMS
+ object. */
+static int
+add_certificate_list (CTRL ctrl, KsbaCMS cms, KsbaCert cert)
+{
+ KsbaError err;
+ int rc = 0;
+ KsbaCert next = NULL;
+ int n;
+ int not_root = 0;
+
+ ksba_cert_ref (cert);
+
+ n = ctrl->include_certs;
+ if (n == -2)
+ {
+ not_root = 1;
+ n = -1;
+ }
+ if (n < 0 || n > 50)
+ n = 50; /* We better apply an upper bound */
+
+ 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;
+ }
+ while ( n-- && !(rc = gpgsm_walk_cert_chain (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", ksba_strerror (err));
+ return map_ksba_err (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 ctrl, CERTLIST signerlist,
+ int data_fd, int detached, FILE *out_fp)
+{
+ int i, rc;
+ KsbaError err;
+ Base64Context b64writer = NULL;
+ KsbaWriter writer;
+ KsbaCMS cms = NULL;
+ KsbaStopReason stopreason;
+ KEYDB_HANDLE kh = NULL;
+ gcry_md_hd_t data_md = NULL;
+ int signer;
+ const char *algoid;
+ int algo;
+ time_t signed_at;
+ CERTLIST cl;
+ int release_signerlist = 0;
+
+ kh = keydb_new (0);
+ if (!kh)
+ {
+ log_error (_("failed to allocated keyDB handle\n"));
+ rc = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+
+ ctrl->pem_name = "SIGNED MESSAGE";
+ rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer);
+ if (rc)
+ {
+ log_error ("can't create writer: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ cms = ksba_cms_new ();
+ if (!cms)
+ {
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ goto leave;
+ }
+
+ err = ksba_cms_set_reader_writer (cms, NULL, writer);
+ if (err)
+ {
+ log_debug ("ksba_cms_set_reader_writer failed: %s\n",
+ ksba_strerror (err));
+ rc = map_ksba_err (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",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+
+ /* If no list of signers is given, use a default one. */
+ if (!signerlist)
+ {
+ KsbaCert cert = get_default_signer ();
+ if (!cert)
+ {
+ log_error ("no default signer found\n");
+ rc = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+ signerlist = xtrycalloc (1, sizeof *signerlist);
+ if (!signerlist)
+ {
+ rc = OUT_OF_CORE (errno);
+ ksba_cert_release (cert);
+ goto leave;
+ }
+ signerlist->cert = cert;
+ release_signerlist = 1;
+ }
+
+
+ /* Gather certificates of signers and store them in the CMS object. */
+ for (cl=signerlist; cl; cl = cl->next)
+ {
+ rc = gpgsm_cert_use_sign_p (cl->cert);
+ if (rc)
+ goto leave;
+
+ err = ksba_cms_add_signer (cms, cl->cert);
+ if (err)
+ {
+ log_error ("ksba_cms_add_signer failed: %s\n", ksba_strerror (err));
+ rc = map_ksba_err (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, "1.3.14.3.2.26" /*SHA-1*/);
+ if (err)
+ {
+ log_debug ("ksba_cms_add_digest_algo failed: %s\n",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+ }
+
+ /* Prepare hashing (actually we are figuring out what we have set above)*/
+ rc = gcry_md_open (&data_md, 0, 0);
+ if (rc)
+ {
+ log_error ("md_open failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ if (DBG_HASHING)
+ gcry_md_start_debug (data_md, "sign.data");
+
+ for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++)
+ {
+ algo = gcry_md_map_name (algoid);
+ if (!algo)
+ {
+ log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?");
+ rc = gpg_error (GPG_ERR_BUG);
+ goto leave;
+ }
+ gcry_md_enable (data_md, algo);
+ }
+
+ 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;
+ /* Fixme do this for all signers and get the algo to use from
+ the signer's certificate - does not make mich sense, bu we
+ should do this consistent as we have already done it above */
+ algo = GCRY_MD_SHA1;
+ hash_data (data_fd, data_md);
+ digest = gcry_md_read (data_md, algo);
+ digest_len = gcry_md_get_algo_dlen (algo);
+ if ( !digest || !digest_len)
+ {
+ log_error ("problem getting the hash of the data\n");
+ rc = gpg_error (GPG_ERR_BUG);
+ goto leave;
+ }
+ for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
+ {
+ err = ksba_cms_set_message_digest (cms, signer, digest, digest_len);
+ if (err)
+ {
+ log_error ("ksba_cms_set_message_digest failed: %s\n",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+ }
+ }
+
+ signed_at = gnupg_get_time ();
+ 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",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+ }
+
+ do
+ {
+ err = ksba_cms_build (cms, &stopreason);
+ if (err)
+ {
+ log_debug ("ksba_cms_build failed: %s\n", ksba_strerror (err));
+ rc = map_ksba_err (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);
+ /* Fixme: get the algo to use from the signer's certificate
+ - does not make much sense, but we should do this
+ consistent as we have already done it above. Code is
+ mostly duplicated above. */
+
+ algo = GCRY_MD_SHA1;
+ rc = hash_and_copy_data (data_fd, data_md, writer);
+ if (rc)
+ goto leave;
+ digest = gcry_md_read (data_md, algo);
+ digest_len = gcry_md_get_algo_dlen (algo);
+ if ( !digest || !digest_len)
+ {
+ log_error ("problem getting the hash of the data\n");
+ rc = gpg_error (GPG_ERR_BUG);
+ goto leave;
+ }
+ for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
+ {
+ err = ksba_cms_set_message_digest (cms, signer,
+ digest, digest_len);
+ if (err)
+ {
+ log_error ("ksba_cms_set_message_digest failed: %s\n",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+ }
+ }
+ else if (stopreason == KSBA_SR_NEED_SIG)
+ { /* calculate the signature for all signers */
+ gcry_md_hd_t md;
+
+ algo = GCRY_MD_SHA1;
+ rc = gcry_md_open (&md, algo, 0);
+ if (rc)
+ {
+ log_error ("md_open failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ if (DBG_HASHING)
+ gcry_md_start_debug (md, "sign.attr");
+ ksba_cms_set_hash_function (cms, HASH_FNC, md);
+ for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
+ {
+ char *sigval = NULL;
+ char *buf, *fpr;
+
+ if (signer)
+ gcry_md_reset (md);
+ rc = ksba_cms_hash_signed_attrs (cms, signer);
+ if (rc)
+ {
+ log_debug ("hashing signed attrs failed: %s\n",
+ ksba_strerror (rc));
+ gcry_md_close (md);
+ goto leave;
+ }
+
+ rc = gpgsm_create_cms_signature (cl->cert, md, algo, &sigval);
+ if (rc)
+ {
+ gcry_md_close (md);
+ goto leave;
+ }
+
+ err = ksba_cms_set_sig_val (cms, signer, sigval);
+ xfree (sigval);
+ if (err)
+ {
+ log_error ("failed to store the signature: %s\n",
+ ksba_strerror (err));
+ rc = map_ksba_err (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 = asprintf (&buf, "%c %d %d 00 %lu %s",
+ detached? 'D':'S',
+ GCRY_PK_RSA, /* FIXME: get pk algo from cert */
+ algo,
+ (ulong)signed_at,
+ fpr);
+ xfree (fpr);
+ if (rc < 0)
+ {
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ gcry_md_close (md);
+ goto leave;
+ }
+ rc = 0;
+ gpgsm_status (ctrl, STATUS_SIG_CREATED, buf);
+ free (buf); /* yes, we must use the regular free() here */
+ }
+ gcry_md_close (md);
+
+ }
+ }
+ while (stopreason != KSBA_SR_READY);
+
+ rc = gpgsm_finish_writer (b64writer);
+ if (rc)
+ {
+ log_error ("write failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ log_info ("signature created\n");
+
+
+ leave:
+ if (release_signerlist)
+ gpgsm_release_certlist (signerlist);
+ ksba_cms_release (cms);
+ gpgsm_destroy_writer (b64writer);
+ keydb_release (kh);
+ gcry_md_close (data_md);
+ return rc;
+}
diff --git a/sm/verify.c b/sm/verify.c
new file mode 100644
index 000000000..6dd4f4e5b
--- /dev/null
+++ b/sm/verify.c
@@ -0,0 +1,550 @@
+/* verify.c - Verify a messages signature
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <assert.h>
+
+#include "gpgsm.h"
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "keydb.h"
+#include "i18n.h"
+
+static char *
+strtimestamp_r (time_t atime)
+{
+ char *buffer = xmalloc (15);
+
+ if (atime < 0)
+ strcpy (buffer, "????" "-??" "-??");
+ else if (!atime)
+ strcpy (buffer, "none");
+ else
+ {
+ struct tm *tp;
+
+ tp = gmtime( &atime );
+ sprintf (buffer, "%04d-%02d-%02d",
+ 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday);
+ }
+ return buffer;
+}
+
+
+
+/* Hash the data for a detached signature */
+static void
+hash_data (int fd, gcry_md_hd_t md)
+{
+ FILE *fp;
+ char buffer[4096];
+ int nread;
+
+ fp = fdopen ( dup (fd), "rb");
+ if (!fp)
+ {
+ log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno));
+ return;
+ }
+
+ do
+ {
+ nread = fread (buffer, 1, DIM(buffer), fp);
+ gcry_md_write (md, buffer, nread);
+ }
+ while (nread);
+ if (ferror (fp))
+ log_error ("read error on fd %d: %s\n", fd, strerror (errno));
+ fclose (fp);
+}
+
+
+
+
+/* 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 ctrl, int in_fd, int data_fd, FILE *out_fp)
+{
+ int i, rc;
+ Base64Context b64reader = NULL;
+ Base64Context b64writer = NULL;
+ KsbaError err;
+ KsbaReader reader;
+ KsbaWriter writer = NULL;
+ KsbaCMS cms = NULL;
+ KsbaStopReason stopreason;
+ KsbaCert cert;
+ KEYDB_HANDLE kh;
+ gcry_md_hd_t data_md = NULL;
+ int signer;
+ const char *algoid;
+ int algo;
+ int is_detached;
+ FILE *fp = NULL;
+ char *p;
+
+ kh = keydb_new (0);
+ if (!kh)
+ {
+ log_error (_("failed to allocated keyDB handle\n"));
+ rc = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+
+
+ fp = fdopen ( dup (in_fd), "rb");
+ if (!fp)
+ {
+ rc = gpg_error (gpg_err_code_from_errno (errno));
+ log_error ("fdopen() failed: %s\n", strerror (errno));
+ goto leave;
+ }
+
+ rc = gpgsm_create_reader (&b64reader, ctrl, fp, &reader);
+ if (rc)
+ {
+ log_error ("can't create reader: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+
+ if (out_fp)
+ {
+ rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer);
+ if (rc)
+ {
+ log_error ("can't create writer: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ }
+
+ cms = ksba_cms_new ();
+ if (!cms)
+ {
+ rc = gpg_error (GPG_ERR_ENOMEM);
+ goto leave;
+ }
+
+ err = ksba_cms_set_reader_writer (cms, reader, writer);
+ if (err)
+ {
+ log_error ("ksba_cms_set_reader_writer failed: %s\n",
+ ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+
+ rc = gcry_md_open (&data_md, 0, 0);
+ if (rc)
+ {
+ log_error ("md_open failed: %s\n", gpg_strerror (rc));
+ goto leave;
+ }
+ if (DBG_HASHING)
+ gcry_md_start_debug (data_md, "vrfy.data");
+
+ is_detached = 0;
+ do
+ {
+ err = ksba_cms_parse (cms, &stopreason);
+ if (err)
+ {
+ log_error ("ksba_cms_parse failed: %s\n", ksba_strerror (err));
+ rc = map_ksba_err (err);
+ goto leave;
+ }
+
+ if (stopreason == KSBA_SR_NEED_HASH)
+ {
+ is_detached = 1;
+ if (opt.verbose)
+ log_info ("detached signature\n");
+ }
+
+ if (stopreason == KSBA_SR_NEED_HASH
+ || stopreason == KSBA_SR_BEGIN_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:"?");
+ else
+ gcry_md_enable (data_md, algo);
+ }
+ if (is_detached)
+ {
+ if (data_fd == -1)
+ log_info ("detached signature w/o data "
+ "- assuming certs-only\n");
+ else
+ 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 */
+
+ }
+ }
+ while (stopreason != KSBA_SR_READY);
+
+ if (b64writer)
+ {
+ rc = gpgsm_finish_writer (b64writer);
+ if (rc)
+ {
+ log_error ("write failed: %s\n", gpg_strerror (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);
+ 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. */
+ keydb_store_cert (cert, 0, NULL);
+ ksba_cert_release (cert);
+ }
+
+ cert = NULL;
+ err = 0;
+ for (signer=0; ; signer++)
+ {
+ char *issuer = NULL;
+ KsbaSexp sigval = NULL;
+ time_t sigtime, keyexptime;
+ KsbaSexp serial;
+ char *msgdigest = NULL;
+ size_t msgdigestlen;
+ char *ctattr;
+
+ err = ksba_cms_get_issuer_serial (cms, signer, &issuer, &serial);
+ if (!signer && err == KSBA_No_Data && data_fd == -1 && is_detached)
+ {
+ log_info ("certs-only message accepted\n");
+ err = 0;
+ break;
+ }
+ if (err)
+ {
+ if (signer && err == -1)
+ err = 0;
+ break;
+ }
+ 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");
+ }
+
+ err = ksba_cms_get_signing_time (cms, signer, &sigtime);
+ if (err == KSBA_No_Data)
+ sigtime = 0;
+ else if (err)
+ {
+ log_error ("error getting signing time: %s\n", ksba_strerror (err));
+ sigtime = (time_t)-1;
+ }
+
+ err = ksba_cms_get_message_digest (cms, signer,
+ &msgdigest, &msgdigestlen);
+ if (!err)
+ {
+ 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_info (data_md, GCRYCTL_IS_ALGO_ENABLED, &algo, NULL) )
+ {
+ log_error ("digest algo %d has not been enabled\n", algo);
+ goto next_signer;
+ }
+ }
+ else if (err == KSBA_No_Data)
+ {
+ assert (!msgdigest);
+ err = 0;
+ algoid = NULL;
+ algo = 0;
+ }
+ else /* real error */
+ break;
+
+ err = ksba_cms_get_sigattr_oids (cms, signer,
+ "1.2.840.113549.1.9.3",&ctattr);
+ if (!err)
+ {
+ 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;
+ goto next_signer;
+ }
+ ksba_free (ctattr);
+ ctattr = NULL;
+ }
+ else if (err != -1)
+ {
+ log_error ("error getting content-type attribute: %s\n",
+ ksba_strerror (err));
+ goto next_signer;
+ }
+ err = 0;
+
+
+ sigval = ksba_cms_get_sig_val (cms, signer);
+ if (!sigval)
+ {
+ log_error ("no signature value available\n");
+ goto next_signer;
+ }
+ if (DBG_X509)
+ log_debug ("signer %d - signature available", signer);
+
+ /* Find the certificate of the signer */
+ keydb_search_reset (kh);
+ rc = keydb_search_issuer_sn (kh, issuer, serial);
+ if (rc)
+ {
+ if (rc == -1)
+ {
+ log_error ("certificate not found\n");
+ rc = gpg_error (GPG_ERR_NO_PUBKEY);
+ }
+ else
+ log_error ("failed to find the certificate: %s\n",
+ gpg_strerror(rc));
+ {
+ char numbuf[50];
+ sprintf (numbuf, "%d", rc);
+
+ gpgsm_status2 (ctrl, STATUS_ERROR, "verify.findkey",
+ numbuf, NULL);
+ }
+ /* fixme: we might want to append the issuer and serial
+ using our standard notation */
+ goto next_signer;
+ }
+
+ rc = keydb_get_cert (kh, &cert);
+ if (rc)
+ {
+ log_error ("failed to get cert: %s\n", gpg_strerror (rc));
+ goto next_signer;
+ }
+
+ log_info (_("Signature made "));
+ if (sigtime)
+ gpgsm_dump_time (sigtime);
+ else
+ log_printf (_("[date not given]"));
+ log_printf (_(" using certificate ID %08lX\n"),
+ gpgsm_get_short_fingerprint (cert));
+
+
+ if (msgdigest)
+ { /* Signed attributes are available. */
+ gcry_md_hd_t md;
+ unsigned char *s;
+
+ /* check that the message digest in the signed attributes
+ matches the one we calculated on the data */
+ s = gcry_md_read (data_md, algo);
+ if ( !s || !msgdigestlen
+ || gcry_md_get_algo_dlen (algo) != msgdigestlen
+ || !s || memcmp (s, msgdigest, msgdigestlen) )
+ {
+ char *fpr;
+
+ log_error ("invalid signature: message digest attribute "
+ "does not match calculated one\n");
+ fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
+ gpgsm_status (ctrl, STATUS_BADSIG, fpr);
+ xfree (fpr);
+ goto next_signer;
+ }
+
+ rc = gcry_md_open (&md, algo, 0);
+ if (rc)
+ {
+ log_error ("md_open failed: %s\n", gpg_strerror (rc));
+ goto next_signer;
+ }
+ if (DBG_HASHING)
+ gcry_md_start_debug (md, "vrfy.attr");
+
+ ksba_cms_set_hash_function (cms, HASH_FNC, md);
+ rc = ksba_cms_hash_signed_attrs (cms, signer);
+ if (rc)
+ {
+ log_error ("hashing signed attrs failed: %s\n",
+ ksba_strerror (rc));
+ gcry_md_close (md);
+ goto next_signer;
+ }
+ rc = gpgsm_check_cms_signature (cert, sigval, md, algo);
+ gcry_md_close (md);
+ }
+ else
+ {
+ rc = gpgsm_check_cms_signature (cert, sigval, data_md, algo);
+ }
+
+ if (rc)
+ {
+ char *fpr;
+
+ log_error ("invalid signature: %s\n", gpg_strerror (rc));
+ fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
+ gpgsm_status (ctrl, STATUS_BADSIG, fpr);
+ xfree (fpr);
+ 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");
+ rc = gpgsm_validate_chain (ctrl, cert, &keyexptime);
+ if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED)
+ {
+ gpgsm_status (ctrl, STATUS_EXPKEYSIG, NULL);
+ rc = 0;
+ }
+ else
+ gpgsm_status (ctrl, STATUS_GOODSIG, NULL);
+
+ {
+ char *buf, *fpr, *tstr;
+
+ fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
+ tstr = strtimestamp_r (sigtime);
+ buf = xmalloc ( strlen(fpr) + strlen (tstr) + 120);
+ sprintf (buf, "%s %s %lu %lu", fpr, tstr,
+ (unsigned long)sigtime, (unsigned long)keyexptime );
+ xfree (tstr);
+ xfree (fpr);
+ gpgsm_status (ctrl, STATUS_VALIDSIG, buf);
+ xfree (buf);
+ }
+
+ 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));
+ goto next_signer;
+ }
+
+ for (i=0; (p = ksba_cert_get_subject (cert, i)); i++)
+ {
+ log_info (!i? _("Good signature from")
+ : _(" aka"));
+ log_printf (" \"");
+ gpgsm_print_name (log_get_stream (), p);
+ log_printf ("\"\n");
+ ksba_free (p);
+ }
+
+ gpgsm_status (ctrl, STATUS_TRUST_FULLY, NULL);
+
+
+ next_signer:
+ rc = 0;
+ xfree (issuer);
+ xfree (serial);
+ xfree (sigval);
+ xfree (msgdigest);
+ ksba_cert_release (cert);
+ cert = NULL;
+ }
+ rc = 0;
+ if (err)
+ {
+ log_error ("ksba error: %s\n", ksba_strerror (err));
+ rc = map_ksba_err (rc);
+ }
+
+
+
+ leave:
+ ksba_cms_release (cms);
+ gpgsm_destroy_reader (b64reader);
+ gpgsm_destroy_writer (b64writer);
+ keydb_release (kh);
+ gcry_md_close (data_md);
+ if (fp)
+ fclose (fp);
+
+ if (rc)
+ {
+ char numbuf[50];
+ sprintf (numbuf, "%d", rc );
+ gpgsm_status2 (ctrl, STATUS_ERROR, "verify.leave",
+ numbuf, NULL);
+ }
+
+ return rc;
+}
+