diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:14:06 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:14:06 +0000 |
commit | eee068778cb28ecf3c14e1bf843a95547d72c42d (patch) | |
tree | 0e07b30ddc5ea579d682d5dbe57998200d1c9ab7 /scd | |
parent | Initial commit. (diff) | |
download | gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.tar.xz gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.zip |
Adding upstream version 2.2.40.upstream/2.2.40upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | scd/ChangeLog-2011 | 2592 | ||||
-rw-r--r-- | scd/Makefile.am | 51 | ||||
-rw-r--r-- | scd/Makefile.in | 846 | ||||
-rw-r--r-- | scd/apdu.c | 3523 | ||||
-rw-r--r-- | scd/apdu.h | 156 | ||||
-rw-r--r-- | scd/app-common.h | 306 | ||||
-rw-r--r-- | scd/app-dinsig.c | 574 | ||||
-rw-r--r-- | scd/app-geldkarte.c | 408 | ||||
-rw-r--r-- | scd/app-help.c | 285 | ||||
-rw-r--r-- | scd/app-nks.c | 1428 | ||||
-rw-r--r-- | scd/app-openpgp.c | 5480 | ||||
-rw-r--r-- | scd/app-p15.c | 6290 | ||||
-rw-r--r-- | scd/app-sc-hsm.c | 2087 | ||||
-rw-r--r-- | scd/app.c | 1356 | ||||
-rw-r--r-- | scd/atr.c | 290 | ||||
-rw-r--r-- | scd/atr.h | 27 | ||||
-rw-r--r-- | scd/ccid-driver.c | 4080 | ||||
-rw-r--r-- | scd/ccid-driver.h | 158 | ||||
-rw-r--r-- | scd/command.c | 2130 | ||||
-rw-r--r-- | scd/iso7816.c | 1034 | ||||
-rw-r--r-- | scd/iso7816.h | 154 | ||||
-rw-r--r-- | scd/scdaemon-w32info.rc | 52 | ||||
-rw-r--r-- | scd/scdaemon.c | 1454 | ||||
-rw-r--r-- | scd/scdaemon.h | 145 | ||||
-rw-r--r-- | scd/scdaemon.w32-manifest.in | 18 |
25 files changed, 34924 insertions, 0 deletions
diff --git a/scd/ChangeLog-2011 b/scd/ChangeLog-2011 new file mode 100644 index 0000000..9184af4 --- /dev/null +++ b/scd/ChangeLog-2011 @@ -0,0 +1,2592 @@ +2011-12-01 Werner Koch <wk@g10code.com> + + NB: ChangeLog files are no longer manually maintained. Starting + on December 1st, 2011 we put change information only in the GIT + commit log, and generate a top-level ChangeLog file from logs at + "make dist". See doc/HACKING for details. + +2011-12-01 Niibe Yutaka <gniibe@fsij.org> + + * app-openpgp.c (do_change_pin): Fix pincb messages when + use_keypad == 1. + +2011-11-29 Niibe Yutaka <gniibe@fsij.org> + + PC/SC pininput support for passphrase modification (2/2) + * apdu.h (apdu_send_simple_kp): Remove. + + * apdu.c (pcsc_keypad_modify): Add bConfirmPIN handling. + (apdu_send_simple_kp): Remove. + + * iso7816.h (iso7816_reset_retry_counter_kp): Remove arguments + of NEWCHV, and NEWCHVLEN. + (iso7816_reset_retry_counter_with_rc_kp, iso7816_put_data_kp): New. + + * iso7816.c (iso7816_reset_retry_counter_with_rc_kp): New. + (iso7816_reset_retry_counter_kp): Call apdu_keypad_modify. Only + handle the case with PININFO. + (iso7816_reset_retry_counter): Don't call + iso7816_reset_retry_counter_kp. + (iso7816_put_data_kp): New. + + * app-openpgp.c (do_change_pin): Add with_resetcode. + Handle keypad for unblocking pass phrase with resetcode, + setting up of resetcode, and unblocking by admin. + + PC/SC pininput support for passphrase modification (1/2) + * iso7816.h (iso7816_change_reference_data_kp): Remove arguments + of OLDCHV, OLDCHVLEN, NEWCHV, and NEWCHVLEN. + + * iso7816.c (iso7816_change_reference_data_kp): Call + apdu_keypad_modify. + (iso7816_change_reference_data): Don't call + iso7816_change_reference_data_kp. + + * apdu.h (apdu_keypad_modify): New. + + * apdu.c (pcsc_keypad_modify, apdu_keypad_modify): New. + (struct reader_table_s): New memeber function keypad_modify. + (new_reader_slot, open_ct_reader, open_ccid_reader) + (open_rapdu_reader): Initialize keypad_modify. + + * app-openpgp.c (do_change_pin): Handle keypad and call + iso7816_change_reference_data_kp if it is the case. + +2011-11-28 Niibe Yutaka <gniibe@fsij.org> + + * iso7816.h (iso7816_verify_kp): Remove arguments of CHV and CHVLEN. + + * iso7816.c (iso7816_verify_kp): Call apdu_keypad_verify. Only + handle the case with PININFO. + (iso7816_verify): Call apdu_send_simple. + + * app-openpgp.c (verify_a_chv, verify_chv3): Follow the change of + iso7816_verify_kp. + + * app-nks.c (verify_pin): Likewise. + + * app-dinsig.c (verify_pin): Likewise. + + * apdu.c: Include "iso7816.h". + (struct reader_table_s): New memeber function keypad_verify. + Add fields verify_ioctl and modify_ioctl in pcsc. + (CM_IOCTL_GET_FEATURE_REQUEST, FEATURE_VERIFY_PIN_DIRECT) + (FEATURE_MODIFY_PIN_DIRECT): New. + (pcsc_control): New. + (control_pcsc_direct, control_pcsc_wrapped, control_pcsc) + (check_pcsc_keypad, pcsc_keypad_verify): New. + (ccid_keypad_verify, apdu_keypad_verify): New. + (new_reader_slot): Initialize with check_pcsc_keypad, + pcsc_keypad_verify, verify_ioctl and modify_ioctl. + (open_ct_reader): Initialize keypad_verify with NULL. + (open_ccid_reader): Initialize keypad_verify. + (open_rapdu_reader): Initialize keypad_verify with NULL. + (apdu_open_reader): Initialize pcsc_control. + + * pcsc-wrapper.c (load_pcsc_driver): Initialize pcsc_control. + (handle_control): New. + (main): Handle the case 6 of handle_control. + +2011-08-10 Werner Koch <wk@g10code.com> + + * command.c (cmd_killscd): Use the new assuan force close flag + if available. + +2011-08-08 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_decipher): Take care of accidentally passed + signed integer data with a leading 0. + +2011-06-16 Werner Koch <wk@g10code.com> + + * app-openpgp.c (send_key_data): Implemented chunked mode. + (change_keyattr): Increase limit to 4096. + (do_decipher): Adjust padding for 4096 bit keys. + +2011-02-23 Werner Koch <wk@g10code.com> + + * apdu.c (apdu_open_reader): Lock in to CCID if used once. + +2011-01-25 NIIBE Yutaka <gniibe@fsij.org>, + Grant Olson <kgo@grant-olson.net> (wk) + + * command.c (do_reset, get_reader_slot) + (update_reader_status_file): Fix handling of the VALID flag for + unplugged readers. + +2011-01-25 Werner Koch <wk@g10code.com> + + From 2.0 branch, 2010-03-17: + + * command.c (open_card): Return GPG_ERR_NOT_OPERATIONAL if no + card services are available. + (get_reader_slot): Detect no services status. + (cmd_serialno): No reset if there are no services. + (scd_command_handler): Stop scdaemon in that case. + * apdu.c (pcsc_no_service): New. + (open_pcsc_reader_direct): Set it. + (apdu_open_reader): Add arg R_NO_SERVICE. + +2011-01-05 Werner Koch <wk@g10code.com> + + * ccid-driver.c (ccid_transceive_secure): Support the gnuk token. + +2010-11-16 Werner Koch <wk@g10code.com> + + * apdu.c (PCSC_UNKNOWN) [W32]: Fix all these values which don't + match those of libpcsc. Reported by Michael Petig. + +2010-10-27 Werner Koch <wk@g10code.com> + + * scdaemon.c (create_socket_name): Use TMPDIR. Change callers. + +2010-10-18 Werner Koch <wk@g10code.com> + + * app-openpgp.c (parse_algorithm_attribute): Remove extra const in + definition of DESC. + +2010-08-16 Werner Koch <wk@g10code.com> + + * scdaemon.c: Replace remaining printf by es_printf. + +2010-06-09 Werner Koch <wk@g10code.com> + + * scdaemon.c (main): s/log_set_get_tid_callback/log_set_pid_suffix_cb/. + (tid_log_callback): Adjust for this change. + +2010-03-11 Werner Koch <wk@g10code.com> + + * scdaemon.c: Include "asshelp.h". + (main): Remove assuan_set_assuan_log_prefix. Add + assuan_set_log_cb. + (handle_signal): Disable pth ctrl dumping. + * command.c (scd_command_handler): Remove assuan_set_log_stream. + +2010-03-10 Werner Koch <wk@g10code.com> + + * Makefile.am (scdaemon_LDADD): Remove libjnlib.a. + +2009-12-15 Werner Koch <wk@g10code.com> + + * iso7816.c (do_generate_keypair): s/readonly/read_only/ because + the first is a keyword in VMS C. + +2009-12-03 Werner Koch <wk@g10code.com> + + * scdaemon.c (set_debug): Allow for numerical debug leveles. Print + active debug flags. + +2009-11-25 Marcus Brinkmann <marcus@g10code.de> + + * command.c (scd_command_handler): Use assuan_fd_t and + assuan_fdopen on fds. + +2009-11-05 Marcus Brinkmann <marcus@g10code.de> + + * command.c (scd_command_handler): Call assuan_init_socket_server, + not assuan_init_socket_server_ext. + +2009-11-04 Werner Koch <wk@g10code.com> + + * command.c (register_commands): Add help arg to + assuan_register_command. Add help strings to all commands. + +2009-11-02 Marcus Brinkmann <marcus@g10code.de> + + * command.c (reset_notify): Take LINE arg and return error. + (register_commands): Use assuan_handler_t type. + +2009-10-25 Werner Koch <wk@g10code.com> + + * scdaemon.c (scd_deinit_default_ctrl): Release IN_DATA. + * command.c (cmd_setdata): Release IN_DATA. Reported by Klaus + Flittner. + +2009-10-16 Marcus Brinkmann <marcus@g10code.com> + + * AM_CFLAGS, scdaemon_LDADD: Use libassuan instead of libassuan-pth. + * scdaemon.c: Invoke ASSUAN_SYSTEM_PTH_IMPL. + (main): Call assuan_set_system_hooks and assuan_sock_init. + +2009-09-23 Marcus Brinkmann <marcus@g10code.de> + + * command.c: Include "scdaemon.h" before <assuan.h> because of + GPG_ERR_SOURCE_DEFAULT check. + (option_handler, open_card, cmd_serialno, cmd_lean, cmd_readcert) + (cmd_readkey, cmd_setdata, cmd_pksign, cmd_pkauth, cmd_pkdecrypt) + (cmd_getattr, cmd_setattr, cmd_writecert, cmd_writekey) + (cmd_genkey, cmd_random, cmd_passwd, cmd_checkpin, cmd_lock) + (cmd_unlock, cmd_getinfo, cmd_restart, cmd_disconnect, cmd_apdu) + (cmd_killscd): Return gpg_error_t instead of int. + (scd_command_handler): Allocate assuan context before starting server. + * scdaemon.c (main): Update to new Assuan API. + +2009-09-03 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_decipher): Compute required Le. + * iso7816.c (iso7816_decipher): Add new arg LE. + * app-nks.c (do_decipher): Adjust for change. + + * iso7816.c (iso7816_put_data, iso7816_put_data_odd): Turn DATA + into a void ptr. + +2009-08-05 Werner Koch <wk@g10code.com> + + * app-openpgp.c (change_keyattr_from_string): New. + (do_setattr): Support KEY-ATTR. + +2009-07-29 Marcus Brinkmann <marcus@g10code.com> + + * ccid-driver.c (print_pr_data): Fix 64 bit compat problem. + +2009-07-24 Werner Koch <wk@g10code.com> + + * ccid-driver.c (parse_ccid_descriptor): Enable hack for SCR 3320. + +2009-07-21 Werner Koch <wk@g10code.com> + + * ccid-driver.c [HAVE_PTH]: Include pth.h. + (my_sleep): New. + (bulk_in): s/gnupg_sleep/my_sleep/. + +2009-07-20 Werner Koch <wk@g10code.com> + + * apdu.c [GNUPG_MAJOR_VERSION==1]: Include dynload.h. + +2009-07-16 Werner Koch <wk@g10code.com> + + * command.c (update_reader_status_file): Test for unplugged reader. + (TEST_CARD_REMOVAL): Ditto. + * app.c (select_application): Ditto. + * ccid-driver.c (bulk_out): Return CCID_DRIVER_ERR_NO_READER if a + reader was unplugged. + (struct ccid_driver_s): Turn nonnull_nad into an unsigned char. + Turn apdu_level, auto_ifsd, powered_off, has_pinpad into + bitfields. Add enodev_seen. + * apdu.c (apdu_prepare_exit): New. + (get_status_ccid): Return the status word and nut just -1. + * scdaemon.c (scd_exit): Call it. + +2009-07-13 Werner Koch <wk@g10code.com> + + * ccid-driver.c (struct ccid_driver_s): Add fields last_progress, + progress_cb and progress_cb_arg. + (ccid_set_progress_cb): New. + (print_progress): New. + (ccid_transceive): Call print_progress for wait time extensions. + * apdu.c (struct reader_table_s): Add field set_progress_cb. + (new_reader_slot): Clear that field. + (open_ccid_reader): Set it to .. + (set_progress_cb_ccid_reader): ... new fucntion. + * app.c (print_progress_line): New. + (lock_reader): Add arg CTRL to set a progress callback and + change all callers to provide it. + (unlock_reader): Remove the progress callback. + +2009-07-10 Werner Koch <wk@g10code.com> + + * iso7816.c (iso7816_compute_ds): Add args EXTENDED_MODE and LE. + Change all callers to use 0. + (iso7816_internal_authenticate): Add args EXTENDED_MODE and LE. + * app-openpgp.c (do_sign): Take exmode and Le from card + capabilities and pass them to iso7816_compute_ds. + (do_auth): Ditto for iso7816_internal_authenticate. + (change_keyattr): Reset CHV verification status. + +2009-07-09 Werner Koch <wk@g10code.com> + + * app-openpgp.c (change_keyattr): New. + (do_writekey): Call it. + + * app-openpgp.c (does_key_exist): Add arg GENERATING. Change + callers. + +2009-06-30 Werner Koch <wk@g10code.com> + + * ccid-driver.c (ccid_transceive): Set RESYNCING flag. + +2009-06-29 Werner Koch <wk@g10code.com> + + * ccid-driver.c (ccid_transceive): Add a hack to support extended + length for Omnikey readers. + (is_exlen_apdu): New. + (parse_ccid_descriptor): Track short+extended apdu exchange level. + +2009-06-18 Werner Koch <wk@g10code.com> + + * app-openpgp.c (verify_chv2): Remove special case for v2 cards. + (get_public_key): Use extended mode. + +2009-06-17 Werner Koch <wk@g10code.com> + + * iso7816.c (iso7816_get_data): Add arg EXTENDED_MODE. Change all + callers. + * app-openpgp.c (data_objects): Use bit flags. Add flag + TRY_EXTLENGTH. + (get_cached_data): Add arg TRY_EXTLEN and use it for iso7816_get_data. + (get_one_do): Use extended length APDU if necessary. + +2009-06-10 Werner Koch <wk@g10code.com> + + * app-openpgp.c (store_fpr): Change first arg to app_t; adjust + callers. Flush the cache. + +2009-06-09 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_readcert): Return NOT_FOUND if the retrieved + data has a length of zero. + (do_getattr): Add EXTCAP subkey "sm". + +2009-05-20 Werner Koch <wk@g10code.com> + + * app-openpgp.c (verify_chv2): Add case for v2 cards. + (verify_chv3): Factor some code out to .. + (build_enter_admin_pin_prompt): .. new. + (do_change_pin): Properly handle v2 cards. + +2009-05-19 Werner Koch <wk@g10code.com> + + * scdaemon.c (create_server_socket): Use SUN_LEN. + (JNLIB_NEED_AFLOCAL): Define. + +2009-05-13 Werner Koch <wk@g10code.com> + + * ccid-driver.c (abort_cmd): Add arg SEQNO and change callers. + (bulk_in): Retry on seqno mismatch. + + * apdu.c (send_le): Release result_buffer. + (apdu_send_direct): Implemend extended length. + * command.c (cmd_apdu): Add option "--exlen". + +2009-05-11 Werner Koch <wk@g10code.com> + + * apdu.c (send_le): Replace log_error by log_info. + +2009-05-08 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_genkey): Allow larger key sizes. + (do_decipher): Ditto. + * iso7816.c (do_generate_keypair): Add arg EXTENDED_MODE an LE. + (iso7816_generate_keypair, iso7816_read_public_key): Ditto. + Changed all callers. + * apdu.c (send_le): Implement extended length return values. + + * ccid-driver.c (bulk_in): Retry on EAGAIN. + (abort_cmd): Change seqno handling. + +2009-04-28 Werner Koch <wk@g10code.com> + + * app-help.c (app_help_count_bits): New. + + * app-nks.c (switch_application): Detect mass signature cards. + Take care of new NEED_APP_SELECT flag. + (do_sign): Don't allow mass signature cards. + (all_zero_p): New. + (do_readkey): New. + (app_select_nks): Register do_readkey. + +2009-04-01 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_setattr, do_writekey): Prepare for extended + length cards. + +2009-03-31 Werner Koch <wk@g10code.com> + + * command.c (percent_plus_unescape): Remove. + (cmd_setattr): Use percent_plus_unescape_inplace. + +2009-03-30 Werner Koch <wk@g10code.com> + + * app-nks.c (do_decipher): Make it work for TCOS 3. + * iso7816.c (iso7816_decipher): Add arg EXTENDED_MODE. + * apdu.c (apdu_send): Add arg EXTENDED_MODE and change all callers. + (apdu_send_le): Ditto. + (apdu_send_direct): Ditto, but not yet functional. + (send_le): Fix command chaining. Implement extended length option. + * ccid-driver.c (ccid_transceive): Remove restriction on apdu length. + (struct ccid_driver_s): Add field IFSC. + (ccid_get_atr): Set IFSC. + (ccid_transceive): Use negotiated IFSC and support S(IFS) command. + +2009-03-26 Werner Koch <wk@g10code.com> + + * command.c (cmd_pksign): Allow more hash algorithms. + + * scdaemon.h (MAX_DIGEST_LEN): Change to 64. + + * apdu.c (open_ccid_reader): Clear the is_to flag. + + * app-nks.c (filelist): Add field KID. + (do_getattr): Change standard authentication key. + (do_sign): Setup a security environment for TCOS 3 cards and support + all SHA-2 algorithms. + +2009-03-24 Werner Koch <wk@g10code.com> + + * command.c (struct server_local_s): Add flag + APP_CTX_MARKED_FOR_RELEASE. + (do_reset): Set the flag. + (open_card): Act on this flag. + * app-common.h (struct app_ctx_s): Add flag NO_REUSE. + (application_notify_card_reset): Set the flag. + * app.c (select_application, release_application): Take care of + that flag. + +2009-03-20 Werner Koch <wk@g10code.com> + + * app-nks.c (keygripstr_from_pk_file): Fix for TCOS 3 cards. + +2009-03-18 Werner Koch <wk@g10code.com> + + * apdu.c (open_pcsc_reader_wrapped): Use close_all_fds. + + * command.c (cmd_learn): Add option --keypairinfo. + * app.c (app_write_learn_status): Add arg FLAGS. + * app-common.h (struct app_ctx_s): Add arg FLAGS to LEARN_STATUS. + Change all implementors. + * app-p15.c (do_learn_status): Take care of flag bit 0. + * app-nks.c (do_learn_status, do_learn_status_core): Ditto. + +2009-03-10 Werner Koch <wk@g10code.com> + + * app-openpgp.c (send_key_attr): New. + (do_getattr): New attribute KEY_ATTR. + * command.c (send_status_direct): New. + +2009-03-06 Werner Koch <wk@g10code.com> + + * app-nks.c (do_learn_status): Factor code out to.. + (do_learn_status_core): .. new. + (do_readcert, do_sign, do_decipher): Switch to SigG if needed. + (verify_pin): Use DESC also for keypad based verify. + +2009-03-05 Werner Koch <wk@g10code.com> + + * app-openpgp.c (verify_a_chv): Remove special case for keypads. + (verify_chv3): Ditto. + + * app-nks.c (get_chv_status): New. + (parse_pwidstr): New. + (verify_pin): Add args PWID and DESC and use them. Remove the + CHV1 caching. + (do_change_pin): Allow PIN selection and add reset mode. + (do_learn_status): Use NKS-NKS3 tag for TCOS 3 cards. + (do_readcert, do_sign): Allow NKS-NKS3 tag. + +2009-03-04 Werner Koch <wk@g10code.com> + + * app-nks.c (do_getattr): New. + (app_select_nks): Register it. + (verify_pin): Factor some code out to... + (basic_pin_checks): New. + (do_change_pin): Call the basic check. + (app_select_nks): Move AID to .. + (aid_nks): .. new. + (aid_sigg): New. + (switch_application): New. + (do_getattr, do_learn_status, do_readcert, do_sign, do_decipher) + (do_change_pin, do_check_pin): Make sure we are in NKS mode. + +2009-03-03 Werner Koch <wk@g10code.com> + + * command.c (scd_command_handler): Remove dereference of STOPME + after free. + +2009-02-27 Werner Koch <wk@g10code.com> + + * app.c (get_supported_applications): New. + * command.c (cmd_getinfo): New subcommand "app_list" + (cmd_killscd): New. + (register_commands): Register command KILLSCD. + (struct server_local_s): Add field STOPME. + (scd_command_handler): Act upon this. + +2009-02-25 Werner Koch <wk@g10code.com> + + * apdu.c (apdu_get_status): Factor all code out to ... + (apdu_private_get_status): .. new. Add arg NO_ATR_RESET. + (apdu_connect): Call new function. + + * scdaemon.c: New option --debug-log-tid. + (tid_log_callback): New. + (main): Move debug-wait code after debug stream init. + +2009-02-24 Werner Koch <wk@g10code.com> + + * ccid-driver.c (ccid_get_atr): Move debug output to .. + (print_r2p_parameters): .. new. + (print_r2p_header, print_pr_data, print_r2p_unknown) + (print_r2p_datablock, print_r2p_slotstatus, print_r2p_escape) + (print_r2p_datarate): New. + (bulk_in): Call parameter printing. + (ccid_set_debug_level): Add debug level 3. + (convert_le_u16): New. + (print_p2r_header, print_p2r_iccpoweron, print_p2r_iccpoweroff) + (print_p2r_getslotstatus, print_p2r_xfrblock) + (print_p2r_getparameters, print_p2r_resetparameters) + (print_p2r_setparameters, print_p2r_escape, print_p2r_iccclock) + (print_p2r_to0apdu, print_p2r_secure, print_p2r_mechanical) + (print_p2r_abort, print_p2r_setdatarate, print_r2p_unknown): New. + (bulk_out): Add arg NO_DEBUG and change all callers to pass 0. + Call parameter printing. + (ccid_slot_status): Call with NO_DEBUG set. + (abort_cmd, send_escape_cmd, ccid_get_atr, ccid_get_atr) + (ccid_transceive_apdu_level, ccid_transceive) + (ccid_transceive_secure): Remove old debug print code. + +2009-02-12 Werner Koch <wk@g10code.com> + + * command.c (cmd_getinfo): Add new subcommand "deny_admin". + +2009-01-28 Werner Koch <wk@g10code.com> + + * scdaemon.c (main): Make --allow-admin the default and make the + option a dummy. + +2009-01-27 Werner Koch <wk@g10code.com> + + * app-geldkarte.c: Changed to use an AID. + + * app.c (app_munge_serialno): Add case for no serialno. + (app_get_serial_and_stamp): Ditto. + +2009-01-26 Werner Koch <wk@g10code.com> + + * app-geldkarte.c: New. + * Makefile.am (card_apps): Add new file. + * app.c (select_application): Test for geldkarte. + +2009-01-12 Werner Koch <wk@g10code.com> + + * command.c (send_client_notifications) [HAVE_W32_SYSTEM]: Fix + brackets. + +2009-01-08 Werner Koch <wk@g10code.com> + + * iso7816.c (iso7816_read_record, iso7816_read_binary): Pass 0 for + L_e because the problem with the CCID driver has gone. + (iso7816_apdu_direct): New. + + * app-nks.c (filelist): Add NKS_VER field. Add NKS 3 specific + entries. + (app_local_s, do_deinit): New. + (get_nks_version): New. + (app_select_nks): Setup local data. + (keygripstr_from_pk_file): Replace SLOT by APP and take care of + NKS version > 2. + (do_learn_status): Take care of NKS version. + +2009-01-05 Werner Koch <wk@g10code.com> + + * apdu.c (apdu_get_status): Save the last status. + +2008-12-18 Werner Koch <wk@g10code.com> + + * ccid-driver.c (abort_cmd): New. + (bulk_in): Call abort_cmd after severe errors. + + * apdu.c (reader_table_s): Add field ANY_STATUS. + (new_reader_slot): Clear it. + (apdu_get_status): Use ANY_STATUS to update the change counter. + Remove the use of the flag bit from LAST_STATUS everywhere. + * command.c (update_reader_status_file): Factor code out to ... + (send_client_notifications): New. Track signals already sent. + (update_reader_status_file): Shutdown the reader after a failed + apdu_get_status. + +2008-12-09 Werner Koch <wk@g10code.com> + + * scdaemon.c (main): Call i18n_init before init_common_subsystems. + +2008-12-08 Werner Koch <wk@g10code.com> + + * scdaemon.c (handle_connections): Sync ticker to the next full + interval. + (TIMERTICK_INTERVAL_USEC): Change to 500ms. + +2008-12-05 Werner Koch <wk@g10code.com> + + * app-openpgp.c (app_local_s): Add field ALGO_ATTR_CHANGE. + (app_select_openpgp): Parse new capability. + (show_caps): Show new capability. + +2008-12-03 Werner Koch <wk@g10code.com> + + * scdaemon.c (opts): Use ARGPARSE_ macros. Add option + --card-timeout. + * command.c (update_reader_status_file): Implement it. + +2008-11-18 Werner Koch <wk@g10code.com> + + * scdaemon.c (make_libversion): New. + (my_strusage): Print libgcrypt and libksba version. + +2008-11-03 Werner Koch <wk@g10code.com> + + * command.c (server_local_s): Add field DISCONNECT_ALLOWED. + (cmd_disconnect): Implement command. + (open_card): Reset disconnect flag. + (update_reader_status_file): Disconnect if allowed. + + * app-common.h (app_ctx_s): Remove INITIALIZED. Make REF_COUNT + unsigned. + * app.c (select_application): Remove INITIALIZED. + (app_write_learn_status, app_readcert, app_readkey, app_getattr) + (app_setattr, app_sign, app_decipher, app_writecert) + (app_writekey, app_get_challenge, app_change_pin, app_check_pin): + Replace INITIALIZED by REF_COUNT check. + (application_notify_card_removed): Rename to .. + (application_notify_card_reset): .. this. Change all callers. + * command.c (do_reset): Call application_notify_card_reset after + sending a reset. + (update_reader_status_file): Add arg SET_CARD_REMOVED. + (scd_update_reader_status_file): Pass true for new flag. + (do_reset): Pass false for new flag. + + * app.c (app_get_serial_and_stamp): Use bin2hex. + * app-help.c (app_help_get_keygrip_string): Ditto. + * app-p15.c (send_certinfo, send_keypairinfo, do_getattr): Ditto. + * app-openpgp.c (send_fpr_if_not_null, send_key_data) + (retrieve_fpr_from_card, send_keypair_info): Ditto. + * app-nks.c (keygripstr_from_pk_file): Ditto. + * command.c (cmd_apdu): Ditto. + +2008-10-21 Marcus Brinkmann <marcus@g10code.com> + + * command.c (open_card): If connect error is SW_HOST_NO_CARD, + return a more descriptive error. + +2008-10-20 Werner Koch <wk@g10code.com> + + * pcsc-wrapper.c (read_32): Use provided arg and not stdin. Is + called with stdin, though. + (handle_close): Mark unused arg. + (handle_status, handle_reset): Ditto. + + * ccid-driver.c (ccid_check_card_presence): Mark not yet used arg. + + * scdaemon.c (scd_deinit_default_ctrl): Mark unused arg. + * command.c (cmd_unlock, cmd_restart, cmd_disconnect): Ditto. + * apdu.c (ct_get_status): Ditto. + (ct_send_apdu, pcsc_send_apdu_wrapped) + (apdu_open_remote_reader): Ditto. + * app.c (select_application): Ditto. + * app-openpgp.c (do_writecert, do_change_pin, do_writekey): Ditto. + * app-nks.c (do_change_pin, do_check_pin): Ditto. + +2008-10-16 Werner Koch <wk@g10code.com> + + * command.c (cmd_disconnect): New dummy command. + (register_commands): Register command. + +2008-10-15 Werner Koch <wk@g10code.com> + + * command.c (scd_command_handler): Return true if there is no more + active session. + * scdaemon.c (start_connection_thread): Set shutdown flag if + requested by command handler. + (main): Make PIPE_SERVER module global. + (handle_connections): Disable listen_fd if a shutdown is pending. + +2008-10-14 Werner Koch <wk@g10code.com> + + * apdu.c (reader_table_s): Add fields connect_card and + disconnect_card. + (new_reader_slot): Set them to NULL. + (apdu_connect, apdu_disconnect): New. + (apdu_close_reader, apdu_shutdown_reader): Call apdu_disconnect. + (connect_pcsc_card, disconnect_pcsc_card): new. + (reset_pcsc_reader_direct): Implement in terms of + disconnect_pcsc_card and connect_pcsc_card. + (apdu_get_atr): Return NULL if there is no ATR. + * sc-copykeys.c (main): Add call to apdu_connect. + * command.c (open_card): Ditto. + + * apdu.h (SW_HOST_ALREADY_CONNECTED): New. + (APDU_CARD_USABLE, APDU_CARD_PRESENT, APDU_CARD_ACTIVE): New. + * apdu.c: Replace constants by the new macros. + (open_pcsc_reader): Factor code out to ... + (open_pcsc_reader_direct, open_pcsc_reader_wrapped): New. + (reset_pcsc_reader): Factor code out to ... + (reset_pcsc_reader_direct, reset_pcsc_reader_wrapped): New. + (pcsc_get_status): Factor code out to ... + (pcsc_get_status_direct, pcsc_get_status_wrapped): New. + (pcsc_send_apdu): Factor code out to ... + (pcsc_send_apdu_direct, pcsc_send_apdu_wrapped): New. + (close_pcsc_reader): Factor code out to ... + (close_pcsc_reader_direct, close_pcsc_reader_wrapped): New. + + * command.c (update_reader_status_file): Open the reader if not + yet done. + + * scdaemon.c (TIMERTICK_INTERVAL_SEC, TIMERTICK_INTERVAL_USEC): + New to replace TIMERTICK_INTERVAL. Chnage from 2s (4 under W32) + to 250ms. + +2008-10-13 Werner Koch <wk@g10code.com> + + * command.c (option_handler) [W32]: Use strtoul with base 16. + (update_reader_status_file) [W32]: Set Event. + (scd_command_handler): Use INT2FD to silent warning. + +2008-09-29 Werner Koch <wk@g10code.com> + + * scdaemon.h (GCRY_MD_USER): Rename to GCRY_MODULE_ID_USER. + (GCRY_MD_USER_TLS_MD5SHA1): Rename to MD_USER_TLS_MD5SHA1 and + change all users. + +2008-09-28 Marcus Brinkmann <marcus@g10code.com> + + * apdu.c (pcsc_get_status): Fix last change. + +2008-09-25 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_setattr): Do not allow setting of the reset + code. + (do_change_pin): Allow setting of the reset code. + +2008-09-24 Werner Koch <wk@g10code.com> + + * app-openpgp.c (verify_chv3): Set the did_chv3 flag which was + accidently removed on 2008-03-26. + (verify_chv2): Revert last change. + (do_change_pin): Do not change CHV2. Add reset code logic for v2 + cards. + * iso7816.c (iso7816_reset_retry_counter_with_rc): New. + + * app-openpgp.c (add_tlv, build_privkey_template): New. + (do_writekey): Support v2 keys and other key lengths than 1024. + * iso7816.c (iso7816_put_data_odd): New. + +2008-09-23 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_sign): Support SHA-2 digests. + (verify_chv2): No CHV auto-sync for v2 cards. + (do_auth): Allow 2048 bit keys. + (parse_algorithm_attribute): New. + (rsa_key_format_t): New. + (struct app_local_s): Add struct KEYATTR. + +2008-09-23 Marcus Brinkmann <marcus@g10code.com> + + * apdu.c (pcsc_get_status): Be more relaxed with the usable flag + under Windows. + +2008-09-23 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_setattr): Use command chaining for long + values. + * iso7816.c (iso7816_put_data): Add arg EXTENDED_MODE. Change all + callers. + * apdu.c (apdu_send_simple): Add arg EXTENDED_MODE. Change all + callers. + (send_le): Implement command chaining. + * ccid-driver.c (ccid_transceive_apdu_level): Increase allowed + APDU size. + (ccid_transceive): Alow for APDUS of up to 259 bytes. + * apdu.h: Add new SW_ codes. + +2008-09-16 Werner Koch <wk@g10code.com> + + * command.c (cmd_writecert): New. + (register_commands): Register it. + * app-common.h (app_ctx_s): Add member WRITECERT. + * app.c (app_writecert): New. + * app-openpgp.c (do_writecert): New. + (parse_historical): New. + (show_extcap): New. + (dump_all_do): Print only the length of longs DOs. + * command.c (cmd_writekey, cmd_apdu, cmd_pksign) + (cmd_passwd): Replace open coding by skip_options. + +2008-08-30 Moritz <moritz@gnu.org> + + * scdaemon.c (main): Use estream_asprintf instead of asprintf. + * command.c (update_reader_status_file): Likewise. + (cmd_serialno): Use estream_asprintf instead of asprintf + and xfree instead of free to release memory allocated + through (estream_)asprintf. + (cmd_learn): Likewise. + (pin_cb): Likewise. + * app-openpgp.c (get_public_key): Likewise. + +2008-08-18 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_setattr): Fix test for v2 cards. + +2008-08-11 Werner Koch <wk@g10code.com> + + * apdu.c (reset_pcsc_reader, open_pcsc_reader) + (reset_rapdu_reader, open_rapdu_reader): Allow ATRs of up to 33 + bytes. Provide maximum size of ATR buffer using DIM. Such long + ATR are never seen in reality but the PC/SC library of MAC OS X is + just too buggy. Reported by Ludovic Rousseau. Fixes bug #948. + +2008-07-30 Werner Koch <wk@g10code.com> + + * app-openpgp.c (verify_a_chv): Use xtrymalloc and make the prompt + for CHV2 more user friendly. + +2008-07-03 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_readcert): New. + (app_local_s): Add fields IS_V2 and MAX_CERTLEN_3. + (app_select_openpgp): Set them and register do_readcert. + (do_setattr): Allow storing of the certificate. + +2008-06-25 Werner Koch <wk@g10code.com> + + * app-dinsig.c (do_sign): Allow for SHA256. + +2008-06-24 Werner Koch <wk@g10code.com> + + * app-common.h (app_ctx_s): Renamed reset_mode parameter of + change_pin to mode_Flags and make it an unsigned int. + (APP_CHANGE_FLAG_RESET, APP_CHANGE_FLAG_NULLPIN): New. + * app-openpgp.c (do_change_pin): Adjust for that. + + * command.c (cmd_passwd): Add option --nullpin. + * app-nks.c (do_check_pin, do_change_pin): New. + (app_select_nks): Register new functions. + +2008-04-21 Moritz Schulte <mo@g10code.com> (wk) + + * app-openpgp.c (verify_a_chv): Make use of the default CHV flag. + +2008-03-26 Werner Koch <wk@g10code.com> + + * app-openpgp.c (verify_chv3): Support the keypad. + +2008-02-09 Marcus Brinkmann <marcus@g10code.de> + + * scdaemon.c (main): Use CONFIG_FILENAME as filename if it is set + in gpgconf-list output. + +2007-12-10 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_decipher): Take care of cryptograms shorter + that 128 bytes. Fixes bug#851. + +2007-11-14 Werner Koch <wk@g10code.com> + + * scdaemon.c (main): Pass STANDARD_SOCKET flag to + create_server_socket. + +2007-11-13 Werner Koch <wk@g10code.com> + + * scdaemon.c (start_connection_thread): Do not call + assuan_sock_check_nonce if we are running in --server mode. + +2007-11-07 Werner Koch <wk@g10code.com> + + * scdaemon.h: Remove errors.h. + +2007-10-02 Werner Koch <wk@g10code.com> + + * command.c (cmd_getinfo): Add "pid" subcommand. + +2007-10-01 Werner Koch <wk@g10code.com> + + * scdaemon.c (create_server_socket): Use Assuan socket wrappers + and remove Windows specific code. + (socket_nonce): New. + (start_connection_thread): Check nonce. + +2007-09-14 Marcus Brinkmann <marcus@g10code.de> + + * scdaemon.c (main): New variable STANDARD_SOCKET, which is 1 for + W32 targets. Use it for create_socket_name. + +2007-08-07 Werner Koch <wk@g10code.com> + + * tlv.c, tlv.h: Move to ../common/. + +2007-08-02 Werner Koch <wk@g10code.com> + + * scdaemon.c: Include gc-opt-flags.h and remove their definition + here. + +2007-08-01 Werner Koch <wk@g10code.com> + + * apdu.c (send_le): Implement exact length hack. Suggested by + Sten Lindgren. + +2007-07-05 Werner Koch <wk@g10code.com> + + * command.c (has_option_name, skip_options): New. + (cmd_genkey): Add option --timestamp. + (cmd_writekey): Enter confidential mode while inquiring the key data. + + * app.c (app_genkey): Add arg CREATETIME. + * app-common.h (app_ctx_s): Likewise + * app-openpgp.c (do_genkey): Ditto. Use it. + + +2007-07-04 Werner Koch <wk@g10code.com> + + * command.c (cmd_getinfo): New subcommand "version". + + * scdaemon.c (TIMERTICK_INTERVAL): New. + (handle_connections) [W32]: Enable a dummy sigs event. + (handle_connections): Use a proper count for select and not + FD_SETSIZE. + (fixed_gcry_pth_init, main): Kludge to fix pth initialization. + +2007-06-21 Werner Koch <wk@g10code.com> + + * scdaemon.h (ctrl_t): Remove. It is now declared in ../common/util.h. + +2007-06-18 Marcus Brinkmann <marcus@g10code.de> + + * scdaemon.c (main): Percent escape output of --gpgconf-list. + +2007-06-12 Werner Koch <wk@g10code.com> + + * scdaemon.c (main): Replace some calls by init_common_subsystems. + +2007-06-11 Werner Koch <wk@g10code.com> + + * Makefile.am (scdaemon_LDADD): Use libcommonpth macro. + + * command.c (initialize_module_command): New. + * scdaemon.c (main) [W32]: Do not use sigpipe code. + (main): Call initialize_module_command. + +2007-06-06 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_sign): Fix arithmetic on void*. + + * app.c (dump_mutex_state) [W32]: Handle the W32Pth case. + + * apdu.c: Remove dynload.h. + + * scdaemon.c (i18n_init): Remove. + +2007-04-20 Werner Koch <wk@g10code.com> + + * sc-copykeys.c (my_gcry_logger): Removed. + (main): Call setup_libgcrypt_logging helper. + * scdaemon.c (my_gcry_logger): Removed. + (main): Call setup_libgcrypt_logging helper. + +2007-04-03 Werner Koch <wk@g10code.com> + + * command.c (cmd_getinfo): New subcommand "reader_list". + * ccid-driver.c (scan_or_find_devices): Ignore EBUSY in scan mode + for special transports. + +2007-03-07 Werner Koch <wk@g10code.com> + + * app-dinsig.c: Include i18n.h. + (verify_pin): Support PIN pads. + * app-nks.c (verify_pin): Ditto. + + * ccid-driver.c (bulk_in): Handle time extension before checking + the message type. + (ccid_transceive_secure): Support the Cherry XX44 keyboard. + Kudos to the nice folks at Cherry for helping with that. + +2007-02-18 Werner Koch <wk@g10code.com> + + * scdaemon.c (DEFAULT_PCSC_DRIVER): Add a default for OS X. + +2007-01-25 Werner Koch <wk@g10code.com> + + * Makefile.am (scdaemon_LDADD): Added LIBICONV. Noted by Billy + Halsey. + +2006-12-21 Werner Koch <wk@g10code.com> + + * app-openpgp.c (verify_chv2): Factored most code out into... + (verify_a_chv): ... new. + (do_sign): Factored verification code out to new function and + take care of a keypad entered PIN. + (compare_fingerprint): Print an additional diagnostic. + +2006-11-28 Werner Koch <wk@g10code.com> + + * apdu.c (send_le, apdu_send_direct): Increase RESULTLEN to 258 to + allow for full 256 byte and the status word. This might break + some old PC/SC drivers or cards, but we will see. Suggested by + Kenneth Wang. + +2006-11-23 Werner Koch <wk@g10code.com> + + * command.c (scd_command_handler): Fixed use of CTRL. + +2006-11-21 Werner Koch <wk@g10code.com> + + * Makefile.am (libexec_PROGRAMS): Put pscs-wrapper into libexec. + Renamed to gnupg-pcsc-wrapper. + * apdu.c (open_pcsc_reader): Use GNUPG_LIBEXECDIR to accces the + wrapper. Suggested by Eric Dorland. + +2006-11-20 Werner Koch <wk@g10code.com> + + * app-openpgp.c (verify_chv2): Support for keypads (only CHV2). + + * ccid-driver.c (ccid_transceive_secure): Made it work for Kaan + and SCM. + +2006-11-17 Werner Koch <wk@g10code.com> + + * ccid-driver.c (scan_or_find_devices): Use DEBUGOUT_2 instead of + log_debug. Removed few other log_debug. + + * iso7816.c (iso7816_check_keypad): Allow for a SW of 0. + + * command.c (pin_cb): New mode to prompt for a keypad entry. + + * scdaemon.c (main) <gpgconf-list>: Add disable-keypad. + +2006-11-15 Werner Koch <wk@g10code.com> + + * app-p15.c (read_ef_odf): Cast one printf arg. + + * scdaemon.h (struct server_control_s): Add field THREAD_STARTUP. + * command.c (scd_command_handler): Add new arg CTRL. + * scdaemon.c (scd_init_default_ctrl): Made static. + (scd_deinit_default_ctrl): New. + (start_connection_thread): Call init/deinit of ctrl. + (handle_connections): Allocate CTRL. + + * apdu.c (PCSC_ERR_MASK): New. + (reset_pcsc_reader, pcsc_get_status, pcsc_send_apdu) + (close_pcsc_reader, open_pcsc_reader): Use it after shifting error + values. Reported by Henrik Nordstrom. Fixes bug #724. + +2006-10-24 Werner Koch <wk@g10code.com> + + * scdaemon.h (GCRY_MD_USER_TLS_MD5SHA1): New. + (MAX_DIGEST_LEN): Increased to 36. + * app-p15.c (do_sign): Support for TLS_MD5SHA1. + (do_auth): Detect TLS_MD5SHA1. + (do_sign): Tweaks for that digest. + +2006-10-23 Werner Koch <wk@g10code.com> + + * scdaemon.c (main): New command --gpgconf-test. + +2006-10-17 Werner Koch <wk@g10code.com> + + * Makefile.am (scdaemon_LDADD): Link against libcommonpth. + +2006-10-12 Werner Koch <wk@g10code.com> + + * apdu.c: Include pth.h after unistd.h for the sake of newer Pth + versions. + +2006-10-11 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_sign): Redirect to do_auth for OpenPGP.3. + +2006-10-06 Werner Koch <wk@g10code.com> + + * Makefile.am (AM_CFLAGS): Use PTH version of libassuan. + (scdaemon_LDADD): Ditto. + + * scdaemon.h (send_status_info): Mark with sentinel attribute. + +2006-10-02 Marcus Brinkmann <marcus@g10code.de> + + * command.c (update_reader_status_file): Increase buffer of + NUMBUF2 (fixing typo). + +2006-09-24 Marcus Brinkmann <marcus@g10code.de> + + * app-openpgp.c (do_sign): Advance INDATA by the SHA1 resp. RMD160 + prefix length. + +2006-09-14 Werner Koch <wk@g10code.com> + + Replaced all call gpg_error_from_errno(errno) by + gpg_error_from_syserror(). + + * command.c (scd_command_handler): Replaced + init_connected_socket_server by init_socket_server_ext. + +2006-09-07 Werner Koch <wk@g10code.com> + + * command.c (update_reader_status_file): Execute an event handler + if available. + +2006-09-06 Werner Koch <wk@g10code.com> + + * apdu.c (pcsc_end_transaction): + * pcsc-wrapper.c (pcsc_end_transaction: Fixed dclaration. + Reported by Bob Dunlop. + + * scdaemon.h (CTRL,APP): Removed and changed everywhere to + ctrl_t/app_t. + + Replaced all Assuan error codes by libgpg-error codes. Removed + all map_to_assuan_status and map_assuan_err. + + * scdaemon.c (main): Call assuan_set_assuan_err_source to have Assuan + switch to gpg-error codes. + * command.c (set_error): Adjusted. + +2006-09-02 Marcus Brinkmann <marcus@g10code.de> + + * command.c (get_reader_slot): Return the slot_table index, not + the APDU slot number. + (update_reader_status_file): Use the slot_table index in the + update_card_removed invocation. + +2006-09-01 Marcus Brinkmann <marcus@g10code.de> + + * command.c (cmd_getinfo): Handle status command. + +2006-08-30 Marcus Brinkmann <marcus@g10code.de> + + * command.c (do_reset): Delay resetting CTRL->reader_slot until + after update_card_removed invocation. + +2006-08-28 Marcus Brinkmann <marcus@g10code.de> + + * app-openpgp.c (do_decipher, do_sign): Allow "OPENPGP.2" + resp. "OPENPGP.1" for KEYIDSTR. + +2006-08-21 Werner Koch <wk@g10code.com> + + * pcsc-wrapper.c (handle_open, handle_close): Reset card and + protocol on error/close. + (handle_status): Don't set the state if the state is unknown. + (handle_reset): Ignore an error if already disconnected. May + happen due to system wake-up after hibernation. Suggested by Bob + Dunlop. + +2006-06-28 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_writekey): Fixed computation of memmove + length. This led to garbled keys if E was larger than one byte. + Thanks to Achim Pietig for hinting at the garbled E. + +2006-06-09 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (scdaemon_LDADD): Add $(NETLIBS). + +2006-04-14 Marcus Brinkmann <marcus@g10code.de> + + * app.c (select_application): Cover up a slot mismatch error in + case it happens (it shouldn't happen). + (release_application): Use APP->slot. Lock the reader. + (application_notify_card_removed): Lock the reader. + +2006-04-11 Werner Koch <wk@g10code.com> + + * command.c (hex_to_buffer): New. + (cmd_apdu): New. + +2006-04-03 Werner Koch <wk@g10code.com> + + * scdaemon.c [__GLIBC__]: Default to libpcsclite.so.1. + +2006-03-21 Werner Koch <wk@g10code.com> + + * command.c (cmd_pksign): Add --hash option. + +2006-03-01 Werner Koch <wk@g10code.com> + + * command.c (status_file_update_lock): New. + (scd_update_reader_status_file): Use lock and factor existing code + out to .. + (update_reader_status_file): .. this. + (do_reset): Use the lock and call update_reader_status_file. + +2006-02-20 Werner Koch <wk@g10code.com> + + * apdu.c (open_pcsc_reader): Fixed double free. Thanks to Moritz. + +2006-02-09 Werner Koch <wk@g10code.com> + + * command.c (get_reader_slot, do_reset) + (scd_update_reader_status_file): Rewrote. + + * app.c (release_application): Factored code out to .. + (deallocate_app): new function. + (select_application): Introduce new saved application stuff. + (application_notify_card_removed): New. + * command.c (update_card_removed): Call it here. + (do_reset): And here. + + * app.c (check_application_conflict): New. + * command.c (open_card): Use it here. + (cmd_restart): New command. + + * command.c (cmd_lock): Fixed --wait option to actually terminate. + +2006-02-08 Werner Koch <wk@g10code.com> + + * ccid-driver.c (ccid_get_atr): Read Parameter and select T=1 + using these parameters. + (scan_or_find_devices): Check for NULL r_fd. + +2006-02-02 Werner Koch <wk@g10code.com> + + * ccid-driver.c (special_transport): New + (ccid_open_reader, do_close_reader, ccid_shutdown_reader) + (bulk_out, bulk_in): Add support for CardMan 4040 reader. + + * ccid-driver.c (scan_or_find_devices): Factored most code out to + (scan_or_find_usb_device): .. new. + (make_reader_id): Fixed vendor mask. + +2006-01-01 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_sign): Give user error if hash algorithm is + not supported by the card. + +2005-12-06 Werner Koch <wk@g10code.com> + + * apdu.c (open_pcsc_reader): Check that pcsc-wrapper is actually + installed. + +2005-11-23 Werner Koch <wk@g10code.com> + + * app-nks.c (verify_pin): Give a special error message for a Nullpin. + +2005-10-29 Werner Koch <wk@g10code.com> + + * ccid-driver.c (send_escape_cmd): New args RESULT, RESULTLEN and + RESULTMAX. Changed all callers. + (ccid_transceive_escape): New. + +2005-10-27 Werner Koch <wk@g10code.com> + + * apdu.c [__CYGWIN__]: Make cygwin environment similar to _WIN32. + Suggested by John P. Clizbe. + * scdaemon.c [__CYGWIN__]: Set default PC/SC driver to winscard.dll. + +2005-10-19 Werner Koch <wk@g10code.com> + + * ccid-driver.h (CCID_DRIVER_ERR_NO_KEYPAD): New. + * apdu.h (SW_HOST_NO_KEYPAD): New. + * iso7816.h (struct iso7816_pininfo_s): New. + * iso7816.c (map_sw): Support new code. + (iso7816_check_keypad): New. + (iso7816_verify_kp, iso7816_change_reference_data_kp) + (iso7816_reset_retry_counter_kp): New. Extended versions of the + original functions. + * apdu.c (host_sw_string): Support new code. + (reader_table_s): New field CHECK_KEYPAD. + (new_reader_slot, open_ct_reader, open_pcsc_reader) + (open_ccid_reader, open_rapdu_reader): Initialize it. + (check_ccid_keypad): New. + (apdu_check_keypad): New. + (apdu_send_le): Factored all code out to ... + (send_le): .. new. Takes an additional arg; changed all callers + of the orginal function to use this one with a NULL for the new + arg. + (apdu_send_simple_kp): New. + (ct_send_apdu, pcsc_send_apdu, my_rapdu_send_apdu) + (send_apdu_ccid): New arg PININFO. + (send_apdu_ccid): Use the new arg. + + * scdaemon.c: New option --disable-keypad. + +2005-10-08 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (scdaemon_LDADD): Add ../gl/libgnu.a after + ../common/libcommon.a. + +2005-09-20 Werner Koch <wk@g10code.com> + + * app-dinsig.c (verify_pin): Try ISO 9564 BCD encoding. + + * iso7816.c (iso7816_select_application): Add arg FLAGS. Changed + all callers to pass 0. + * app-openpgp.c (app_select_openpgp): But this one requires a + special flag. + + * app-p15.c (app_select_p15): Don't use select application for the + BELPIC. + +2005-09-09 Werner Koch <wk@g10code.com> + + * pcsc-wrapper.c (main): Removed bogus free. + + * app-p15.c (do_auth): New. + (do_getattr): New attribs $AUTHKEYID and $DISPSERIALNO. + * app-openpgp.c (do_getattr): Ditto. + +2005-09-08 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_getattr): New key $AUTHKEYID. + +2005-09-06 Werner Koch <wk@g10code.com> + + * app-p15.c (do_sign): Tweaked for BELPIC cards. + (read_home_df): New arg R_BELPIC. + (app_select_p15): Set card type for BELPIC. + +2005-09-05 Werner Koch <wk@g10code.com> + + * iso7816.c (iso7816_select_path): New. + * app-p15.c (select_ef_by_path): Allow for direct path selection. + (app_select_p15): Try using the Belgian variant of pkcs#15. + (read_home_df): New. + (read_ef_odf): Generalized. + (read_ef_tokeninfo): New. + (read_p15_info): Set serialnumber from TokenInfo. + (app_select_p15): Don't munge serialNumber - that must be done + only once. + + * iso7816.c (iso7816_read_binary): Use Le=0 when reading all + data. Handle 6C00 error and take 6B00 as indication for EOF. + * apdu.h (SW_EXACT_LENGTH_P): New. + * apdu.c (new_reader_slot, reset_pcsc_reader, pcsc_get_status) + (open_pcsc_reader): Set new reader state IS_T0. + (apdu_send_le): When doing T=0 make sure not to send Lc and Le. + Problem reported by Carl Meijer. + (apdu_send_direct): Initialize RESULTLEN. + * pcsc-wrapper.c (handle_status): Return the current protocol as + a new third word. + +2005-08-05 Werner Koch <wk@g10code.com> + + * apdu.c (open_rapdu_reader): Set the reader number. + +2005-07-05 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_readkey): Return a mallcoed copy of the key as + required by the description. Thanks to Moritz for tracking this + problem down. + +2005-06-21 Werner Koch <wk@g10code.com> + + * scdaemon.c (main): ifdef call to ccid_set_debug_level. + + * apdu.c (reset_pcsc_reader, open_pcsc_reader): Cast size_t to + ulong for printf. + +2005-06-06 Werner Koch <wk@g10code.com> + + * scdaemon.c (main): New option --debug-allow-core-dump. + +2005-06-03 Werner Koch <wk@g10code.com> + + * scdaemon.c (handle_connections): Make sure that the signals we + are handling are not blocked.Block signals while creating new + threads. + (handle_connections): Include the file descriptor into the name of + the thread. + +2005-06-02 Werner Koch <wk@g10code.com> + + * app.c (app_dump_state, dump_mutex_state): New. + * scdaemon.c (handle_signal): Print it on SIGUSR1. + + * app-openpgp.c (do_writekey): Typo fix. + + * command.c (open_card): Check for locked state even if an + application context is available. + + * app-common.h: Add REF_COUNT field. + * app.c (release_application, select_application): Implement + reference counting to share the context beween connections. + + * app.c (lock_reader, unlock_reader): Take SLOT instead of APP as + argument. Changed all callers. + (select_application): Unlock the reader on error. This should fix + the hangs I noticed last week. + + * scdaemon.h: Removed card_ctx_t cruft. + +2005-06-01 Werner Koch <wk@g10code.com> + + * scdaemon.c: Include mkdtemp.h. + +2005-05-31 Werner Koch <wk@g10code.com> + + * tlv.c [GNUPG_MAJOR_VERSION==1]: Define constants instead of + including a gnupg 1.4 header. + +2005-05-30 Werner Koch <wk@g10code.com> + + * tlv.c: Add hack to compile without gpg-error.h when used with + GnuPG 1.4. + +2005-05-23 Werner Koch <wk@g10code.com> + + * Makefile.am: Do not build sc-copykeys anymore. + + * app-openpgp.c (app_openpgp_storekey, app_openpgp_readkey) + (app_openpgp_cardinfo): Removed. + + * ccid-driver.c (parse_ccid_descriptor): SCR335 FW version 5.14 is + good. + (do_close_reader): Never do a reset. The caller should instead + make sure that the reader has been closed properly. The new retry + code in ccid_slot_status will make sure that the readersatrts up + fine even if the last process didn't closed the USB connection + properly. + (ccid_get_atr): For certain readers try switching to ISO mode. + Thanks to Ludovic Rousseau for this hint and the magic numbers. + (print_command_failed): New. + (bulk_in): Use it here. Add new arg NO_DEBUG. + (ccid_slot_status): Disabled debugging. + +2005-05-21 Werner Koch <wk@g10code.com> + + * scdaemon.c (handle_signal): Print thread info on SIGUSR1. + +2005-05-20 Werner Koch <wk@g10code.com> + + * ccid-driver.c: Replaced macro DEBUG_T1 by a new debug level. + (parse_ccid_descriptor): Mark SCR335 firmware version 5.18 good. + (ccid_transceive): Arghhh. The seqno is another bit in the + R-block than in the I block, this was wrong at one place. + + * scdaemon.c: New options --debug-ccid-driver and + --debug-disable-ticker. + + * app-openpgp.c (do_genkey, do_writekey): Factored code to check + for existing key out into .. + (does_key_exist): .. New function. + +2005-05-19 Werner Koch <wk@g10code.com> + + * tlv.c (parse_sexp): New. + + * command.c (cmd_writekey): New. + * app.c (app_writekey): New. + * app-common.c (app_t): Add function ptr WRITEKEY. + * app-openpgp.c (do_writekey): New. + + * app-openpgp.c (do_readkey) [GNUPG_MAJOR_VERSION==1]: Return error. + * app-common.h (app_t) [GNUPG_MAJOR_VERSION==1]: Add a field to + store the Assuan context. + +2005-05-17 Werner Koch <wk@g10code.com> + + * scdaemon.c: Removed non-pth code paths. + (create_socket_name, create_server_socket): New. Taken from + ../agent/gpg-agent. + (cleanup): Changed to adjust for SOCKET_NAME now being malloced. + (ticker_thread): Always use pth_event_occurred; it is again + defined for all decent PTH versions. + (handle_connections): New. Based on the gpg-agent code. + (start_connection_thread): Ditto. + (ticker_thread): Removed. + (cleanup_sh): Removed. + (main): Run the handler for the pipe server in a separate + thread. This replaces the old ticker thread. + (scd_get_socket_name): New. + * command.c (cmd_getinfo): New command GETINFO. + (scd_command_handler): Renamed argument and changed code to use an + already connected FD. + +2005-05-15 Werner Koch <wk@g10code.com> + + * app.c, app-common.h, app-nks.c, app-p15.c, app-dinsig.c + * app-openpgp.c: Change most function return types from int to + gpg_error_t. + * command.c (pin_cb): Ditto. + * sc-copykeys.c (pincb): Ditto. + + * app.c (lock_reader, unlock_reader): New. Changed call handler + wrappers to make use of these functions. + +2005-05-07 Werner Koch <wk@g10code.com> + + * ccid-driver.c (do_close_reader): Don't do a reset before close. + Some folks reported that it makes the SCR335 hang less often. + Look at the source on how to re-enable it. + +2005-04-27 Werner Koch <wk@g10code.com> + + * app-p15.c (micardo_mse): New. + (do_sign): Call it. + * iso7816.c (iso7816_manage_security_env): Allow passing DATA as + NULL to indicate an empty Lc. + * tlv.c (find_tlv): Check that a found object fits into the + buffer. + (find_tlv_unchecked): New as replacement for the old non-checking + variant. + * app.c (select_application): Keep on using the non-checking + variant. + * app-openpgp.c (get_one_do, dump_all_do): Ditto. + + + Removal of the old OpenSC based code. + + * app-p15.c: New. Basic support for pkcs15 cards without OpenSC. + There are quite a couple of things missing but at least I can use + my old TCOS cards from the Aegypten-1 development for signing. + * app.c (select_application): Detect pkcs15 applications. + * Makefile.am (scdaemon_SOURCES): Removed card.c, card-common.h + and card-p15.c because they are now obsolete. Added app-p15.c. + Removed all OpenSC stuff. + * command.c (do_reset, open_card, cmd_serialno, cmd_learn) + (cmd_readcert, cmd_readkey, cmd_pksign, cmd_pkdecrypt): Removed + all special cases for the old card.c based mechanisms. + * scdaemon.c, apdu.c: Removed all special cases for OpenSC. + +2005-04-20 Werner Koch <wk@g10code.com> + + * command.c: Use GPG_ERR_LOCKED instead of EBUSY. + +2005-04-14 Werner Koch <wk@g10code.com> + + * app-openpgp.c (retrieve_key_material): Rewritten. Return a + proper error code. + (retrieve_next_token): Removed. + (retrieve_fpr_from_card): Rewritten to make use of DO caching and + to take the KEYNO as arg. + (get_public_key): Renamed variable for clarity. + +2005-04-12 Werner Koch <wk@g10code.com> + + Basic support for several sessions. + + * command.c (scd_command_handler): Replace the primary_connection + stuff by a real connection list. Release the local context on + exit. + (scd_update_reader_status_file): Update accordingly. Send signal + to all connections who registered an event signal. + (cmd_lock, cmd_unlock, register_commands): New commands LOCK and + UNLOCK. + (cmd_setdata, cmd_pksign, cmd_pkauth, cmd_pkdecrypt, cmd_setattr) + (cmd_genkey, cmd_passwd, cmd_checkpin): Return an error if reader + is locked. + (do_reset): Handle locking. + (open_card): Ditto. Share the reader slot with other sessions. + (get_reader_slot): New. + (update_card_removed): New. Use it in the TEST_CARD_REMOVAL macro. + +2005-04-07 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_check_pin): Add hack to allow verification of + CHV3. + (get_public_key): Don't use gcry functions to create S-expressions. + (do_deinit, do_readkey, do_genkey, send_keypair_info): Adjust for + above change. + +2005-03-29 Moritz Schulte <moritz@g10code.com> + + * app-openpgp.c (retrieve_fpr_from_card): New function. + (retrieve_next_token): New function. + (retrieve_key_material): New function. + (get_public_key): Implement retrival of key through expernal + helper (gpg) in case the openpgp card is not cooperative enough. + +2005-03-16 Werner Koch <wk@g10code.com> + + * ccid-driver.c (parse_ccid_descriptor): Make SCM workaround + reader type specific. + (scan_or_find_devices): Do not check the interface subclass in the + SPR532 kludge, as this depends on the firmware version. + (ccid_get_atr): Get the Slot status first. This solves the + problem with readers hanging on recent Linux 2.6.x. + (bulk_in): Add argument TIMEOUT and changed all callers to pass an + appropriate one. Change the standard timeout from 10 to 5 seconds. + (ccid_slot_status): Add a retry code with an initial short timeout. + (do_close_reader): Do an usb_reset before closing the reader. + +2005-02-25 Werner Koch <wk@g10code.com> + + * app-openpgp.c (get_public_key): Make sure not to return negative + numbers. + (do_sign): Allow passing of indata with algorithm prefix. + (do_auth): Allow OPENPGP.3 as an alternative ID. + + * app.c (app_getattr): Return just the S/N but not the timestamp. + +2005-02-24 Werner Koch <wk@g10code.com> + + * app.c (app_getattr): Return APPTYPE or SERIALNO type even if the + application does dot support the getattr call. + + * app-openpgp.c (get_one_do): Never try to get a non cacheable + object from the cache. + (get_one_do): Add new arg to return an error code. Changed all + callers. + (do_getattr): Let it return a proper error code. + + * app.c (select_application): Return an error code and the + application context in an new arg. + * command.c (open_card): Adjusted for that. Don't use the + fallback if no card is present. Return an error if the card has + been removed without a reset. + (do_reset, cmd_serialno): Clear that error flag. + (TEST_CARD_REMOVAL): New. Use it with all command handlers. + (scd_update_reader_status_file): Set the error flag on all changes. + + * scdaemon.c (ticker_thread): Termintate if a shutdown is pending. + + * apdu.c: Added some PCSC error codes. + (pcsc_error_to_sw): New. + (reset_pcsc_reader, pcsc_get_status, pcsc_send_apdu) + (open_pcsc_reader): Do proper error code mapping. + +2005-03-16 Werner Koch <wk@g10code.com> + + * ccid-driver.c (parse_ccid_descriptor): Make SCM workaround + reader type specific. + (scan_or_find_devices): Do not check the interface subclass in the + SPR532 kludge, as this depends on the firmware version. + (ccid_get_atr): Get the Slot status first. This solves the + problem with readers hanging on recent Linux 2.6.x. + +2005-02-22 Werner Koch <wk@g10code.com> + + * app-openpgp.c (app_local_s): New field PK. + (do_deinit, do_genkey, app_openpgp_storekey): Clear it. + (get_public_key, send_keypair_info): New. + (do_learn_status): Send KEYPAIR info + + * app-common.h (app_ctx_t): Add function pointer READKEY. + * app.c (app_readkey): New. + * command.c (cmd_readkey): Use READKEY function if possible. + +2005-01-26 Werner Koch <wk@g10code.com> + + * ccid-driver.c (parse_ccid_descriptor): Need the CSM workaround + also for newer firmware versions. Need to get a list of fixed + firmware versions and use that. + +2005-01-25 Werner Koch <wk@g10code.com> + + * apdu.c (apdu_send_le, apdu_send_direct): Fix some compiler + warnings. + + * app-openpgp.c (get_cached_data): New arg GET_IMMEDIATE to bypass + the cache. Changed all callers. + (get_one_do): Bypass the cache if the value would have been read + directly for v1.1 cards.It makes things a bit slower but obnly for + 1.0 cards and there are not that many cards out in the wild. This + is required to fix a caching bug when generating new keys; as a + side effect of the retrieval of the the C4 DO from the 6E DO the + cached fingerprint will get updated to the old value and later + when signing the generated key the checking of the fingerprint + fails because it won't match the new one. Thanks to Moritz for + analyzing this problem. + (verify_chv3): Removed the CHV status reread logic because we + won't cache the C4 DO anymore. + +2004-12-28 Werner Koch <wk@g10code.com> + + * ccid-driver.c (find_endpoint): New. + (scan_or_find_devices): Add new args to return endpoint info and + interface number. + (ccid_open_reader, ccid_shutdown_reader): Take care of these new + args. + (bulk_in, bulk_out): Use the correct endpoints. + (ccid_transceive_apdu_level): New. + (ccid_transceive): Divert to above. + (parse_ccid_descriptor): Allow APDU level exchange mode. + (do_close_reader): Pass the interface number to usb_release_interface. + +2004-12-21 Werner Koch <wk@g10code.com> + + * scdaemon.c (main): Use default_homedir(). + +2004-12-18 Werner Koch <wk@g10code.com> + + * scdaemon.c (main) [W32]: Remove special Pth initialize.. + + * scdaemon.h (map_assuan_err): Define in terms of + map_assuan_err_with_source. + +2004-12-15 Werner Koch <wk@g10code.com> + + * scdaemon.c [W32]: Various hacks to make it run under W32. + + * command.c (scd_update_reader_status_file) [W32]: Don't use kill. + + * apdu.c [W32]: Disable use of pcsc_wrapper. + + * Makefile.am (scdaemon_LDADD): Reorder libs. + (sc_copykeys_LDADD): Add libassuan because it is needed for W32. + +2004-12-06 Werner Koch <wk@g10code.com> + + * Makefile.am (pkglib_PROGRAMS): Build only for W32. + +2004-10-22 Werner Koch <wk@g10code.com> + + * app-openpgp.c (verify_chv3): The minium length for CHV3 is + 8. Changed string to match the other ones. + +2004-10-21 Werner Koch <wk@g10code.com> + + * app-openpgp.c (do_sign): Replace asprintf by direct allocation. + This avoids problems with missing vasprintf implementations in + gnupg 1.4. + + * app-common.h (app_openpgp_storekey: Add prototype. + +2004-10-20 Werner Koch <wk@g10code.com> + + * sc-investigate: Removed. + * Makefile.am (sc_investigate): Removed. + + * pcsc-wrapper.c (load_pcsc_driver): Load get_status_change func. + (handle_open): Succeed even without a present card. + (handle_status, handle_reset): New. + + * apdu.c (apdu_open_reader): Load pcsc_get_status_change fucntion. + (pcsc_get_status): Implemented. + (reset_pcsc_reader): Implemented. + (open_pcsc_reader): Succeed even with no card inserted. + (open_ccid_reader): Set LAST_STATUS. + + * iso7816.c (iso7816_select_application): Always use 0 for P1. + +2004-10-18 Werner Koch <wk@g10code.com> + + * ccid-driver.c (ccid_get_atr): Reset T=1 state info. + +2004-10-14 Werner Koch <wk@g10code.com> + + * app-openpgp.c (parse_login_data): New. + (app_select_openpgp): Call it. + (do_setattr): Reparse it after change. + +2004-10-06 Werner Koch <wk@g10code.de> + + * ccid-driver.c (ccid_open_reader): Store the vendor ID. + (ccid_transceive_secure): New. + (parse_ccid_descriptor): Workaround for an SCM reader problem. + +2004-10-04 Werner Koch <wk@g10code.de> + + * ccid-driver.c (send_escape_cmd): New. + +2004-09-30 Werner Koch <wk@g10code.com> + + * Makefile.am: Adjusted for gettext 0.14. + + * app-openpgp.c (do_sign): Add the error string to the verify + failed messages. + +2004-09-27 Werner Koch <wk@g10code.com> + + From gnupg 1.3 + + * app-openpgp.c: Made all strings translatable. + (verify_chv3) [GNUPG_MAJOR_VERSION]: Make opt.allow_admin + available for use in gnupg 2. + (verify_chv3): Reimplemented countdown showing to use only + functions from this module. Flush the CVH status cache on a + successful read. + (get_one_do): Hack to bypass the cache for cards versions > 1.0. + (store_fpr): Store the creation date for card version > 1.0. + + * app-openpgp.c (app_openpgp_storekey): Call flush_cache. + (get_cached_data): Move local data initialization to .. + (app_select_openpgp): .. here. Read some flags for later use. + (do_getattr): New read-only attribute EXTCAP. + + * apdu.c (open_pcsc_reader): Do not print empty reader string. + + * ccid-driver.c (do_close_reader): Factored some code out from ... + (ccid_close_reader): ..here. + (ccid_shutdown_reader): New. + + * apdu.c (apdu_shutdown_reader): New. + (shutdown_ccid_reader): New. + + * apdu.c (open_ccid_reader): New arg PORTSTR. Pass it to + ccid_open_reader. + (apdu_open_reader): Pass portstr to open_ccid_reader. + (apdu_open_reader): No fallback if a full CCID reader id has been + given. + + * ccid-driver.c (ccid_get_reader_list): New. + (ccid_open_reader): Changed API to take a string for the reader. + Removed al the cruft for the libusb development vesion which seems + not to be maintained anymore and there are no packages anyway. + The stable library works just fine. + (struct ccid_reader_id_s): Deleted and replaced everywhere by a + simple string. + (usb_get_string_simple): Removed. + (bulk_in): Do valgrind hack here and not just everywhere. + + * ccid-driver.c (read_device_info): Removed. + (make_reader_id, scan_or_find_devices): New. + (ccid_open_reader): Simplified by make use of the new functions. + (ccid_set_debug_level): New. Changed the macros to make use of + it. It has turned out that it is often useful to enable debugging + at runtime so I added this option. + + From gnupg 1.3 - David Shaw <dshaw@jabberwocky.com> + + * app-openpgp.c (verify_chv3): Show a countdown of how many wrong + admin PINs can be entered before the card is locked. + + * app-openpgp.c (get_cached_data): Avoid mallocing zero since it + breaks us when using --enable-m-guard. + + * ccid-driver.c (usb_get_string_simple): Replacement function to + work with older libusb. + + * ccid-driver.c (read_device_info): Fix segfault when usb device + is not accessible. + (ccid_open_reader): Allow working with an even older version of + libusb (usb_busses global instead of usb_get_busses()). + +2004-09-11 Werner Koch <wk@g10code.com> + + * app-openpgp.c (app_select_openpgp): Its app_munge_serialno and + not app_number_serialno. + +2004-08-20 Werner Koch <wk@g10code.de> + + * app.c (select_application): Fixed serial number extraction and + added the BMI card workaround. + (app_munge_serialno): New. + * app-openpgp.c (app_select_openpgp): Try munging serialno. + +2004-08-05 Werner Koch <wk@g10code.de> + + * scdaemon.c (main): New option --disable-application. + * app.c (is_app_allowed): New. + (select_application): Use it to check for disabled applications. + + * ccid-driver.h (CCID_DRIVER_ERR_ABORTED): New. + * ccid-driver.c (ccid_open_reader): Support the stable 0.1 version + of libusb. + (ccid_get_atr): Handle short messages. + + * apdu.c (my_rapdu_get_status): Implemented. + +2004-07-27 Moritz Schulte <moritz@g10code.com> + + * apdu.c: Include <signal.h>. + + * Makefile.am: Use @DL_LIBS@ instead of -ldl. + +2004-07-22 Werner Koch <wk@g10code.de> + + * Makefile.am: Make OpenSC lib link after libgcrypt. Do not link + to pth. + * apdu.c: Don't use Pth if we use OpenSC. + * sc-investigate.c, scdaemon.c: Disable use of pth if OpenSC is used. + + * scdaemon.c (main): Bumbed thread stack size up to 512k. + +2004-07-16 Werner Koch <wk@gnupg.org> + + * apdu.c (reader_table_s): Add function pointers for the backends. + (apdu_close_reader, apdu_get_status, apdu_activate) + (send_apdu): Make use of them. + (new_reader_slot): Intialize them to NULL. + (dump_ccid_reader_status, ct_dump_reader_status): New. + (dump_pcsc_reader_status): New. + (open_ct_reader, open_pcsc_reader, open_ccid_reader) + (open_osc_reader, open_rapdu_reader): Intialize function pointers. + (ct_activate_card, ct_send_apdu, pcsc_send_apdu, osc_send_apdu) + (error_string): Removed. Replaced by apdu_strerror. + (get_ccid_error_string): Removed. + (ct_activate_card): Remove the unused loop. + (reset_ct_reader): Implemented. + (ct_send_apdu): Activate the card if not yet done. + (pcsc_send_apdu): Ditto. + +2004-07-15 Werner Koch <wk@gnupg.org> + + * ccid-driver.h: Add error codes. + * ccid-driver.c: Implement more or less proper error codes all + over the place. + + * apdu.c (apdu_send_direct): New. + (get_ccid_error_string): Add some error code mappings. + (send_apdu): Pass error codes along for drivers already supporting + them. + (host_sw_string): New. + (get_ccid_error_string): Use above. + (send_apdu_ccid): Reset the reader if it has not yet been done. + (open_ccid_reader): Don't care if the ATR can't be read. + (apdu_activate_card): New. + (apdu_strerror): New. + (dump_reader_status): Only enable it with opt.VERBOSE. + * iso7816.c (map_sw): Add mappings for the new error codes. + +2004-07-02 Werner Koch <wk@gnupg.org> + + * apdu.c (open_ct_reader, open_pcsc_reader, open_ccid_reader) + (reset_ccid_reader, open_osc_reader): Call dump_reader_status only + in verbose mode. + +2004-07-01 Werner Koch <wk@gnupg.org> + + * sc-investigate.c: Initialize Pth which is now required. + (interactive_shell): New command "readpk". + + * app-openpgp.c (do_getattr): Fix for sending CA-FPR. + +2004-06-30 Werner Koch <wk@gnupg.org> + + * app-openpgp.c (app_openpgp_readkey): Fixed check for valid + exponent. + +2004-06-18 Werner Koch <wk@g10code.com> + + * sc-investigate.c (my_read_line): Renamed from read_line. + +2004-06-16 Werner Koch <wk@gnupg.org> + + * apdu.c (osc_get_status): Fixed type in function name. Noted by + Axel Thimm. Yes, I didn't tested it with OpenSC :-(. + +2004-04-28 Werner Koch <wk@gnupg.org> + + * app-openpgp.c (do_setattr): Sync FORCE_CHV1. + +2004-04-27 Werner Koch <wk@gnupg.org> + + * app-common.h: Do not include ksba.h for gnupg 1. + +2004-04-26 Werner Koch <wk@gnupg.org> + + * app-common.h: New members FNC.DEINIT and APP_LOCAL. + * app.c (release_application): Call new deconstructor. + * app-openpgp.c (do_deinit): New. + (get_cached_data, flush_cache_item, flush_cache_after_error) + (flush_cache): New. + (get_one_do): Replaced arg SLOT by APP. Make used of cached data. + (verify_chv2, verify_chv3): Flush some cache item after error. + (do_change_pin): Ditto. + (do_sign): Ditto. + (do_setattr): Flush cache item. + (do_genkey): Flush the entire cache. + (compare_fingerprint): Use cached data. + + * scdaemon.c (main): Do the last change the usual way. This is so + that we can easily test for versioned config files above. + +2004-04-26 Marcus Brinkmann <marcus@g10code.de> + + * scdaemon.c (main): For now, always print default filename for + --gpgconf-list, and never /dev/null. + +2004-04-21 Werner Koch <wk@gnupg.org> + + * command.c (scd_update_reader_status_file): Send a signal back to + the client. + (option_handler): Parse the new event-signal option. + + * scdaemon.c (handle_signal): Do not use SIGUSR{1,2} anymore for + changing the verbosity. + +2004-04-20 Werner Koch <wk@gnupg.org> + + * command.c (scd_update_reader_status_file): Write status files. + + * app-help.c (app_help_read_length_of_cert): Fixed calculation of + R_CERTOFF. + + * pcsc-wrapper.c: New. + * Makefile.am (pkglib_PROGRAMS): Install it here. + * apdu.c (writen, readn): New. + (open_pcsc_reader, pcsc_send_apdu, close_pcsc_reader): Use the + pcsc-wrapper if we are using Pth. + (apdu_send_le): Reinitialize RESULTLEN. Handle SW_EOF_REACHED + like SW_SUCCESS. + +2004-04-19 Werner Koch <wk@gnupg.org> + + * ccid-driver.c (parse_ccid_descriptor): Store some of the reader + features away. New arg HANDLE + (read_device_info): New arg HANDLE. Changed caller. + (bulk_in): Handle time extension requests. + (ccid_get_atr): Setup parameters and the IFSD. + (compute_edc): New. Factored out code. + (ccid_transceive): Use default NADs when required. + +2004-04-14 Werner Koch <wk@gnupg.org> + + * scdaemon.h (server_control_s): Add member READER_SLOT. + * scdaemon.c (scd_init_default_ctrl): Initialize READER_SLOT to -1. + * command.c (open_card): Reuse an open slot. + (reset_notify): Just reset the slot if supported by the reader. + (do_reset): Factored code from above out. + (scd_command_handler): Use it for cleanup. + + * apdu.h: New pseudo stati SW_HOST_NOT_SUPPORTED, + SW_HOST_LOCKING_FAILED and SW_HOST_BUSY. + * iso7816.c (map_sw): Map it. + + * ccid-driver.c (ccid_slot_status): Add arg STATUSBITS. + * apdu.c (apdu_get_status): New. + (ct_get_status, pcsc_get_status, ocsc_get_status): New stubs. + (get_status_ccid): New. + (apdu_reset): New. + (reset_ct_reader, reset_pcsc_reader, reset_osc_reader): New stubs. + (reset_ccid_reader): New. + (apdu_enum_reader): New. + + * apdu.c (lock_slot, trylock_slot, unlock_slot): New helpers. + (new_reader_slot) [USE_GNU_PTH]: Init mutex. + (apdu_reset, apdu_get_status, apdu_send_le): Run functions + in locked mode. + + * command.c (scd_update_reader_status_file): New. + * scdaemon.c (handle_tick): Call it. + +2004-04-13 Werner Koch <wk@gnupg.org> + + * scdaemon.c: Convert to a Pth application. + (handle_signal, ticker_thread, handle_tick): New. + (main): Fire up the ticker thread in server mode. + +2004-03-23 Werner Koch <wk@gnupg.org> + + * scdaemon.c (main) <gpgconf_list>: Fixed output for pcsc_driver. + +2004-03-17 Werner Koch <wk@gnupg.org> + + * tlv.c (parse_ber_header): Do not check for tag overflow - it + does not make sense. Simplified the check for length overflow. + + * scdaemon.c (main) <gpgconf>: Fixed default value quoting. + +2004-03-16 Werner Koch <wk@gnupg.org> + + * app-dinsig.c: Implemented. Based on app-nks.c and card-dinsig.c + * app-nks.c (get_length_of_cert): Removed. + * app-help.c: New. + (app_help_read_length_of_cert): New. Code taken from above. New + optional arg R_CERTOFF. + + * card-dinsig.c: Removed. + * card.c (card_get_serial_and_stamp): Do not bind to the old and + never finsiged card-dinsig.c. + + * iso7816.c (iso7816_read_binary): Allow for an NMAX > 254. + +2004-03-11 Werner Koch <wk@gnupg.org> + + * scdaemon.h (out_of_core): Removed. Replaced callers by standard + gpg_error function. + + * apdu.c, iso7816.c, ccid-driver.c [GNUPG_SCD_MAIN_HEADER]: Allow + to include a header defined by the compiler. This helps us to + reuse the source in other software. + +2004-03-10 Werner Koch <wk@gnupg.org> + + * iso7816.c (iso7816_read_record): New arg SHORT_EF. Changed all + callers. + +2004-02-18 Werner Koch <wk@gnupg.org> + + * sc-investigate.c (main): Setup the used character set. + * scdaemon.c (main): Ditto. + + * scdaemon.c (set_debug): New. Add option --debug-level. + (main): Add option --gpgconf-list. + +2004-02-12 Werner Koch <wk@gnupg.org> + + * Makefile.am: Include cmacros.am for common flags. + +2004-01-29 Werner Koch <wk@gnupg.org> + + * command.c (reset_notify): Release the application context and + close the reader. + +2004-01-28 Werner Koch <wk@gnupg.org> + + * iso7816.c (iso7816_manage_security_env): New. + (iso7816_decipher): Add PADIND argument. + +2004-01-27 Werner Koch <wk@gnupg.org> + + * command.c (cmd_readcert, cmd_readkey): Work on a copy of LINE. + + * app-common.h (app_ctx_s): Added readcert field. + * app.c (app_readcert): New. + * tlv.c (parse_ber_header): Added; taken from libksba. + +2004-01-26 Werner Koch <wk@gnupg.org> + + * card.c (map_sc_err): Use SCD as the error source. + + * command.c (open_card): ADD arg NAME to allow requesting a + specific application. Changed all callers. + (cmd_serialno): Allow optional argument to select the desired + application. + + * app-nks.c: New. + + * scdaemon.h (opt): Add READER_PORT. + * scdaemon.c (main): Set it here. + * app.c (app_set_default_reader_port): Removed. + (select_application): Add NAME arg and figure out a + default serial number from the GDO. Add SLOT arg and remove all + reader management. + (release_application): New. + (app_write_learn_status): Output an APPTYPE status line. + * command.c (open_card): Adapt for select_application change. + * app-openpgp.c (app_select_openpgp): Removed SN and SNLEN args + and set it directly. Changed all callers. + +2004-01-25 Werner Koch <wk@gnupg.org> + + * iso7816.c (iso7816_select_application): P1 kludge for OpenPGP + card. + * app-openpgp.c (find_tlv): Factor out this function to .. + * tlv.c, tlv.h: .. new. + + * scdaemon.h: Introduced app_t and ctrl_t as the new types for APP + and CTRL. + +2004-01-21 Werner Koch <wk@gnupg.org> + + * apdu.c (apdu_send_le): Treat SW_EOF_REACHED as a warning. + +2004-01-20 Werner Koch <wk@gnupg.org> + + * iso7816.c (iso7816_read_binary): New. + (iso7816_select_file): New. + (iso7816_list_directory): New. + + * sc-investigate.c: Add option -i. + (select_app, read_line, interactive_shell): New. + +2004-01-16 Werner Koch <wk@gnupg.org> + + * apdu.h: Add SW_FILE_NOT_FOUND. + * iso7816.c (map_sw): Map it to GPG_ERR_ENOENT. + * iso7816.c (iso7816_select_file): New. + + * app-dinsig.c: New file w/o any real code yet. + * Makefile.am (scdaemon_SOURCES,sc_investigate_SOURCES): Add file. + + * sc-investigate.c: Add option --disable-ccid. + +2003-12-19 Werner Koch <wk@gnupg.org> + + * apdu.c (apdu_send_le): Send a get_response with the indicated + length and not the 64 bytes we used for testing. + + * app-openpgp.c (verify_chv2, verify_chv3, do_sign): Check the + minimum length of the passphrase, so that we don't need to + decrement the retry counter. + +2003-12-17 Werner Koch <wk@gnupg.org> + + * card-p15.c (p15_enum_keypairs): Replaced KRC by RC. + * card-dinsig.c (dinsig_enum_keypairs): Ditto. + +2003-12-16 Werner Koch <wk@gnupg.org> + + * scdaemon.c (main): Set the prefixes for assuan logging. + +2003-11-17 Werner Koch <wk@gnupg.org> + + * scdaemon.c, scdaemon.h: New options --allow-admin and --deny-admin. + * app-openpgp.c (verify_chv3): Check it here. + +2003-11-12 Werner Koch <wk@gnupg.org> + + Adjusted for API changes in Libksba. + +2003-10-30 Werner Koch <wk@gnupg.org> + + * apdu.c (close_ct_reader, close_pcsc_reader): Implemented. + (get_ccid_error_string): New. Not very useful messages, though. + +2003-10-25 Werner Koch <wk@gnupg.org> + + * ccid-driver.c (ccid_open_reader): Return an error if no USB + devices are found. + + * command.c (cmd_genkey, cmd_passwd): Fixed faulty use of + !spacep(). + + * apdu.c (apdu_open_reader): Hacks for PC/SC under Windows. + +2003-10-20 Werner Koch <wk@gnupg.org> + + * command.c (cmd_checkpin): New. + (register_commands): Add command CHECKPIN. + * app.c (app_check_pin): New. + * app-openpgp.c (check_against_given_fingerprint): New. Factored + out that code elsewhere. + (do_check_pin): New. + +2003-10-10 Werner Koch <wk@gnupg.org> + + * ccid-driver.c (ccid_close_reader): New. + + * apdu.c (close_ccid_reader, close_ct_reader, close_csc_reader) + (close_osc_reader, apdu_close_reader): New. Not all are properly + implemented yet. + +2003-10-09 Werner Koch <wk@gnupg.org> + + * ccid-driver.c (ccid_transceive): Add T=1 chaining for sending. + +2003-10-08 Werner Koch <wk@gnupg.org> + + * app-openpgp.c (do_getattr): Support SERIALNO and AID. + +2003-10-01 Werner Koch <wk@gnupg.org> + + * ccid-driver.c: Detect GnuPG 1.3 and include appropriate files. + * apdu.c: Ditto. + * app-openpgp.c: Ditto. + * iso7816.c: Ditto. + (generate_keypair): Renamed to .. + (do_generate_keypair): .. this. + * app-common.h [GNUPG_MAJOR_VERSION]: New. + * iso7816.h [GNUPG_MAJOR_VERSION]: Include cardglue.h + +2003-09-30 Werner Koch <wk@gnupg.org> + + * command.c (cmd_getattr): New command GETATTR. + * app.c (app_setattr): New. + (do_getattr): New. + (do_learn_status): Reimplemented in terms of do_getattr. + + * app-openpgp.c (do_change_pin): Make sure CVH1 and CHV2 are + always synced. + (verify_chv2, verify_chv3): New. Factored out common code. + (do_setattr, do_sign, do_auth, do_decipher): Change the names of + the prompts to match that we have only 2 different PINs. + (app_select_openpgp): Check whether the card enforced CHV1. + (convert_sig_counter_value): New. Factor out code from + get_sig_counter. + +2003-09-28 Werner Koch <wk@gnupg.org> + + * app-openpgp.c (dump_all_do): Use gpg_err_code and not gpg_error. + +2003-09-19 Werner Koch <wk@gnupg.org> + + * ccid-driver.c (parse_ccid_descriptor): New. + (read_device_info): New. + (ccid_open_reader): Check that the device has all required features. + +2003-09-06 Werner Koch <wk@gnupg.org> + + * scdaemon.c (main): --pcsc-driver again defaults to pcsclite. + David Corcoran was so kind to remove the GPL incompatible + advertisng clause from pcsclite. + * apdu.c (apdu_open_reader): Actually make pcsc-driver option work. + +2003-09-05 Werner Koch <wk@gnupg.org> + + * ccid-driver.c: More work, data can now actually be retrieved. + * ccid-driver.c, ccid-driver.h: Alternativley allow use under BSD + conditions. + +2003-09-02 Werner Koch <wk@gnupg.org> + + * scdaemon.c, scdaemon.h: New option --pcsc-ccid. + * ccid-driver.c, ccid-driver.h: New but far from being useful. + * Makefile.am: Add above. + * apdu.c: Add support for that ccid driver. + +2003-08-26 Timo Schulz <twoaday@freakmail.de> + + * apdu.c (new_reader_slot): Only set 'is_osc' when OpenSC + is used. + +2003-08-25 Werner Koch <wk@gnupg.org> + + * command.c (cmd_setattr): Use a copy of LINE. + (cmd_genkey): Use a copy of KEYNO. + (cmd_passwd): Use a copy of CHVNOSTR. + (cmd_pksign, cmd_pkauth, cmd_pkdecrypt): s/strdup/xtrystrdup/. + +2003-08-19 Werner Koch <wk@gnupg.org> + + * scdaemon.c, scdaemon.h: New option --pcsc-driver. + * apdu.c (apdu_open_reader): Use that option here instead of a + hardcoded one. + +2003-08-18 Werner Koch <wk@gnupg.org> + + * Makefile.am: Add OPENSC_LIBS to all programs. + + * scdaemon.c, scdaemon.h: New option --disable-opensc. + * card.c (card_open): Implement it. + * apdu.c (open_osc_reader, osc_send_apdu): New. + (apdu_open_reader) [HAVE_OPENSC]: Use the opensc driver if not + disabled. + (error_string) [HAVE_OPENSC]: Use sc_strerror. + (send_apdu) [HAVE_OPENSC]: Call osc_apdu_send. + + * card-p15.c (p15_enum_keypairs, p15_prepare_key): Adjusted for + libgpg-error. + +2003-08-14 Timo Schulz <twoaday@freakmail.de> + + * apdu.c (ct_activate_card): Change the code a little to avoid + problems with other readers. + * Always use 'dynload.h' instead of 'dlfcn.h'. + +2003-08-05 Werner Koch <wk@gnupg.org> + + * app-openpgp.c (dump_all_do): Don't analyze constructed DOs after + an error. + +2003-08-04 Werner Koch <wk@gnupg.org> + + * app.c (app_set_default_reader_port): New. + (select_application): Use it here. + * scdaemon.c (main): and here. + * sc-copykeys.c: --reader-port does now take a string. + * sc-investigate.c, scdaemon.c: Ditto. + * apdu.c (apdu_open_reader): Ditto. Load pcsclite if no ctapi + driver is configured. Always include code for ctapi. + (new_reader_slot): Don't test for already used ports and remove + port arg. + (open_pcsc_reader, pcsc_send_apdu, pcsc_error_string): New. + (apdu_send_le): Changed RC to long to cope with PC/SC. + + * scdaemon.c, scdaemon.h: New option --ctapi-driver. + * sc-investigate.c, sc-copykeys.c: Ditto. + +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, 2003, 2004, 2005, 2007, 2008 Free Software Foundation, Inc. + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +Local Variables: +buffer-read-only: t +End: diff --git a/scd/Makefile.am b/scd/Makefile.am new file mode 100644 index 0000000..32be6ab --- /dev/null +++ b/scd/Makefile.am @@ -0,0 +1,51 @@ +# Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses/>. + +## Process this file with automake to produce Makefile.in + +EXTRA_DIST = ChangeLog-2011 scdaemon-w32info.rc scdaemon.w32-manifest.in + +libexec_PROGRAMS = scdaemon + +AM_CPPFLAGS = $(LIBUSB_CPPFLAGS) + +include $(top_srcdir)/am/cmacros.am + +if HAVE_W32_SYSTEM +scdaemon_robjs = $(resource_objs) scdaemon-w32info.o +endif + +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \ + $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) + + +card_apps = app-openpgp.c app-nks.c app-dinsig.c app-p15.c app-geldkarte.c app-sc-hsm.c + +scdaemon_SOURCES = \ + scdaemon.c scdaemon.h \ + command.c \ + atr.c atr.h \ + apdu.c apdu.h \ + ccid-driver.c ccid-driver.h \ + iso7816.c iso7816.h \ + app.c app-common.h app-help.c $(card_apps) + + +scdaemon_LDADD = $(libcommonpth) \ + $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \ + $(LIBUSB_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(DL_LIBS) $(NETLIBS) $(LIBICONV) $(scdaemon_robjs) diff --git a/scd/Makefile.in b/scd/Makefile.in new file mode 100644 index 0000000..d278ee5 --- /dev/null +++ b/scd/Makefile.in @@ -0,0 +1,846 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses/>. + +# cmacros.am - C macro definitions +# Copyright (C) 2004 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses/>. + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +libexec_PROGRAMS = scdaemon$(EXEEXT) +@HAVE_DOSISH_SYSTEM_FALSE@am__append_1 = -DGNUPG_BINDIR="\"$(bindir)\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LIBEXECDIR="\"$(libexecdir)\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LIBDIR="\"$(libdir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_DATADIR="\"$(datadir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_SYSCONFDIR="\"$(sysconfdir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LOCALSTATEDIR="\"$(localstatedir)\"" + + +# If a specific protect tool program has been defined, pass its name +# to cc. Note that these macros should not be used directly but via +# the gnupg_module_name function. +@GNUPG_AGENT_PGM_TRUE@am__append_2 = -DGNUPG_DEFAULT_AGENT="\"@GNUPG_AGENT_PGM@\"" +@GNUPG_PINENTRY_PGM_TRUE@am__append_3 = -DGNUPG_DEFAULT_PINENTRY="\"@GNUPG_PINENTRY_PGM@\"" +@GNUPG_SCDAEMON_PGM_TRUE@am__append_4 = -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\"" +@GNUPG_DIRMNGR_PGM_TRUE@am__append_5 = -DGNUPG_DEFAULT_DIRMNGR="\"@GNUPG_DIRMNGR_PGM@\"" +@GNUPG_PROTECT_TOOL_PGM_TRUE@am__append_6 = -DGNUPG_DEFAULT_PROTECT_TOOL="\"@GNUPG_PROTECT_TOOL_PGM@\"" +@GNUPG_DIRMNGR_LDAP_PGM_TRUE@am__append_7 = -DGNUPG_DEFAULT_DIRMNGR_LDAP="\"@GNUPG_DIRMNGR_LDAP_PGM@\"" +subdir = scd +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/autobuild.m4 \ + $(top_srcdir)/m4/codeset.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/isc-posix.m4 $(top_srcdir)/m4/ksba.m4 \ + $(top_srcdir)/m4/lcmessage.m4 $(top_srcdir)/m4/ldap.m4 \ + $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \ + $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libassuan.m4 \ + $(top_srcdir)/m4/libgcrypt.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/npth.m4 $(top_srcdir)/m4/ntbtls.m4 \ + $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/m4/po.m4 \ + $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/readline.m4 \ + $(top_srcdir)/m4/socklen.m4 $(top_srcdir)/m4/sys_socket_h.m4 \ + $(top_srcdir)/m4/tar-ustar.m4 $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = scdaemon.w32-manifest +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(libexecdir)" +PROGRAMS = $(libexec_PROGRAMS) +am__objects_1 = app-openpgp.$(OBJEXT) app-nks.$(OBJEXT) \ + app-dinsig.$(OBJEXT) app-p15.$(OBJEXT) app-geldkarte.$(OBJEXT) \ + app-sc-hsm.$(OBJEXT) +am_scdaemon_OBJECTS = scdaemon.$(OBJEXT) command.$(OBJEXT) \ + atr.$(OBJEXT) apdu.$(OBJEXT) ccid-driver.$(OBJEXT) \ + iso7816.$(OBJEXT) app.$(OBJEXT) app-help.$(OBJEXT) \ + $(am__objects_1) +scdaemon_OBJECTS = $(am_scdaemon_OBJECTS) +am__DEPENDENCIES_1 = +@HAVE_W32_SYSTEM_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) \ +@HAVE_W32_SYSTEM_TRUE@ scdaemon-w32info.o +scdaemon_DEPENDENCIES = $(libcommonpth) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/apdu.Po ./$(DEPDIR)/app-dinsig.Po \ + ./$(DEPDIR)/app-geldkarte.Po ./$(DEPDIR)/app-help.Po \ + ./$(DEPDIR)/app-nks.Po ./$(DEPDIR)/app-openpgp.Po \ + ./$(DEPDIR)/app-p15.Po ./$(DEPDIR)/app-sc-hsm.Po \ + ./$(DEPDIR)/app.Po ./$(DEPDIR)/atr.Po \ + ./$(DEPDIR)/ccid-driver.Po ./$(DEPDIR)/command.Po \ + ./$(DEPDIR)/iso7816.Po ./$(DEPDIR)/scdaemon.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(scdaemon_SOURCES) +DIST_SOURCES = $(scdaemon_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(srcdir)/scdaemon.w32-manifest.in $(top_srcdir)/am/cmacros.am \ + $(top_srcdir)/build-aux/depcomp \ + $(top_srcdir)/build-aux/mkinstalldirs +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +AWK_HEX_NUMBER_OPTION = @AWK_HEX_NUMBER_OPTION@ +BUILD_FILEVERSION = @BUILD_FILEVERSION@ +BUILD_HOSTNAME = @BUILD_HOSTNAME@ +BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@ +BUILD_REVISION = @BUILD_REVISION@ +BUILD_TIMESTAMP = @BUILD_TIMESTAMP@ +BUILD_VERSION = @BUILD_VERSION@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DL_LIBS = @DL_LIBS@ +DNSLIBS = @DNSLIBS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENCFS = @ENCFS@ +EXEEXT = @EXEEXT@ +FUSERMOUNT = @FUSERMOUNT@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUPG_AGENT_PGM = @GNUPG_AGENT_PGM@ +GNUPG_DIRMNGR_LDAP_PGM = @GNUPG_DIRMNGR_LDAP_PGM@ +GNUPG_DIRMNGR_PGM = @GNUPG_DIRMNGR_PGM@ +GNUPG_PINENTRY_PGM = @GNUPG_PINENTRY_PGM@ +GNUPG_PROTECT_TOOL_PGM = @GNUPG_PROTECT_TOOL_PGM@ +GNUPG_SCDAEMON_PGM = @GNUPG_SCDAEMON_PGM@ +GPGKEYS_LDAP = @GPGKEYS_LDAP@ +GPGRT_CONFIG = @GPGRT_CONFIG@ +GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@ +GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@ +GPG_ERROR_LIBS = @GPG_ERROR_LIBS@ +GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@ +GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +KSBA_CFLAGS = @KSBA_CFLAGS@ +KSBA_CONFIG = @KSBA_CONFIG@ +KSBA_LIBS = @KSBA_LIBS@ +LBER_LIBS = @LBER_LIBS@ +LDAPLIBS = @LDAPLIBS@ +LDAP_CPPFLAGS = @LDAP_CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@ +LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@ +LIBASSUAN_LIBS = @LIBASSUAN_LIBS@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBGNUTLS_CFLAGS = @LIBGNUTLS_CFLAGS@ +LIBGNUTLS_LIBS = @LIBGNUTLS_LIBS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBOBJS = @LIBOBJS@ +LIBREADLINE = @LIBREADLINE@ +LIBS = @LIBS@ +LIBUSB_CPPFLAGS = @LIBUSB_CPPFLAGS@ +LIBUSB_LIBS = @LIBUSB_LIBS@ +LIBUTIL_LIBS = @LIBUTIL_LIBS@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NETLIBS = @NETLIBS@ +NPTH_CFLAGS = @NPTH_CFLAGS@ +NPTH_CONFIG = @NPTH_CONFIG@ +NPTH_LIBS = @NPTH_LIBS@ +NTBTLS_CFLAGS = @NTBTLS_CFLAGS@ +NTBTLS_CONFIG = @NTBTLS_CONFIG@ +NTBTLS_LIBS = @NTBTLS_LIBS@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_GT = @PACKAGE_GT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POSUB = @POSUB@ +RANLIB = @RANLIB@ +SENDMAIL = @SENDMAIL@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SHRED = @SHRED@ +SQLITE3_CFLAGS = @SQLITE3_CFLAGS@ +SQLITE3_LIBS = @SQLITE3_LIBS@ +STRIP = @STRIP@ +SYSROOT = @SYSROOT@ +SYS_SOCKET_H = @SYS_SOCKET_H@ +TAR = @TAR@ +USE_C99_CFLAGS = @USE_C99_CFLAGS@ +USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +W32SOCKLIBS = @W32SOCKLIBS@ +WINDRES = @WINDRES@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +YAT2M = @YAT2M@ +ZLIBS = @ZLIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = $(datadir)/locale +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +EXTRA_DIST = ChangeLog-2011 scdaemon-w32info.rc scdaemon.w32-manifest.in + +# NB: AM_CFLAGS may also be used by tools running on the build +# platform to create source files. +AM_CPPFLAGS = $(LIBUSB_CPPFLAGS) -DLOCALEDIR=\"$(localedir)\" \ + $(am__append_1) $(am__append_2) $(am__append_3) \ + $(am__append_4) $(am__append_5) $(am__append_6) \ + $(am__append_7) +@HAVE_W32CE_SYSTEM_FALSE@extra_sys_libs = + +# Under Windows we use LockFileEx. WindowsCE provides this only on +# the WindowsMobile 6 platform and thus we need to use the coredll6 +# import library. We also want to use a stacksize of 256k instead of +# the 2MB which is the default with cegcc. 256k is the largest stack +# we use with pth. +@HAVE_W32CE_SYSTEM_TRUE@extra_sys_libs = -lcoredll6 +@HAVE_W32CE_SYSTEM_FALSE@extra_bin_ldflags = +@HAVE_W32CE_SYSTEM_TRUE@extra_bin_ldflags = -Wl,--stack=0x40000 +resource_objs = + +# Convenience macros +libcommon = ../common/libcommon.a +libcommonpth = ../common/libcommonpth.a +libcommontls = ../common/libcommontls.a +libcommontlsnpth = ../common/libcommontlsnpth.a +@HAVE_W32_SYSTEM_TRUE@scdaemon_robjs = $(resource_objs) scdaemon-w32info.o +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \ + $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) + +card_apps = app-openpgp.c app-nks.c app-dinsig.c app-p15.c app-geldkarte.c app-sc-hsm.c +scdaemon_SOURCES = \ + scdaemon.c scdaemon.h \ + command.c \ + atr.c atr.h \ + apdu.c apdu.h \ + ccid-driver.c ccid-driver.h \ + iso7816.c iso7816.h \ + app.c app-common.h app-help.c $(card_apps) + +scdaemon_LDADD = $(libcommonpth) \ + $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \ + $(LIBUSB_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(DL_LIBS) $(NETLIBS) $(LIBICONV) $(scdaemon_robjs) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj .rc +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/am/cmacros.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu scd/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu scd/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; +$(top_srcdir)/am/cmacros.am $(am__empty): + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +scdaemon.w32-manifest: $(top_builddir)/config.status $(srcdir)/scdaemon.w32-manifest.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +install-libexecPROGRAMS: $(libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(libexecdir)" && rm -f $$files + +clean-libexecPROGRAMS: + -test -z "$(libexec_PROGRAMS)" || rm -f $(libexec_PROGRAMS) + +scdaemon$(EXEEXT): $(scdaemon_OBJECTS) $(scdaemon_DEPENDENCIES) $(EXTRA_scdaemon_DEPENDENCIES) + @rm -f scdaemon$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(scdaemon_OBJECTS) $(scdaemon_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/apdu.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/app-dinsig.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/app-geldkarte.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/app-help.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/app-nks.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/app-openpgp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/app-p15.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/app-sc-hsm.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/app.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atr.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ccid-driver.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iso7816.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/scdaemon.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(libexecdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libexecPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/apdu.Po + -rm -f ./$(DEPDIR)/app-dinsig.Po + -rm -f ./$(DEPDIR)/app-geldkarte.Po + -rm -f ./$(DEPDIR)/app-help.Po + -rm -f ./$(DEPDIR)/app-nks.Po + -rm -f ./$(DEPDIR)/app-openpgp.Po + -rm -f ./$(DEPDIR)/app-p15.Po + -rm -f ./$(DEPDIR)/app-sc-hsm.Po + -rm -f ./$(DEPDIR)/app.Po + -rm -f ./$(DEPDIR)/atr.Po + -rm -f ./$(DEPDIR)/ccid-driver.Po + -rm -f ./$(DEPDIR)/command.Po + -rm -f ./$(DEPDIR)/iso7816.Po + -rm -f ./$(DEPDIR)/scdaemon.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-libexecPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/apdu.Po + -rm -f ./$(DEPDIR)/app-dinsig.Po + -rm -f ./$(DEPDIR)/app-geldkarte.Po + -rm -f ./$(DEPDIR)/app-help.Po + -rm -f ./$(DEPDIR)/app-nks.Po + -rm -f ./$(DEPDIR)/app-openpgp.Po + -rm -f ./$(DEPDIR)/app-p15.Po + -rm -f ./$(DEPDIR)/app-sc-hsm.Po + -rm -f ./$(DEPDIR)/app.Po + -rm -f ./$(DEPDIR)/atr.Po + -rm -f ./$(DEPDIR)/ccid-driver.Po + -rm -f ./$(DEPDIR)/command.Po + -rm -f ./$(DEPDIR)/iso7816.Po + -rm -f ./$(DEPDIR)/scdaemon.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-libexecPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libexecPROGRAMS cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am \ + install-libexecPROGRAMS install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-libexecPROGRAMS + +.PRECIOUS: Makefile + + +@HAVE_W32_SYSTEM_TRUE@.rc.o: +@HAVE_W32_SYSTEM_TRUE@ $(WINDRES) $(DEFAULT_INCLUDES) $(INCLUDES) "$<" "$@" + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/scd/apdu.c b/scd/apdu.c new file mode 100644 index 0000000..9568d25 --- /dev/null +++ b/scd/apdu.c @@ -0,0 +1,3523 @@ +/* apdu.c - ISO 7816 APDU functions and low level I/O + * Copyright (C) 2003, 2004, 2008, 2009, 2010, + * 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +/* NOTE: This module is also used by other software, thus the use of + the macro USE_NPTH is mandatory. For GnuPG this macro is + guaranteed to be defined true. */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#ifdef USE_NPTH +# include <unistd.h> +# include <fcntl.h> +# include <npth.h> +#endif + + +/* If requested include the definitions for the remote APDU protocol + code. */ +#ifdef USE_G10CODE_RAPDU +#include "rapdu.h" +#endif /*USE_G10CODE_RAPDU*/ + +#if defined(GNUPG_SCD_MAIN_HEADER) +#include GNUPG_SCD_MAIN_HEADER +#elif GNUPG_MAJOR_VERSION == 1 +/* This is used with GnuPG version < 1.9. The code has been source + copied from the current GnuPG >= 1.9 and is maintained over + there. */ +#include "../common/options.h" +#include "errors.h" +#include "memory.h" +#include "../common/util.h" +#include "../common/i18n.h" +#include "dynload.h" +#include "cardglue.h" +#else /* GNUPG_MAJOR_VERSION != 1 */ +#include "scdaemon.h" +#include "../common/exechelp.h" +#endif /* GNUPG_MAJOR_VERSION != 1 */ +#include "../common/host2net.h" +#include "../common/membuf.h" + +#include "iso7816.h" +#include "apdu.h" +#define CCID_DRIVER_INCLUDE_USB_IDS 1 +#include "ccid-driver.h" + +struct dev_list { + void *table; + const char *portstr; + int idx; + int idx_max; +}; + +#define MAX_READER 4 /* Number of readers we support concurrently. */ + + +#if defined(_WIN32) || defined(__CYGWIN__) +#define DLSTDCALL __stdcall +#else +#define DLSTDCALL +#endif + +#if defined(__APPLE__) || defined(_WIN32) || defined(__CYGWIN__) +typedef unsigned int pcsc_dword_t; +#else +typedef unsigned long pcsc_dword_t; +#endif + +/* PC/SC context to access readers. Shared among all readers. */ +static struct pcsc { + unsigned int context_valid:1; + long context; + char *reader_list; /* List of detected readers. */ +} pcsc; + +/* A structure to collect information pertaining to one reader + slot. */ +struct reader_table_s { + int used; /* True if slot is used. */ + unsigned short port; /* Port number: 0 = unused, 1 - dev/tty */ + + /* Function pointers initialized to the various backends. */ + int (*connect_card)(int); + int (*disconnect_card)(int); + int (*close_reader)(int); + int (*reset_reader)(int); + int (*get_status_reader)(int, unsigned int *, int); + int (*send_apdu_reader)(int,unsigned char *,size_t, + unsigned char *, size_t *, pininfo_t *); + int (*check_pinpad)(int, int, pininfo_t *); + void (*dump_status_reader)(int); + int (*set_progress_cb)(int, gcry_handler_progress_t, void*); + int (*set_prompt_cb)(int, void (*) (void *, int), void*); + int (*pinpad_verify)(int, int, int, int, int, pininfo_t *); + int (*pinpad_modify)(int, int, int, int, int, pininfo_t *); + + struct { + ccid_driver_t handle; + } ccid; + struct { + long card; + pcsc_dword_t protocol; + pcsc_dword_t verify_ioctl; + pcsc_dword_t modify_ioctl; + int pinmin; + int pinmax; + pcsc_dword_t current_state; + } pcsc; +#ifdef USE_G10CODE_RAPDU + struct { + rapdu_t handle; + } rapdu; +#endif /*USE_G10CODE_RAPDU*/ + char *rdrname; /* Name of the connected reader or NULL if unknown. */ + unsigned int is_t0:1; /* True if we know that we are running T=0. */ + unsigned int is_spr532:1; /* True if we know that the reader is a SPR532. */ + unsigned int pinpad_varlen_supported:1; /* True if we know that the reader + supports variable length pinpad + input. */ + unsigned int require_get_status:1; + unsigned char atr[33]; + size_t atrlen; /* A zero length indicates that the ATR has + not yet been read; i.e. the card is not + ready for use. */ +#ifdef USE_NPTH + npth_mutex_t lock; +#endif +}; +typedef struct reader_table_s *reader_table_t; + +/* A global table to keep track of active readers. */ +static struct reader_table_s reader_table[MAX_READER]; + +#ifdef USE_NPTH +static npth_mutex_t reader_table_lock; +#endif + + +/* PC/SC constants and function pointer. */ +#define PCSC_SCOPE_USER 0 +#define PCSC_SCOPE_TERMINAL 1 +#define PCSC_SCOPE_SYSTEM 2 +#define PCSC_SCOPE_GLOBAL 3 + +#define PCSC_PROTOCOL_T0 1 +#define PCSC_PROTOCOL_T1 2 +#ifdef HAVE_W32_SYSTEM +# define PCSC_PROTOCOL_RAW 0x00010000 /* The active protocol. */ +#else +# define PCSC_PROTOCOL_RAW 4 +#endif + +#define PCSC_SHARE_EXCLUSIVE 1 +#define PCSC_SHARE_SHARED 2 +#define PCSC_SHARE_DIRECT 3 + +#define PCSC_LEAVE_CARD 0 +#define PCSC_RESET_CARD 1 +#define PCSC_UNPOWER_CARD 2 +#define PCSC_EJECT_CARD 3 + +#ifdef HAVE_W32_SYSTEM +# define PCSC_UNKNOWN 0x0000 /* The driver is not aware of the status. */ +# define PCSC_ABSENT 0x0001 /* Card is absent. */ +# define PCSC_PRESENT 0x0002 /* Card is present. */ +# define PCSC_SWALLOWED 0x0003 /* Card is present and electrical connected. */ +# define PCSC_POWERED 0x0004 /* Card is powered. */ +# define PCSC_NEGOTIABLE 0x0005 /* Card is awaiting PTS. */ +# define PCSC_SPECIFIC 0x0006 /* Card is ready for use. */ +#else +# define PCSC_UNKNOWN 0x0001 +# define PCSC_ABSENT 0x0002 /* Card is absent. */ +# define PCSC_PRESENT 0x0004 /* Card is present. */ +# define PCSC_SWALLOWED 0x0008 /* Card is present and electrical connected. */ +# define PCSC_POWERED 0x0010 /* Card is powered. */ +# define PCSC_NEGOTIABLE 0x0020 /* Card is awaiting PTS. */ +# define PCSC_SPECIFIC 0x0040 /* Card is ready for use. */ +#endif + +#define PCSC_STATE_UNAWARE 0x0000 /* Want status. */ +#define PCSC_STATE_IGNORE 0x0001 /* Ignore this reader. */ +#define PCSC_STATE_CHANGED 0x0002 /* State has changed. */ +#define PCSC_STATE_UNKNOWN 0x0004 /* Reader unknown. */ +#define PCSC_STATE_UNAVAILABLE 0x0008 /* Status unavailable. */ +#define PCSC_STATE_EMPTY 0x0010 /* Card removed. */ +#define PCSC_STATE_PRESENT 0x0020 /* Card inserted. */ +#define PCSC_STATE_ATRMATCH 0x0040 /* ATR matches card. */ +#define PCSC_STATE_EXCLUSIVE 0x0080 /* Exclusive Mode. */ +#define PCSC_STATE_INUSE 0x0100 /* Shared mode. */ +#define PCSC_STATE_MUTE 0x0200 /* Unresponsive card. */ +#ifdef HAVE_W32_SYSTEM +# define PCSC_STATE_UNPOWERED 0x0400 /* Card not powerred up. */ +#endif + +/* Some PC/SC error codes. */ +#define PCSC_E_CANCELLED 0x80100002 +#define PCSC_E_CANT_DISPOSE 0x8010000E +#define PCSC_E_INSUFFICIENT_BUFFER 0x80100008 +#define PCSC_E_INVALID_ATR 0x80100015 +#define PCSC_E_INVALID_HANDLE 0x80100003 +#define PCSC_E_INVALID_PARAMETER 0x80100004 +#define PCSC_E_INVALID_TARGET 0x80100005 +#define PCSC_E_INVALID_VALUE 0x80100011 +#define PCSC_E_NO_MEMORY 0x80100006 +#define PCSC_E_UNKNOWN_READER 0x80100009 +#define PCSC_E_TIMEOUT 0x8010000A +#define PCSC_E_SHARING_VIOLATION 0x8010000B +#define PCSC_E_NO_SMARTCARD 0x8010000C +#define PCSC_E_UNKNOWN_CARD 0x8010000D +#define PCSC_E_PROTO_MISMATCH 0x8010000F +#define PCSC_E_NOT_READY 0x80100010 +#define PCSC_E_SYSTEM_CANCELLED 0x80100012 +#define PCSC_E_NOT_TRANSACTED 0x80100016 +#define PCSC_E_READER_UNAVAILABLE 0x80100017 +#define PCSC_E_NO_SERVICE 0x8010001D +#define PCSC_E_NO_READERS_AVAILABLE 0x8010002E +#define PCSC_E_SERVICE_STOPPED 0x8010001E +#define PCSC_W_RESET_CARD 0x80100068 +#define PCSC_W_REMOVED_CARD 0x80100069 + +/* Fix pcsc-lite ABI incompatibility. */ +#ifndef SCARD_CTL_CODE +#ifdef _WIN32 +#include <winioctl.h> +#define SCARD_CTL_CODE(code) CTL_CODE(FILE_DEVICE_SMARTCARD, (code), \ + METHOD_BUFFERED, FILE_ANY_ACCESS) +#else +#define SCARD_CTL_CODE(code) (0x42000000 + (code)) +#endif +#endif + +#define CM_IOCTL_GET_FEATURE_REQUEST SCARD_CTL_CODE(3400) +#define CM_IOCTL_VENDOR_IFD_EXCHANGE SCARD_CTL_CODE(1) +#define FEATURE_VERIFY_PIN_DIRECT 0x06 +#define FEATURE_MODIFY_PIN_DIRECT 0x07 +#define FEATURE_GET_TLV_PROPERTIES 0x12 + +#define PCSCv2_PART10_PROPERTY_bEntryValidationCondition 2 +#define PCSCv2_PART10_PROPERTY_bTimeOut2 3 +#define PCSCv2_PART10_PROPERTY_bMinPINSize 6 +#define PCSCv2_PART10_PROPERTY_bMaxPINSize 7 +#define PCSCv2_PART10_PROPERTY_wIdVendor 11 +#define PCSCv2_PART10_PROPERTY_wIdProduct 12 + + +/* The PC/SC error is defined as a long as per specs. Due to left + shifts bit 31 will get sign extended. We use this mask to fix + it. */ +#define PCSC_ERR_MASK(a) ((a) & 0xffffffff) + + +struct pcsc_io_request_s +{ + unsigned long protocol; + unsigned long pci_len; +}; + +typedef struct pcsc_io_request_s *pcsc_io_request_t; + +#ifdef __APPLE__ +#pragma pack(1) +#endif + +struct pcsc_readerstate_s +{ + const char *reader; + void *user_data; + pcsc_dword_t current_state; + pcsc_dword_t event_state; + pcsc_dword_t atrlen; + unsigned char atr[33]; +}; + +#ifdef __APPLE__ +#pragma pack() +#endif + +typedef struct pcsc_readerstate_s *pcsc_readerstate_t; + +long (* DLSTDCALL pcsc_establish_context) (pcsc_dword_t scope, + const void *reserved1, + const void *reserved2, + long *r_context); +long (* DLSTDCALL pcsc_release_context) (long context); +long (* DLSTDCALL pcsc_cancel) (long context); +long (* DLSTDCALL pcsc_list_readers) (long context, + const char *groups, + char *readers, pcsc_dword_t*readerslen); +long (* DLSTDCALL pcsc_get_status_change) (long context, + pcsc_dword_t timeout, + pcsc_readerstate_t readerstates, + pcsc_dword_t nreaderstates); +long (* DLSTDCALL pcsc_connect) (long context, + const char *reader, + pcsc_dword_t share_mode, + pcsc_dword_t preferred_protocols, + long *r_card, + pcsc_dword_t *r_active_protocol); +long (* DLSTDCALL pcsc_reconnect) (long card, + pcsc_dword_t share_mode, + pcsc_dword_t preferred_protocols, + pcsc_dword_t initialization, + pcsc_dword_t *r_active_protocol); +long (* DLSTDCALL pcsc_disconnect) (long card, + pcsc_dword_t disposition); +long (* DLSTDCALL pcsc_status) (long card, + char *reader, pcsc_dword_t *readerlen, + pcsc_dword_t *r_state, + pcsc_dword_t *r_protocol, + unsigned char *atr, pcsc_dword_t *atrlen); +long (* DLSTDCALL pcsc_begin_transaction) (long card); +long (* DLSTDCALL pcsc_end_transaction) (long card, + pcsc_dword_t disposition); +long (* DLSTDCALL pcsc_transmit) (long card, + const pcsc_io_request_t send_pci, + const unsigned char *send_buffer, + pcsc_dword_t send_len, + pcsc_io_request_t recv_pci, + unsigned char *recv_buffer, + pcsc_dword_t *recv_len); +long (* DLSTDCALL pcsc_set_timeout) (long context, + pcsc_dword_t timeout); +long (* DLSTDCALL pcsc_control) (long card, + pcsc_dword_t control_code, + const void *send_buffer, + pcsc_dword_t send_len, + void *recv_buffer, + pcsc_dword_t recv_len, + pcsc_dword_t *bytes_returned); + + +/* Prototypes. */ +static int pcsc_vendor_specific_init (int slot); +static int pcsc_get_status (int slot, unsigned int *status, int on_wire); +static int reset_pcsc_reader (int slot); +static int apdu_get_status_internal (int slot, int hang, unsigned int *status, + int on_wire); +static int check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo); +static int pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1, + pininfo_t *pininfo); +static int pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1, + pininfo_t *pininfo); + + + +/* + * Helper + */ + +/* Return true if (BUFFER,LENGTH) consists of only binary zeroes. */ +static int +all_zero_p (const void *buffer, size_t length) +{ + const unsigned char *p; + + for (p=buffer; length; p++, length--) + if (*p) + return 0; + return 1; +} + + + +static int +lock_slot (int slot) +{ +#ifdef USE_NPTH + int err; + + err = npth_mutex_lock (&reader_table[slot].lock); + if (err) + { + log_error ("failed to acquire apdu lock: %s\n", strerror (err)); + return SW_HOST_LOCKING_FAILED; + } +#endif /*USE_NPTH*/ + return 0; +} + +static int +trylock_slot (int slot) +{ +#ifdef USE_NPTH + int err; + + err = npth_mutex_trylock (&reader_table[slot].lock); + if (err == EBUSY) + return SW_HOST_BUSY; + else if (err) + { + log_error ("failed to acquire apdu lock: %s\n", strerror (err)); + return SW_HOST_LOCKING_FAILED; + } +#endif /*USE_NPTH*/ + return 0; +} + +static void +unlock_slot (int slot) +{ +#ifdef USE_NPTH + int err; + + err = npth_mutex_unlock (&reader_table[slot].lock); + if (err) + log_error ("failed to release apdu lock: %s\n", strerror (errno)); +#endif /*USE_NPTH*/ +} + + +/* Find an unused reader slot for PORTSTR and put it into the reader + table. Return -1 on error or the index into the reader table. + Acquire slot's lock on successful return. Caller needs to unlock it. */ +static int +new_reader_slot (void) +{ + int i, reader = -1; + + for (i=0; i < MAX_READER; i++) + if (!reader_table[i].used) + { + reader = i; + reader_table[reader].used = 1; + break; + } + + if (reader == -1) + { + log_error ("new_reader_slot: out of slots\n"); + return -1; + } + + if (lock_slot (reader)) + { + reader_table[reader].used = 0; + return -1; + } + + reader_table[reader].connect_card = NULL; + reader_table[reader].disconnect_card = NULL; + reader_table[reader].close_reader = NULL; + reader_table[reader].reset_reader = NULL; + reader_table[reader].get_status_reader = NULL; + reader_table[reader].send_apdu_reader = NULL; + reader_table[reader].check_pinpad = check_pcsc_pinpad; + reader_table[reader].dump_status_reader = NULL; + reader_table[reader].set_progress_cb = NULL; + reader_table[reader].set_prompt_cb = NULL; + reader_table[reader].pinpad_verify = pcsc_pinpad_verify; + reader_table[reader].pinpad_modify = pcsc_pinpad_modify; + + reader_table[reader].is_t0 = 1; + reader_table[reader].is_spr532 = 0; + reader_table[reader].pinpad_varlen_supported = 0; + reader_table[reader].require_get_status = 1; + reader_table[reader].pcsc.verify_ioctl = 0; + reader_table[reader].pcsc.modify_ioctl = 0; + reader_table[reader].pcsc.pinmin = -1; + reader_table[reader].pcsc.pinmax = -1; + reader_table[reader].pcsc.current_state = PCSC_STATE_UNAWARE; + + return reader; +} + + +static void +dump_reader_status (int slot) +{ + if (!opt.verbose) + return; + + if (reader_table[slot].dump_status_reader) + reader_table[slot].dump_status_reader (slot); + + if (reader_table[slot].atrlen) + { + log_info ("slot %d: ATR=", slot); + log_printhex (reader_table[slot].atr, reader_table[slot].atrlen, ""); + } +} + + + +static const char * +host_sw_string (long err) +{ + switch (err) + { + case 0: return "okay"; + case SW_HOST_OUT_OF_CORE: return "out of core"; + case SW_HOST_INV_VALUE: return "invalid value"; + case SW_HOST_NO_DRIVER: return "no driver"; + case SW_HOST_NOT_SUPPORTED: return "not supported"; + case SW_HOST_LOCKING_FAILED: return "locking failed"; + case SW_HOST_BUSY: return "busy"; + case SW_HOST_NO_CARD: return "no card"; + case SW_HOST_CARD_INACTIVE: return "card inactive"; + case SW_HOST_CARD_IO_ERROR: return "card I/O error"; + case SW_HOST_GENERAL_ERROR: return "general error"; + case SW_HOST_NO_READER: return "no reader"; + case SW_HOST_ABORTED: return "aborted"; + case SW_HOST_NO_PINPAD: return "no pinpad"; + case SW_HOST_ALREADY_CONNECTED: return "already connected"; + case SW_HOST_CANCELLED: return "cancelled"; + case SW_HOST_USB_OTHER: return "USB general error"; + case SW_HOST_USB_IO: return "USB I/O error"; + case SW_HOST_USB_ACCESS: return "USB permission denied"; + case SW_HOST_USB_NO_DEVICE:return "USB no device"; + case SW_HOST_USB_BUSY: return "USB busy"; + case SW_HOST_USB_TIMEOUT: return "USB timeout"; + case SW_HOST_USB_OVERFLOW: return "USB overflow"; + default: return "unknown host status error"; + } +} + + +const char * +apdu_strerror (int rc) +{ + switch (rc) + { + case SW_EOF_REACHED : return "eof reached"; + case SW_EEPROM_FAILURE : return "eeprom failure"; + case SW_WRONG_LENGTH : return "wrong length"; + case SW_SM_NOT_SUP : return "secure messaging not supported"; + case SW_CC_NOT_SUP : return "command chaining not supported"; + case SW_FILE_STRUCT : return "command can't be used for file structure."; + case SW_CHV_WRONG : return "CHV wrong"; + case SW_CHV_BLOCKED : return "CHV blocked"; + case SW_REF_DATA_INV : return "referenced data invalidated"; + case SW_USE_CONDITIONS : return "use conditions not satisfied"; + case SW_NO_CURRENT_EF : return "no current EF selected"; + case SW_BAD_PARAMETER : return "bad parameter"; + case SW_NOT_SUPPORTED : return "not supported"; + case SW_FILE_NOT_FOUND : return "file not found"; + case SW_RECORD_NOT_FOUND:return "record not found"; + case SW_REF_NOT_FOUND : return "reference not found"; + case SW_NOT_ENOUGH_MEMORY: return "not enough memory space in the file"; + case SW_INCONSISTENT_LC: return "Lc inconsistent with TLV structure."; + case SW_INCORRECT_P0_P1: return "incorrect parameters P0,P1"; + case SW_BAD_LC : return "Lc inconsistent with P0,P1"; + case SW_BAD_P0_P1 : return "bad P0,P1"; + case SW_INS_NOT_SUP : return "instruction not supported"; + case SW_CLA_NOT_SUP : return "class not supported"; + case SW_SUCCESS : return "success"; + default: + if ((rc & ~0x00ff) == SW_MORE_DATA) + return "more data available"; + if ( (rc & 0x10000) ) + return host_sw_string (rc); + return "unknown status error"; + } +} + +/* + PC/SC Interface + */ + +static const char * +pcsc_error_string (long err) +{ + const char *s; + + if (!err) + return "okay"; + if ((err & 0x80100000) != 0x80100000) + return "invalid PC/SC error code"; + err &= 0xffff; + switch (err) + { + case 0x0002: s = "cancelled"; break; + case 0x000e: s = "can't dispose"; break; + case 0x0008: s = "insufficient buffer"; break; + case 0x0015: s = "invalid ATR"; break; + case 0x0003: s = "invalid handle"; break; + case 0x0004: s = "invalid parameter"; break; + case 0x0005: s = "invalid target"; break; + case 0x0011: s = "invalid value"; break; + case 0x0006: s = "no memory"; break; + case 0x0013: s = "comm error"; break; + case 0x0001: s = "internal error"; break; + case 0x0014: s = "unknown error"; break; + case 0x0007: s = "waited too long"; break; + case 0x0009: s = "unknown reader"; break; + case 0x000a: s = "timeout"; break; + case 0x000b: s = "sharing violation"; break; + case 0x000c: s = "no smartcard"; break; + case 0x000d: s = "unknown card"; break; + case 0x000f: s = "proto mismatch"; break; + case 0x0010: s = "not ready"; break; + case 0x0012: s = "system cancelled"; break; + case 0x0016: s = "not transacted"; break; + case 0x0017: s = "reader unavailable"; break; + case 0x0065: s = "unsupported card"; break; + case 0x0066: s = "unresponsive card"; break; + case 0x0067: s = "unpowered card"; break; + case 0x0068: s = "reset card"; break; + case 0x0069: s = "removed card"; break; + case 0x006a: s = "inserted card"; break; + case 0x001f: s = "unsupported feature"; break; + case 0x0019: s = "PCI too small"; break; + case 0x001a: s = "reader unsupported"; break; + case 0x001b: s = "duplicate reader"; break; + case 0x001c: s = "card unsupported"; break; + case 0x001d: s = "no service"; break; + case 0x001e: s = "service stopped"; break; + case 0x002e: s = "no readers available"; break; + default: s = "unknown PC/SC error code"; break; + } + return s; +} + +/* Map PC/SC error codes to our special host status words. */ +static int +pcsc_error_to_sw (long ec) +{ + int rc; + + switch ( PCSC_ERR_MASK (ec) ) + { + case 0: rc = 0; break; + + case PCSC_E_CANCELLED: rc = SW_HOST_CANCELLED; break; + case PCSC_E_NO_MEMORY: rc = SW_HOST_OUT_OF_CORE; break; + case PCSC_E_TIMEOUT: rc = SW_HOST_CARD_IO_ERROR; break; + case PCSC_E_NO_SERVICE: + case PCSC_E_SERVICE_STOPPED: + case PCSC_E_UNKNOWN_READER: rc = SW_HOST_NO_READER; break; + case PCSC_E_SHARING_VIOLATION: rc = SW_HOST_LOCKING_FAILED; break; + case PCSC_E_NO_SMARTCARD: rc = SW_HOST_NO_CARD; break; + case PCSC_W_REMOVED_CARD: rc = SW_HOST_NO_CARD; break; + + case PCSC_E_INVALID_TARGET: + case PCSC_E_INVALID_VALUE: + case PCSC_E_INVALID_HANDLE: + case PCSC_E_INVALID_PARAMETER: + case PCSC_E_INSUFFICIENT_BUFFER: rc = SW_HOST_INV_VALUE; break; + + default: rc = SW_HOST_GENERAL_ERROR; break; + } + + return rc; +} + +static void +dump_pcsc_reader_status (int slot) +{ + if (reader_table[slot].pcsc.card) + { + log_info ("reader slot %d: active protocol:", slot); + if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T0)) + log_printf (" T0"); + else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1)) + log_printf (" T1"); + else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_RAW)) + log_printf (" raw"); + log_printf ("\n"); + } + else + log_info ("reader slot %d: not connected\n", slot); +} + + +static int +pcsc_get_status (int slot, unsigned int *status, int on_wire) +{ + long err; + struct pcsc_readerstate_s rdrstates[1]; + + (void)on_wire; + memset (rdrstates, 0, sizeof *rdrstates); + rdrstates[0].reader = reader_table[slot].rdrname; + rdrstates[0].current_state = reader_table[slot].pcsc.current_state; + err = pcsc_get_status_change (pcsc.context, 0, rdrstates, 1); + if (err == PCSC_E_TIMEOUT) + err = 0; /* Timeout is no error here. */ + if (err) + { + log_error ("pcsc_get_status_change failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + return pcsc_error_to_sw (err); + } + + if ((rdrstates[0].event_state & PCSC_STATE_CHANGED)) + reader_table[slot].pcsc.current_state = + (rdrstates[0].event_state & ~PCSC_STATE_CHANGED); + + if (DBG_READER) + log_debug + ("pcsc_get_status_change: %s%s%s%s%s%s%s%s%s%s\n", + (rdrstates[0].event_state & PCSC_STATE_IGNORE)? " ignore":"", + (rdrstates[0].event_state & PCSC_STATE_CHANGED)? " changed":"", + (rdrstates[0].event_state & PCSC_STATE_UNKNOWN)? " unknown":"", + (rdrstates[0].event_state & PCSC_STATE_UNAVAILABLE)?" unavail":"", + (rdrstates[0].event_state & PCSC_STATE_EMPTY)? " empty":"", + (rdrstates[0].event_state & PCSC_STATE_PRESENT)? " present":"", + (rdrstates[0].event_state & PCSC_STATE_ATRMATCH)? " atr":"", + (rdrstates[0].event_state & PCSC_STATE_EXCLUSIVE)? " excl":"", + (rdrstates[0].event_state & PCSC_STATE_INUSE)? " inuse":"", + (rdrstates[0].event_state & PCSC_STATE_MUTE)? " mute":"" ); + + *status = 0; + if ( (reader_table[slot].pcsc.current_state & PCSC_STATE_PRESENT) ) + { + *status |= APDU_CARD_PRESENT; + if ( !(reader_table[slot].pcsc.current_state & PCSC_STATE_MUTE) ) + *status |= APDU_CARD_ACTIVE; + } +#ifndef HAVE_W32_SYSTEM + /* We indicate a useful card if it is not in use by another + application. This is because we only use exclusive access + mode. */ + if ( (*status & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE)) + == (APDU_CARD_PRESENT|APDU_CARD_ACTIVE) + && (opt.pcsc_shared + || !(reader_table[slot].pcsc.current_state & PCSC_STATE_INUSE))) + *status |= APDU_CARD_USABLE; +#else + /* Some winscard drivers may set EXCLUSIVE and INUSE at the same + time when we are the only user (SCM SCR335) under Windows. */ + if ((*status & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE)) + == (APDU_CARD_PRESENT|APDU_CARD_ACTIVE)) + *status |= APDU_CARD_USABLE; +#endif + + if (!on_wire && (rdrstates[0].event_state & PCSC_STATE_CHANGED)) + /* Event like sleep/resume occurs, which requires RESET. */ + return SW_HOST_NO_READER; + else + return 0; +} + + +/* Send the APDU of length APDULEN to SLOT and return a maximum of + *BUFLEN data in BUFFER, the actual returned size will be stored at + BUFLEN. Returns: A status word. */ +static int +pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen, + unsigned char *buffer, size_t *buflen, + pininfo_t *pininfo) +{ + long err; + struct pcsc_io_request_s send_pci; + pcsc_dword_t recv_len; + + (void)pininfo; + + if (!reader_table[slot].atrlen + && (err = reset_pcsc_reader (slot))) + return err; + + if (DBG_CARD_IO) + log_printhex (apdu, apdulen, " PCSC_data:"); + + if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1)) + send_pci.protocol = PCSC_PROTOCOL_T1; + else + send_pci.protocol = PCSC_PROTOCOL_T0; + send_pci.pci_len = sizeof send_pci; + recv_len = *buflen; + err = pcsc_transmit (reader_table[slot].pcsc.card, + &send_pci, apdu, apdulen, + NULL, buffer, &recv_len); + *buflen = recv_len; + if (err) + log_error ("pcsc_transmit failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + + /* Handle fatal errors which require shutdown of reader. */ + if (err == PCSC_E_NOT_TRANSACTED || err == PCSC_W_RESET_CARD + || err == PCSC_W_REMOVED_CARD) + { + reader_table[slot].pcsc.current_state = PCSC_STATE_UNAWARE; + scd_kick_the_loop (); + } + + return pcsc_error_to_sw (err); +} + + +/* Do some control with the value of IOCTL_CODE to the card inserted + to SLOT. Input buffer is specified by CNTLBUF of length LEN. + Output buffer is specified by BUFFER of length *BUFLEN, and the + actual output size will be stored at BUFLEN. Returns: A status word. + This routine is used for PIN pad input support. */ +static int +control_pcsc (int slot, pcsc_dword_t ioctl_code, + const unsigned char *cntlbuf, size_t len, + unsigned char *buffer, pcsc_dword_t *buflen) +{ + long err; + + err = pcsc_control (reader_table[slot].pcsc.card, ioctl_code, + cntlbuf, len, buffer, buflen? *buflen:0, buflen); + if (err) + { + log_error ("pcsc_control failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + return pcsc_error_to_sw (err); + } + + return 0; +} + + +static int +close_pcsc_reader (int slot) +{ + (void)slot; + + if (pcsc.context_valid) + { + pcsc_release_context (pcsc.context); + pcsc.context_valid = 0; + } + return 0; +} + + +/* Connect a PC/SC card. */ +static int +connect_pcsc_card (int slot) +{ + long err; + + log_assert (slot >= 0 && slot < MAX_READER); + + if (reader_table[slot].pcsc.card) + return SW_HOST_ALREADY_CONNECTED; + + reader_table[slot].atrlen = 0; + reader_table[slot].is_t0 = 0; + + err = pcsc_connect (pcsc.context, + reader_table[slot].rdrname, + opt.pcsc_shared? PCSC_SHARE_SHARED:PCSC_SHARE_EXCLUSIVE, + PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1, + &reader_table[slot].pcsc.card, + &reader_table[slot].pcsc.protocol); + if (err) + { + reader_table[slot].pcsc.card = 0; + if (err != PCSC_E_NO_SMARTCARD) + log_error ("pcsc_connect failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + if (err == PCSC_W_REMOVED_CARD && pcsc_cancel) + { + long err2; + err2 = pcsc_cancel (pcsc.context); + if (err2) + log_error ("pcsc_cancel failed: %s (0x%lx)\n", + pcsc_error_string (err2), err2); + else if (opt.verbose) + log_error ("pcsc_cancel succeeded\n"); + } + } + else + { + char reader[250]; + pcsc_dword_t readerlen, atrlen; + pcsc_dword_t card_state, card_protocol; + + pcsc_vendor_specific_init (slot); + + atrlen = DIM (reader_table[0].atr); + readerlen = sizeof reader - 1; + err = pcsc_status (reader_table[slot].pcsc.card, + reader, &readerlen, + &card_state, &card_protocol, + reader_table[slot].atr, &atrlen); + if (err) + log_error ("pcsc_status failed: %s (0x%lx) %lu\n", + pcsc_error_string (err), err, (long unsigned int)readerlen); + else + { + if (atrlen > DIM (reader_table[0].atr)) + log_bug ("ATR returned by pcsc_status is too large\n"); + reader_table[slot].atrlen = atrlen; + reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0); + } + } + + dump_reader_status (slot); + return pcsc_error_to_sw (err); +} + + +static int +disconnect_pcsc_card (int slot) +{ + long err; + + log_assert (slot >= 0 && slot < MAX_READER); + + if (!reader_table[slot].pcsc.card) + return 0; + + err = pcsc_disconnect (reader_table[slot].pcsc.card, PCSC_LEAVE_CARD); + if (err) + { + log_error ("pcsc_disconnect failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + return SW_HOST_CARD_IO_ERROR; + } + reader_table[slot].pcsc.card = 0; + return 0; +} + + +/* Send an PC/SC reset command and return a status word on error or 0 + on success. */ +static int +reset_pcsc_reader (int slot) +{ + int sw; + + sw = disconnect_pcsc_card (slot); + if (!sw) + sw = connect_pcsc_card (slot); + + return sw; +} + + +/* Examine reader specific parameters and initialize. This is mostly + for pinpad input. Called at opening the connection to the reader. */ +static int +pcsc_vendor_specific_init (int slot) +{ + unsigned char buf[256]; + pcsc_dword_t len; + int sw; + int vendor = 0; + int product = 0; + pcsc_dword_t get_tlv_ioctl = (pcsc_dword_t)-1; + unsigned char *p; + + len = sizeof (buf); + sw = control_pcsc (slot, CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0, buf, &len); + if (sw) + { + log_error ("pcsc_vendor_specific_init: GET_FEATURE_REQUEST failed: %d\n", + sw); + return SW_NOT_SUPPORTED; + } + else + { + p = buf; + while (p < buf + len) + { + unsigned char code = *p++; + int l = *p++; + unsigned int v = 0; + + if (l == 1) + v = p[0]; + else if (l == 2) + v = buf16_to_uint (p); + else if (l == 4) + v = buf32_to_uint (p); + + if (code == FEATURE_VERIFY_PIN_DIRECT) + reader_table[slot].pcsc.verify_ioctl = v; + else if (code == FEATURE_MODIFY_PIN_DIRECT) + reader_table[slot].pcsc.modify_ioctl = v; + else if (code == FEATURE_GET_TLV_PROPERTIES) + get_tlv_ioctl = v; + + if (DBG_CARD_IO) + log_debug ("feature: code=%02X, len=%d, v=%02X\n", code, l, v); + + p += l; + } + } + + if (get_tlv_ioctl == (pcsc_dword_t)-1) + { + /* + * For system which doesn't support GET_TLV_PROPERTIES, + * we put some heuristics here. + */ + if (reader_table[slot].rdrname) + { + if (strstr (reader_table[slot].rdrname, "SPRx32")) + { + reader_table[slot].is_spr532 = 1; + reader_table[slot].pinpad_varlen_supported = 1; + } + else if (strstr (reader_table[slot].rdrname, "ST-2xxx")) + { + reader_table[slot].pcsc.pinmax = 15; + reader_table[slot].pinpad_varlen_supported = 1; + } + else if (strstr (reader_table[slot].rdrname, "cyberJack") + || strstr (reader_table[slot].rdrname, "DIGIPASS") + || strstr (reader_table[slot].rdrname, "Gnuk") + || strstr (reader_table[slot].rdrname, "KAAN") + || strstr (reader_table[slot].rdrname, "Trustica")) + reader_table[slot].pinpad_varlen_supported = 1; + } + + return 0; + } + + len = sizeof (buf); + sw = control_pcsc (slot, get_tlv_ioctl, NULL, 0, buf, &len); + if (sw) + { + log_error ("pcsc_vendor_specific_init: GET_TLV_IOCTL failed: %d\n", sw); + return SW_NOT_SUPPORTED; + } + + p = buf; + while (p < buf + len) + { + unsigned char tag = *p++; + int l = *p++; + unsigned int v = 0; + + /* Umm... here is little endian, while the encoding above is big. */ + if (l == 1) + v = p[0]; + else if (l == 2) + v = (((unsigned int)p[1] << 8) | p[0]); + else if (l == 4) + v = (((unsigned int)p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]); + + if (tag == PCSCv2_PART10_PROPERTY_bMinPINSize) + reader_table[slot].pcsc.pinmin = v; + else if (tag == PCSCv2_PART10_PROPERTY_bMaxPINSize) + reader_table[slot].pcsc.pinmax = v; + else if (tag == PCSCv2_PART10_PROPERTY_wIdVendor) + vendor = v; + else if (tag == PCSCv2_PART10_PROPERTY_wIdProduct) + product = v; + + if (DBG_CARD_IO) + log_debug ("TLV properties: tag=%02X, len=%d, v=%08X\n", tag, l, v); + + p += l; + } + + if (vendor == VENDOR_VEGA && product == VEGA_ALPHA) + { + /* + * Please read the comment of ccid_vendor_specific_init in + * ccid-driver.c. + */ + const unsigned char cmd[] = { '\xb5', '\x01', '\x00', '\x03', '\x00' }; + sw = control_pcsc (slot, CM_IOCTL_VENDOR_IFD_EXCHANGE, + cmd, sizeof (cmd), NULL, 0); + if (sw) + return SW_NOT_SUPPORTED; + } + else if (vendor == VENDOR_SCM && product == SCM_SPR532) /* SCM SPR532 */ + { + reader_table[slot].is_spr532 = 1; + reader_table[slot].pinpad_varlen_supported = 1; + } + else if (vendor == 0x046a) + { + /* Cherry ST-2xxx (product == 0x003e) supports TPDU level + * exchange. Other products which only support short APDU level + * exchange only work with shorter keys like RSA 1024. + */ + reader_table[slot].pcsc.pinmax = 15; + reader_table[slot].pinpad_varlen_supported = 1; + } + else if (vendor == 0x0c4b /* Tested with Reiner cyberJack GO */ + || vendor == 0x1a44 /* Tested with Vasco DIGIPASS 920 */ + || vendor == 0x234b /* Tested with FSIJ Gnuk Token */ + || vendor == 0x0d46 /* Tested with KAAN Advanced??? */ + || (vendor == 0x1fc9 && product == 0x81e6) /* Tested with Trustica Cryptoucan */) + reader_table[slot].pinpad_varlen_supported = 1; + + return 0; +} + +static int +pcsc_init (void) +{ + static int pcsc_api_loaded; + long err; + + /* Load the PC/SC API */ + if (!pcsc_api_loaded) + { + void *handle; + + handle = dlopen (opt.pcsc_driver, RTLD_LAZY); + if (!handle) + { + log_error ("pscd_open_reader: failed to open driver '%s': %s\n", + opt.pcsc_driver, dlerror ()); + return -1; + } + + pcsc_establish_context = dlsym (handle, "SCardEstablishContext"); + pcsc_release_context = dlsym (handle, "SCardReleaseContext"); + pcsc_cancel = dlsym (handle, "SCardCancel"); + pcsc_list_readers = dlsym (handle, "SCardListReaders"); +#if defined(_WIN32) || defined(__CYGWIN__) + if (!pcsc_list_readers) + pcsc_list_readers = dlsym (handle, "SCardListReadersA"); +#endif + pcsc_get_status_change = dlsym (handle, "SCardGetStatusChange"); +#if defined(_WIN32) || defined(__CYGWIN__) + if (!pcsc_get_status_change) + pcsc_get_status_change = dlsym (handle, "SCardGetStatusChangeA"); +#endif + pcsc_connect = dlsym (handle, "SCardConnect"); +#if defined(_WIN32) || defined(__CYGWIN__) + if (!pcsc_connect) + pcsc_connect = dlsym (handle, "SCardConnectA"); +#endif + pcsc_reconnect = dlsym (handle, "SCardReconnect"); +#if defined(_WIN32) || defined(__CYGWIN__) + if (!pcsc_reconnect) + pcsc_reconnect = dlsym (handle, "SCardReconnectA"); +#endif + pcsc_disconnect = dlsym (handle, "SCardDisconnect"); + pcsc_status = dlsym (handle, "SCardStatus"); +#if defined(_WIN32) || defined(__CYGWIN__) + if (!pcsc_status) + pcsc_status = dlsym (handle, "SCardStatusA"); +#endif + pcsc_begin_transaction = dlsym (handle, "SCardBeginTransaction"); + pcsc_end_transaction = dlsym (handle, "SCardEndTransaction"); + pcsc_transmit = dlsym (handle, "SCardTransmit"); + pcsc_set_timeout = dlsym (handle, "SCardSetTimeout"); + pcsc_control = dlsym (handle, "SCardControl"); + + if (!pcsc_establish_context + || !pcsc_release_context + || !pcsc_list_readers + || !pcsc_get_status_change + || !pcsc_connect + || !pcsc_reconnect + || !pcsc_disconnect + || !pcsc_status + || !pcsc_begin_transaction + || !pcsc_end_transaction + || !pcsc_transmit + || !pcsc_control + /* || !pcsc_set_timeout */) + { + /* Note that set_timeout is currently not used and also not + available under Windows. */ + log_error ("pcsc_open_reader: invalid PC/SC driver " + "(%d%d%d%d%d%d%d%d%d%d%d%d%d)\n", + !!pcsc_establish_context, + !!pcsc_release_context, + !!pcsc_list_readers, + !!pcsc_get_status_change, + !!pcsc_connect, + !!pcsc_reconnect, + !!pcsc_disconnect, + !!pcsc_status, + !!pcsc_begin_transaction, + !!pcsc_end_transaction, + !!pcsc_transmit, + !!pcsc_set_timeout, + !!pcsc_control ); + dlclose (handle); + return -1; + } + pcsc_api_loaded = 1; + } + + pcsc.context_valid = 0; + err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL, &pcsc.context); + if (err) + { + log_error ("pcsc_establish_context failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + return -1; + } + pcsc.context_valid = 1; + + return 0; +} + + +/* Select a reader from list of readers available. */ +static const char * +select_a_reader (const char *list, unsigned int len) +{ + const char *black_list_to_skip[] = { + /* We do left match by strncmp(3). */ + "Windows Hello" + }; + const char *white_list_to_prefer[] = { + /* We do substring match by strstr(3). */ + "SPRx32", + "Yubico" + }; + const char *p = list; + const char *candidate = NULL; + unsigned int n; + + /* + * (1) If one in the white list is found in LIST, that one is + * selected. + * (2) Otherwise, if one not in the black list is found in LIST, + * that is a candidate. + * (3) Select the first candidate, or in case of no candidate, + * return the first entry even if it's in the black list. + */ + while (len) + { + int i; + int is_bad; + + if (!*p) + break; + + for (n=0; n < len; n++) + if (!p[n]) + break; + + /* Something wrong in the LIST. */ + if (n >= len) + break; + + for (i = 0; i < DIM (white_list_to_prefer); i++) + if (strstr (p, white_list_to_prefer[i])) + return p; + + is_bad = 0; + for (i = 0; i < DIM (black_list_to_skip); i++) + if (!strncmp (p, black_list_to_skip[i], + strlen (black_list_to_skip[i]))) + is_bad = 1; + + if (!is_bad && !candidate) + candidate = p; + + len -= n + 1; + p += n + 1; + } + + if (candidate) + return candidate; + + return list; +} + + +/* Open the PC/SC reader. If PORTSTR is NULL we default to a suitable + port. Returns -1 on error or a slot number for the reader. */ +static int +open_pcsc_reader (const char *portstr) +{ + long err; + int slot; + char *list = NULL; + const char *rdrname = NULL; + pcsc_dword_t nreader = 0; + const char *p; + size_t n; + membuf_t reader_mb; + + xfree (pcsc.reader_list); + pcsc.reader_list = NULL; + + if (!pcsc.context_valid) + if (pcsc_init () < 0) + return -1; + + if (DBG_READER) + log_debug ("open_pcsc_reader(portstr=%s)\n", portstr); + + + slot = new_reader_slot (); + if (slot == -1) + return -1; /* No need to cleanup here. */ + + err = pcsc_list_readers (pcsc.context, NULL, NULL, &nreader); + if (!err) + { + list = xtrymalloc (nreader+1); /* Better add 1 for safety reasons. */ + if (!list) + { + log_error ("error allocating memory for reader list\n"); + close_pcsc_reader (slot); + reader_table[slot].used = 0; + unlock_slot (slot); + slot = -1 /*SW_HOST_OUT_OF_CORE*/; + goto leave; + } + err = pcsc_list_readers (pcsc.context, NULL, list, &nreader); + } + if (err) + { + log_error ("pcsc_list_readers failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + close_pcsc_reader (slot); + reader_table[slot].used = 0; + unlock_slot (slot); + xfree (list); + slot = -1; + goto leave; + } + + init_membuf (&reader_mb, 256); + + p = list; + while (nreader > 0) + { + if (!*p) + break; + + for (n=0; n < nreader; n++) + if (!p[n]) + break; + + if (n >= nreader) + { + log_error ("invalid response from pcsc_list_readers\n"); + xfree (get_membuf (&reader_mb, NULL)); + close_pcsc_reader (slot); + reader_table[slot].used = 0; + unlock_slot (slot); + xfree (list); + slot = -1; + goto leave; + } + + log_info ("detected reader '%s'\n", p); + put_membuf_str (&reader_mb, p); + put_membuf (&reader_mb, "\n", 1); + if (!rdrname && portstr && !strncmp (p, portstr, strlen (portstr))) + rdrname = p; + nreader -= n + 1; + p += n + 1; + } + put_membuf (&reader_mb, "", 1); + pcsc.reader_list = get_membuf (&reader_mb, NULL); + if (!pcsc.reader_list) + log_error ("error allocating memory for reader list\n"); + + if (!rdrname) + rdrname = select_a_reader (list, nreader); + + reader_table[slot].rdrname = xtrystrdup (rdrname); + if (!reader_table[slot].rdrname) + { + log_error ("error allocating memory for reader name\n"); + close_pcsc_reader (slot); + reader_table[slot].used = 0; + unlock_slot (slot); + slot = -1; + xfree (list); + goto leave; + } + xfree (list); + list = NULL; + + reader_table[slot].pcsc.card = 0; + reader_table[slot].atrlen = 0; + + reader_table[slot].connect_card = connect_pcsc_card; + reader_table[slot].disconnect_card = disconnect_pcsc_card; + reader_table[slot].close_reader = close_pcsc_reader; + reader_table[slot].reset_reader = reset_pcsc_reader; + reader_table[slot].get_status_reader = pcsc_get_status; + reader_table[slot].send_apdu_reader = pcsc_send_apdu; + reader_table[slot].dump_status_reader = dump_pcsc_reader_status; + + dump_reader_status (slot); + unlock_slot (slot); + + leave: + if (DBG_READER) + log_debug ("open_pcsc_reader => slot=%d\n", slot); + return slot; +} + + +/* Check whether the reader supports the ISO command code COMMAND + on the pinpad. Return 0 on success. */ +static int +check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo) +{ + int r; + + if (reader_table[slot].pcsc.pinmin >= 0) + pininfo->minlen = reader_table[slot].pcsc.pinmin; + + if (reader_table[slot].pcsc.pinmax >= 0) + pininfo->maxlen = reader_table[slot].pcsc.pinmax; + + if (!pininfo->minlen) + pininfo->minlen = 1; + if (!pininfo->maxlen) + pininfo->maxlen = 15; + + if ((command == ISO7816_VERIFY && reader_table[slot].pcsc.verify_ioctl != 0) + || (command == ISO7816_CHANGE_REFERENCE_DATA + && reader_table[slot].pcsc.modify_ioctl != 0)) + r = 0; /* Success */ + else + r = SW_NOT_SUPPORTED; + + if (DBG_CARD_IO) + log_debug ("check_pcsc_pinpad: command=%02X, r=%d\n", + (unsigned int)command, r); + + if (reader_table[slot].pinpad_varlen_supported) + pininfo->fixedlen = 0; + + return r; +} + +#define PIN_VERIFY_STRUCTURE_SIZE 24 +static int +pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1, + pininfo_t *pininfo) +{ + int sw; + unsigned char *pin_verify; + int len = PIN_VERIFY_STRUCTURE_SIZE + pininfo->fixedlen; + /* + * The result buffer is only expected to have two-byte result on + * return. However, some implementation uses this buffer for lower + * layer too and it assumes that there is enough space for lower + * layer communication. Such an implementation fails for TPDU + * readers with "insufficient buffer", as it needs header and + * trailer. Six is the number for header + result + trailer (TPDU). + */ + unsigned char result[6]; + pcsc_dword_t resultlen = 6; + int no_lc; + + if (!reader_table[slot].atrlen + && (sw = reset_pcsc_reader (slot))) + return sw; + + if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16) + return SW_NOT_SUPPORTED; + + pin_verify = xtrymalloc (len); + if (!pin_verify) + return SW_HOST_OUT_OF_CORE; + + no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532); + + pin_verify[0] = 0x00; /* bTimeOut */ + pin_verify[1] = 0x00; /* bTimeOut2 */ + pin_verify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */ + pin_verify[3] = pininfo->fixedlen; /* bmPINBlockString */ + pin_verify[4] = 0x00; /* bmPINLengthFormat */ + pin_verify[5] = pininfo->maxlen; /* wPINMaxExtraDigit */ + pin_verify[6] = pininfo->minlen; /* wPINMaxExtraDigit */ + pin_verify[7] = 0x02; /* bEntryValidationCondition: Validation key pressed */ + if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen) + pin_verify[7] |= 0x01; /* Max size reached. */ + pin_verify[8] = 0x01; /* bNumberMessage: One message */ + pin_verify[9] = 0x09; /* wLangId: 0x0409: US English */ + pin_verify[10] = 0x04; /* wLangId: 0x0409: US English */ + pin_verify[11] = 0x00; /* bMsgIndex */ + pin_verify[12] = 0x00; /* bTeoPrologue[0] */ + pin_verify[13] = 0x00; /* bTeoPrologue[1] */ + pin_verify[14] = pininfo->fixedlen + 0x05 - no_lc; /* bTeoPrologue[2] */ + pin_verify[15] = pininfo->fixedlen + 0x05 - no_lc; /* ulDataLength */ + pin_verify[16] = 0x00; /* ulDataLength */ + pin_verify[17] = 0x00; /* ulDataLength */ + pin_verify[18] = 0x00; /* ulDataLength */ + pin_verify[19] = class; /* abData[0] */ + pin_verify[20] = ins; /* abData[1] */ + pin_verify[21] = p0; /* abData[2] */ + pin_verify[22] = p1; /* abData[3] */ + pin_verify[23] = pininfo->fixedlen; /* abData[4] */ + if (pininfo->fixedlen) + memset (&pin_verify[24], 0xff, pininfo->fixedlen); + else if (no_lc) + len--; + + if (DBG_CARD_IO) + log_debug ("send secure: c=%02X i=%02X p1=%02X p2=%02X len=%d pinmax=%d\n", + class, ins, p0, p1, len, pininfo->maxlen); + + sw = control_pcsc (slot, reader_table[slot].pcsc.verify_ioctl, + pin_verify, len, result, &resultlen); + xfree (pin_verify); + if (sw || resultlen < 2) + { + log_error ("control_pcsc failed: %d\n", sw); + return sw? sw: SW_HOST_INCOMPLETE_CARD_RESPONSE; + } + sw = (result[resultlen-2] << 8) | result[resultlen-1]; + if (DBG_CARD_IO) + log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen); + return sw; +} + + +#define PIN_MODIFY_STRUCTURE_SIZE 29 +static int +pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1, + pininfo_t *pininfo) +{ + int sw; + unsigned char *pin_modify; + int len = PIN_MODIFY_STRUCTURE_SIZE + 2 * pininfo->fixedlen; + unsigned char result[6]; /* See the comment at pinpad_verify. */ + pcsc_dword_t resultlen = 6; + int no_lc; + + if (!reader_table[slot].atrlen + && (sw = reset_pcsc_reader (slot))) + return sw; + + if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16) + return SW_NOT_SUPPORTED; + + pin_modify = xtrymalloc (len); + if (!pin_modify) + return SW_HOST_OUT_OF_CORE; + + no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532); + + pin_modify[0] = 0x00; /* bTimeOut */ + pin_modify[1] = 0x00; /* bTimeOut2 */ + pin_modify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */ + pin_modify[3] = pininfo->fixedlen; /* bmPINBlockString */ + pin_modify[4] = 0x00; /* bmPINLengthFormat */ + pin_modify[5] = 0x00; /* bInsertionOffsetOld */ + pin_modify[6] = pininfo->fixedlen; /* bInsertionOffsetNew */ + pin_modify[7] = pininfo->maxlen; /* wPINMaxExtraDigit */ + pin_modify[8] = pininfo->minlen; /* wPINMaxExtraDigit */ + pin_modify[9] = (p0 == 0 ? 0x03 : 0x01); + /* bConfirmPIN + * 0x00: new PIN once + * 0x01: new PIN twice (confirmation) + * 0x02: old PIN and new PIN once + * 0x03: old PIN and new PIN twice (confirmation) + */ + pin_modify[10] = 0x02; /* bEntryValidationCondition: Validation key pressed */ + if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen) + pin_modify[10] |= 0x01; /* Max size reached. */ + pin_modify[11] = 0x03; /* bNumberMessage: Three messages */ + pin_modify[12] = 0x09; /* wLangId: 0x0409: US English */ + pin_modify[13] = 0x04; /* wLangId: 0x0409: US English */ + pin_modify[14] = 0x00; /* bMsgIndex1 */ + pin_modify[15] = 0x01; /* bMsgIndex2 */ + pin_modify[16] = 0x02; /* bMsgIndex3 */ + pin_modify[17] = 0x00; /* bTeoPrologue[0] */ + pin_modify[18] = 0x00; /* bTeoPrologue[1] */ + pin_modify[19] = 2 * pininfo->fixedlen + 0x05 - no_lc; /* bTeoPrologue[2] */ + pin_modify[20] = 2 * pininfo->fixedlen + 0x05 - no_lc; /* ulDataLength */ + pin_modify[21] = 0x00; /* ulDataLength */ + pin_modify[22] = 0x00; /* ulDataLength */ + pin_modify[23] = 0x00; /* ulDataLength */ + pin_modify[24] = class; /* abData[0] */ + pin_modify[25] = ins; /* abData[1] */ + pin_modify[26] = p0; /* abData[2] */ + pin_modify[27] = p1; /* abData[3] */ + pin_modify[28] = 2 * pininfo->fixedlen; /* abData[4] */ + if (pininfo->fixedlen) + memset (&pin_modify[29], 0xff, 2 * pininfo->fixedlen); + else if (no_lc) + len--; + + if (DBG_CARD_IO) + log_debug ("send secure: c=%02X i=%02X p1=%02X p2=%02X len=%d pinmax=%d\n", + class, ins, p0, p1, len, (int)pininfo->maxlen); + + sw = control_pcsc (slot, reader_table[slot].pcsc.modify_ioctl, + pin_modify, len, result, &resultlen); + xfree (pin_modify); + if (sw || resultlen < 2) + { + log_error ("control_pcsc failed: %d\n", sw); + return sw? sw : SW_HOST_INCOMPLETE_CARD_RESPONSE; + } + sw = (result[resultlen-2] << 8) | result[resultlen-1]; + if (DBG_CARD_IO) + log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen); + return sw; +} + +#ifdef HAVE_LIBUSB +/* + Internal CCID driver interface. + */ + + +static void +dump_ccid_reader_status (int slot) +{ + log_info ("reader slot %d: using ccid driver\n", slot); +} + +static int +close_ccid_reader (int slot) +{ + ccid_close_reader (reader_table[slot].ccid.handle); + return 0; +} + + +static int +reset_ccid_reader (int slot) +{ + int err; + reader_table_t slotp = reader_table + slot; + unsigned char atr[33]; + size_t atrlen; + + err = ccid_get_atr (slotp->ccid.handle, atr, sizeof atr, &atrlen); + if (err) + return err; + /* If the reset was successful, update the ATR. */ + log_assert (sizeof slotp->atr >= sizeof atr); + slotp->atrlen = atrlen; + memcpy (slotp->atr, atr, atrlen); + dump_reader_status (slot); + return 0; +} + + +static int +set_progress_cb_ccid_reader (int slot, gcry_handler_progress_t cb, void *cb_arg) +{ + reader_table_t slotp = reader_table + slot; + + return ccid_set_progress_cb (slotp->ccid.handle, cb, cb_arg); +} + +static int +set_prompt_cb_ccid_reader (int slot, void (*cb) (void *, int ), void *cb_arg) +{ + reader_table_t slotp = reader_table + slot; + + return ccid_set_prompt_cb (slotp->ccid.handle, cb, cb_arg); +} + + +static int +get_status_ccid (int slot, unsigned int *status, int on_wire) +{ + int rc; + int bits; + + rc = ccid_slot_status (reader_table[slot].ccid.handle, &bits, on_wire); + if (rc) + return rc; + + if (bits == 0) + *status = (APDU_CARD_USABLE|APDU_CARD_PRESENT|APDU_CARD_ACTIVE); + else if (bits == 1) + *status = APDU_CARD_PRESENT; + else + *status = 0; + + return 0; +} + + +/* Actually send the APDU of length APDULEN to SLOT and return a + maximum of *BUFLEN data in BUFFER, the actual returned size will be + set to BUFLEN. Returns: Internal CCID driver error code. */ +static int +send_apdu_ccid (int slot, unsigned char *apdu, size_t apdulen, + unsigned char *buffer, size_t *buflen, + pininfo_t *pininfo) +{ + long err; + size_t maxbuflen; + + /* If we don't have an ATR, we need to reset the reader first. */ + if (!reader_table[slot].atrlen + && (err = reset_ccid_reader (slot))) + return err; + + if (DBG_CARD_IO) + log_printhex (apdu, apdulen, " raw apdu:"); + + maxbuflen = *buflen; + if (pininfo) + err = ccid_transceive_secure (reader_table[slot].ccid.handle, + apdu, apdulen, pininfo, + buffer, maxbuflen, buflen); + else + err = ccid_transceive (reader_table[slot].ccid.handle, + apdu, apdulen, + buffer, maxbuflen, buflen); + if (err) + log_error ("ccid_transceive failed: (0x%lx)\n", + err); + + return err; +} + + +/* Check whether the CCID reader supports the ISO command code COMMAND + on the pinpad. Return 0 on success. For a description of the pin + parameters, see ccid-driver.c */ +static int +check_ccid_pinpad (int slot, int command, pininfo_t *pininfo) +{ + unsigned char apdu[] = { 0, 0, 0, 0x81 }; + + apdu[1] = command; + return ccid_transceive_secure (reader_table[slot].ccid.handle, apdu, + sizeof apdu, pininfo, NULL, 0, NULL); +} + + +static int +ccid_pinpad_operation (int slot, int class, int ins, int p0, int p1, + pininfo_t *pininfo) +{ + unsigned char apdu[4]; + int err, sw; + unsigned char result[2]; + size_t resultlen = 2; + + apdu[0] = class; + apdu[1] = ins; + apdu[2] = p0; + apdu[3] = p1; + err = ccid_transceive_secure (reader_table[slot].ccid.handle, + apdu, sizeof apdu, pininfo, + result, 2, &resultlen); + if (err) + return err; + + if (resultlen < 2) + return SW_HOST_INCOMPLETE_CARD_RESPONSE; + + sw = (result[resultlen-2] << 8) | result[resultlen-1]; + return sw; +} + + +/* Open the reader and try to read an ATR. */ +static int +open_ccid_reader (struct dev_list *dl) +{ + int err; + int slot; + int require_get_status; + reader_table_t slotp; + + slot = new_reader_slot (); + if (slot == -1) + return -1; + slotp = reader_table + slot; + + err = ccid_open_reader (dl->portstr, dl->idx, dl->table, + &slotp->ccid.handle, &slotp->rdrname); + if (!err) + { + err = ccid_get_atr (slotp->ccid.handle, + slotp->atr, sizeof slotp->atr, &slotp->atrlen); + if (err) + ccid_close_reader (slotp->ccid.handle); + } + + if (err) + { + slotp->used = 0; + unlock_slot (slot); + return -1; + } + + require_get_status = ccid_require_get_status (slotp->ccid.handle); + + reader_table[slot].close_reader = close_ccid_reader; + reader_table[slot].reset_reader = reset_ccid_reader; + reader_table[slot].get_status_reader = get_status_ccid; + reader_table[slot].send_apdu_reader = send_apdu_ccid; + reader_table[slot].check_pinpad = check_ccid_pinpad; + reader_table[slot].dump_status_reader = dump_ccid_reader_status; + reader_table[slot].set_progress_cb = set_progress_cb_ccid_reader; + reader_table[slot].set_prompt_cb = set_prompt_cb_ccid_reader; + reader_table[slot].pinpad_verify = ccid_pinpad_operation; + reader_table[slot].pinpad_modify = ccid_pinpad_operation; + /* Our CCID reader code does not support T=0 at all, thus reset the + flag. */ + reader_table[slot].is_t0 = 0; + reader_table[slot].require_get_status = require_get_status; + + dump_reader_status (slot); + unlock_slot (slot); + return slot; +} +#endif /* HAVE_LIBUSB */ + +#ifdef USE_G10CODE_RAPDU +/* + The Remote APDU Interface. + + This uses the Remote APDU protocol to contact a reader. + + The port number is actually an index into the list of ports as + returned via the protocol. + */ + + +static int +rapdu_status_to_sw (int status) +{ + int rc; + + switch (status) + { + case RAPDU_STATUS_SUCCESS: rc = 0; break; + + case RAPDU_STATUS_INVCMD: + case RAPDU_STATUS_INVPROT: + case RAPDU_STATUS_INVSEQ: + case RAPDU_STATUS_INVCOOKIE: + case RAPDU_STATUS_INVREADER: rc = SW_HOST_INV_VALUE; break; + + case RAPDU_STATUS_TIMEOUT: rc = SW_HOST_CARD_IO_ERROR; break; + case RAPDU_STATUS_CARDIO: rc = SW_HOST_CARD_IO_ERROR; break; + case RAPDU_STATUS_NOCARD: rc = SW_HOST_NO_CARD; break; + case RAPDU_STATUS_CARDCHG: rc = SW_HOST_NO_CARD; break; + case RAPDU_STATUS_BUSY: rc = SW_HOST_BUSY; break; + case RAPDU_STATUS_NEEDRESET: rc = SW_HOST_CARD_INACTIVE; break; + + default: rc = SW_HOST_GENERAL_ERROR; break; + } + + return rc; +} + + + +static int +close_rapdu_reader (int slot) +{ + rapdu_release (reader_table[slot].rapdu.handle); + return 0; +} + + +static int +reset_rapdu_reader (int slot) +{ + int err; + reader_table_t slotp; + rapdu_msg_t msg = NULL; + + slotp = reader_table + slot; + + err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_RESET); + if (err) + { + log_error ("sending rapdu command RESET failed: %s\n", + err < 0 ? strerror (errno): rapdu_strerror (err)); + rapdu_msg_release (msg); + return rapdu_status_to_sw (err); + } + err = rapdu_read_msg (slotp->rapdu.handle, &msg); + if (err) + { + log_error ("receiving rapdu message failed: %s\n", + err < 0 ? strerror (errno): rapdu_strerror (err)); + rapdu_msg_release (msg); + return rapdu_status_to_sw (err); + } + if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen) + { + int sw = rapdu_status_to_sw (msg->cmd); + log_error ("rapdu command RESET failed: %s\n", + rapdu_strerror (msg->cmd)); + rapdu_msg_release (msg); + return sw; + } + if (msg->datalen > DIM (slotp->atr)) + { + log_error ("ATR returned by the RAPDU layer is too large\n"); + rapdu_msg_release (msg); + return SW_HOST_INV_VALUE; + } + slotp->atrlen = msg->datalen; + memcpy (slotp->atr, msg->data, msg->datalen); + + rapdu_msg_release (msg); + return 0; +} + + +static int +my_rapdu_get_status (int slot, unsigned int *status, int on_wire) +{ + int err; + reader_table_t slotp; + rapdu_msg_t msg = NULL; + int oldslot; + + (void)on_wire; + slotp = reader_table + slot; + + oldslot = rapdu_set_reader (slotp->rapdu.handle, slot); + err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_GET_STATUS); + rapdu_set_reader (slotp->rapdu.handle, oldslot); + if (err) + { + log_error ("sending rapdu command GET_STATUS failed: %s\n", + err < 0 ? strerror (errno): rapdu_strerror (err)); + return rapdu_status_to_sw (err); + } + err = rapdu_read_msg (slotp->rapdu.handle, &msg); + if (err) + { + log_error ("receiving rapdu message failed: %s\n", + err < 0 ? strerror (errno): rapdu_strerror (err)); + rapdu_msg_release (msg); + return rapdu_status_to_sw (err); + } + if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen) + { + int sw = rapdu_status_to_sw (msg->cmd); + log_error ("rapdu command GET_STATUS failed: %s\n", + rapdu_strerror (msg->cmd)); + rapdu_msg_release (msg); + return sw; + } + *status = msg->data[0]; + + rapdu_msg_release (msg); + return 0; +} + + +/* Actually send the APDU of length APDULEN to SLOT and return a + maximum of *BUFLEN data in BUFFER, the actual returned size will be + set to BUFLEN. Returns: APDU error code. */ +static int +my_rapdu_send_apdu (int slot, unsigned char *apdu, size_t apdulen, + unsigned char *buffer, size_t *buflen, + pininfo_t *pininfo) +{ + int err; + reader_table_t slotp; + rapdu_msg_t msg = NULL; + size_t maxlen = *buflen; + + slotp = reader_table + slot; + + *buflen = 0; + if (DBG_CARD_IO) + log_printhex (apdu, apdulen, " APDU_data:"); + + if (apdulen < 4) + { + log_error ("rapdu_send_apdu: APDU is too short\n"); + return SW_HOST_INV_VALUE; + } + + err = rapdu_send_apdu (slotp->rapdu.handle, apdu, apdulen); + if (err) + { + log_error ("sending rapdu command APDU failed: %s\n", + err < 0 ? strerror (errno): rapdu_strerror (err)); + rapdu_msg_release (msg); + return rapdu_status_to_sw (err); + } + err = rapdu_read_msg (slotp->rapdu.handle, &msg); + if (err) + { + log_error ("receiving rapdu message failed: %s\n", + err < 0 ? strerror (errno): rapdu_strerror (err)); + rapdu_msg_release (msg); + return rapdu_status_to_sw (err); + } + if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen) + { + int sw = rapdu_status_to_sw (msg->cmd); + log_error ("rapdu command APDU failed: %s\n", + rapdu_strerror (msg->cmd)); + rapdu_msg_release (msg); + return sw; + } + + if (msg->datalen > maxlen) + { + log_error ("rapdu response apdu too large\n"); + rapdu_msg_release (msg); + return SW_HOST_INV_VALUE; + } + + *buflen = msg->datalen; + memcpy (buffer, msg->data, msg->datalen); + + rapdu_msg_release (msg); + return 0; +} + +static int +open_rapdu_reader (int portno, + const unsigned char *cookie, size_t length, + int (*readfnc) (void *opaque, + void *buffer, size_t size), + void *readfnc_value, + int (*writefnc) (void *opaque, + const void *buffer, size_t size), + void *writefnc_value, + void (*closefnc) (void *opaque), + void *closefnc_value) +{ + int err; + int slot; + reader_table_t slotp; + rapdu_msg_t msg = NULL; + + slot = new_reader_slot (); + if (slot == -1) + return -1; + slotp = reader_table + slot; + + slotp->rapdu.handle = rapdu_new (); + if (!slotp->rapdu.handle) + { + slotp->used = 0; + unlock_slot (slot); + return -1; + } + + rapdu_set_reader (slotp->rapdu.handle, portno); + + rapdu_set_iofunc (slotp->rapdu.handle, + readfnc, readfnc_value, + writefnc, writefnc_value, + closefnc, closefnc_value); + rapdu_set_cookie (slotp->rapdu.handle, cookie, length); + + /* First try to get the current ATR, but if the card is inactive + issue a reset instead. */ + err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_GET_ATR); + if (err == RAPDU_STATUS_NEEDRESET) + err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_RESET); + if (err) + { + log_info ("sending rapdu command GET_ATR/RESET failed: %s\n", + err < 0 ? strerror (errno): rapdu_strerror (err)); + goto failure; + } + err = rapdu_read_msg (slotp->rapdu.handle, &msg); + if (err) + { + log_info ("receiving rapdu message failed: %s\n", + err < 0 ? strerror (errno): rapdu_strerror (err)); + goto failure; + } + if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen) + { + log_info ("rapdu command GET ATR failed: %s\n", + rapdu_strerror (msg->cmd)); + goto failure; + } + if (msg->datalen > DIM (slotp->atr)) + { + log_error ("ATR returned by the RAPDU layer is too large\n"); + goto failure; + } + slotp->atrlen = msg->datalen; + memcpy (slotp->atr, msg->data, msg->datalen); + + reader_table[slot].close_reader = close_rapdu_reader; + reader_table[slot].reset_reader = reset_rapdu_reader; + reader_table[slot].get_status_reader = my_rapdu_get_status; + reader_table[slot].send_apdu_reader = my_rapdu_send_apdu; + reader_table[slot].check_pinpad = NULL; + reader_table[slot].dump_status_reader = NULL; + reader_table[slot].pinpad_verify = NULL; + reader_table[slot].pinpad_modify = NULL; + + dump_reader_status (slot); + rapdu_msg_release (msg); + unlock_slot (slot); + return slot; + + failure: + rapdu_msg_release (msg); + rapdu_release (slotp->rapdu.handle); + slotp->used = 0; + unlock_slot (slot); + return -1; +} + +#endif /*USE_G10CODE_RAPDU*/ + + + +/* + Driver Access + */ +gpg_error_t +apdu_dev_list_start (const char *portstr, struct dev_list **l_p) +{ + struct dev_list *dl = xtrymalloc (sizeof (struct dev_list)); + + *l_p = NULL; + if (!dl) + return gpg_error_from_syserror (); + + dl->portstr = portstr; + dl->idx = 0; + + npth_mutex_lock (&reader_table_lock); + +#ifdef HAVE_LIBUSB + if (opt.disable_ccid) + { + dl->table = NULL; + dl->idx_max = 1; + } + else + { + gpg_error_t err; + + err = ccid_dev_scan (&dl->idx_max, &dl->table); + if (err) + return err; + + if (dl->idx_max == 0) + { + /* If a CCID reader specification has been given, the user does + not want a fallback to other drivers. */ + if (portstr && strlen (portstr) > 5 && portstr[4] == ':') + { + if (DBG_READER) + log_debug ("leave: apdu_open_reader => slot=-1 (no ccid)\n"); + + xfree (dl); + npth_mutex_unlock (&reader_table_lock); + return gpg_error (GPG_ERR_ENODEV); + } + else + dl->idx_max = 1; + } + } +#else + dl->table = NULL; + dl->idx_max = 1; +#endif /* HAVE_LIBUSB */ + + *l_p = dl; + return 0; +} + +void +apdu_dev_list_finish (struct dev_list *dl) +{ +#ifdef HAVE_LIBUSB + if (dl->table) + ccid_dev_scan_finish (dl->table, dl->idx_max); +#endif + xfree (dl); + npth_mutex_unlock (&reader_table_lock); +} + + +int +apdu_open_reader (struct dev_list *dl, int app_empty) +{ + int slot; + +#ifdef HAVE_LIBUSB + if (dl->table) + { /* CCID readers. */ + int readerno; + + /* See whether we want to use the reader ID string or a reader + number. A readerno of -1 indicates that the reader ID string is + to be used. */ + if (dl->portstr && strchr (dl->portstr, ':')) + readerno = -1; /* We want to use the readerid. */ + else if (dl->portstr) + { + readerno = atoi (dl->portstr); + if (readerno < 0) + { + return -1; + } + } + else + readerno = 0; /* Default. */ + + if (readerno > 0) + { /* Use single, the specific reader. */ + if (readerno >= dl->idx_max) + return -1; + + dl->idx = readerno; + dl->portstr = NULL; + slot = open_ccid_reader (dl); + dl->idx = dl->idx_max; + if (slot >= 0) + return slot; + else + return -1; + } + + while (dl->idx < dl->idx_max) + { + unsigned int bai = ccid_get_BAI (dl->idx, dl->table); + + if (DBG_READER) + log_debug ("apdu_open_reader: BAI=%x\n", bai); + + /* Check identity by BAI against already opened HANDLEs. */ + for (slot = 0; slot < MAX_READER; slot++) + if (reader_table[slot].used + && reader_table[slot].ccid.handle + && ccid_compare_BAI (reader_table[slot].ccid.handle, bai)) + break; + + if (slot == MAX_READER) + { /* Found a new device. */ + if (DBG_READER) + log_debug ("apdu_open_reader: new device=%x\n", bai); + + slot = open_ccid_reader (dl); + + dl->idx++; + if (slot >= 0) + return slot; + else + { + /* Skip this reader. */ + log_error ("ccid open error: skip\n"); + continue; + } + } + else + dl->idx++; + } + + /* Not found. Try one for PC/SC, only when it's the initial scan. */ + if (app_empty && dl->idx == dl->idx_max) + { + dl->idx++; + slot = open_pcsc_reader (dl->portstr); + } + else + slot = -1; + } + else +#endif + { /* PC/SC readers. */ + + if (app_empty && dl->idx == 0) + { + dl->idx++; + slot = open_pcsc_reader (dl->portstr); + } + else + slot = -1; + } + + return slot; +} + + +/* Open an remote reader and return an internal slot number or -1 on + error. This function is an alternative to apdu_open_reader and used + with remote readers only. Note that the supplied CLOSEFNC will + only be called once and the slot will not be valid afther this. + + If PORTSTR is NULL we default to the first available port. +*/ +int +apdu_open_remote_reader (const char *portstr, + const unsigned char *cookie, size_t length, + int (*readfnc) (void *opaque, + void *buffer, size_t size), + void *readfnc_value, + int (*writefnc) (void *opaque, + const void *buffer, size_t size), + void *writefnc_value, + void (*closefnc) (void *opaque), + void *closefnc_value) +{ +#ifdef USE_G10CODE_RAPDU + return open_rapdu_reader (portstr? atoi (portstr) : 0, + cookie, length, + readfnc, readfnc_value, + writefnc, writefnc_value, + closefnc, closefnc_value); +#else + (void)portstr; + (void)cookie; + (void)length; + (void)readfnc; + (void)readfnc_value; + (void)writefnc; + (void)writefnc_value; + (void)closefnc; + (void)closefnc_value; +#ifdef _WIN32 + errno = ENOENT; +#else + errno = ENOSYS; +#endif + return -1; +#endif +} + + +int +apdu_close_reader (int slot) +{ + int sw; + + if (DBG_READER) + log_debug ("enter: apdu_close_reader: slot=%d\n", slot); + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + { + if (DBG_READER) + log_debug ("leave: apdu_close_reader => SW_HOST_NO_DRIVER\n"); + return SW_HOST_NO_DRIVER; + } + sw = apdu_disconnect (slot); + if (sw) + { + /* + * When the reader/token was removed it might come here. + * It should go through to call CLOSE_READER even if we got an error. + */ + if (DBG_READER) + log_debug ("apdu_close_reader => 0x%x (apdu_disconnect)\n", sw); + } + if (reader_table[slot].close_reader) + { + sw = reader_table[slot].close_reader (slot); + xfree (reader_table[slot].rdrname); + reader_table[slot].rdrname = NULL; + reader_table[slot].used = 0; + if (DBG_READER) + log_debug ("leave: apdu_close_reader => 0x%x (close_reader)\n", sw); + return sw; + } + xfree (reader_table[slot].rdrname); + reader_table[slot].rdrname = NULL; + reader_table[slot].used = 0; + if (DBG_READER) + log_debug ("leave: apdu_close_reader => SW_HOST_NOT_SUPPORTED\n"); + return SW_HOST_NOT_SUPPORTED; +} + + +/* Function suitable for a cleanup function to close all reader. It + should not be used if the reader will be opened again. The reason + for implementing this to properly close USB devices so that they + will startup the next time without error. */ +void +apdu_prepare_exit (void) +{ + static int sentinel; + int slot; + + if (!sentinel) + { + sentinel = 1; + npth_mutex_lock (&reader_table_lock); + for (slot = 0; slot < MAX_READER; slot++) + if (reader_table[slot].used) + { + apdu_disconnect (slot); + if (reader_table[slot].close_reader) + reader_table[slot].close_reader (slot); + xfree (reader_table[slot].rdrname); + reader_table[slot].rdrname = NULL; + reader_table[slot].used = 0; + } + npth_mutex_unlock (&reader_table_lock); + sentinel = 0; + } +} + + +/* Enumerate all readers and return information on whether this reader + is in use. The caller should start with SLOT set to 0 and + increment it with each call until an error is returned. */ +int +apdu_enum_reader (int slot, int *used) +{ + if (slot < 0 || slot >= MAX_READER) + return SW_HOST_NO_DRIVER; + *used = reader_table[slot].used; + return 0; +} + + +/* Connect a card. This is used to power up the card and make sure + that an ATR is available. Depending on the reader backend it may + return an error for an inactive card or if no card is available. + Return -1 on error. Return 1 if reader requires get_status to + watch card removal. Return 0 if it's a token (always with a card), + or it supports INTERRUPT endpoint to watch card removal. + */ +int +apdu_connect (int slot) +{ + int sw = 0; + unsigned int status = 0; + + if (DBG_READER) + log_debug ("enter: apdu_connect: slot=%d\n", slot); + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + { + if (DBG_READER) + log_debug ("leave: apdu_connect => SW_HOST_NO_DRIVER\n"); + return -1; + } + + /* Only if the access method provides a connect function we use it. + If not, we expect that the card has been implicitly connected by + apdu_open_reader. */ + if (reader_table[slot].connect_card) + { + sw = lock_slot (slot); + if (!sw) + { + sw = reader_table[slot].connect_card (slot); + unlock_slot (slot); + } + } + + /* We need to call apdu_get_status_internal, so that the last-status + machinery gets setup properly even if a card is inserted while + scdaemon is fired up and apdu_get_status has not yet been called. + Without that we would force a reset of the card with the next + call to apdu_get_status. */ + if (!sw) + sw = apdu_get_status_internal (slot, 1, &status, 1); + + if (sw) + ; + else if (!(status & APDU_CARD_PRESENT)) + sw = SW_HOST_NO_CARD; + else if ((status & APDU_CARD_PRESENT) && !(status & APDU_CARD_ACTIVE)) + sw = SW_HOST_CARD_INACTIVE; + + if (sw == SW_HOST_CARD_INACTIVE) + { + /* Try power it up again. */ + sw = apdu_reset (slot); + } + + if (DBG_READER) + log_debug ("leave: apdu_connect => sw=0x%x\n", sw); + + if (sw) + return -1; + + return reader_table[slot].require_get_status; +} + + +int +apdu_disconnect (int slot) +{ + int sw; + + if (DBG_READER) + log_debug ("enter: apdu_disconnect: slot=%d\n", slot); + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + { + if (DBG_READER) + log_debug ("leave: apdu_disconnect => SW_HOST_NO_DRIVER\n"); + return SW_HOST_NO_DRIVER; + } + + if (reader_table[slot].disconnect_card) + { + sw = lock_slot (slot); + if (!sw) + { + sw = reader_table[slot].disconnect_card (slot); + unlock_slot (slot); + } + } + else + sw = 0; + + if (DBG_READER) + log_debug ("leave: apdu_disconnect => sw=0x%x\n", sw); + return sw; +} + + +/* Set the progress callback of SLOT to CB and its args to CB_ARG. If + CB is NULL the progress callback is removed. */ +int +apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg) +{ + int sw; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (reader_table[slot].set_progress_cb) + { + sw = lock_slot (slot); + if (!sw) + { + sw = reader_table[slot].set_progress_cb (slot, cb, cb_arg); + unlock_slot (slot); + } + } + else + sw = 0; + return sw; +} + + +int +apdu_set_prompt_cb (int slot, void (*cb) (void *, int), void *cb_arg) +{ + int sw; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (reader_table[slot].set_prompt_cb) + { + sw = lock_slot (slot); + if (!sw) + { + sw = reader_table[slot].set_prompt_cb (slot, cb, cb_arg); + unlock_slot (slot); + } + } + else + sw = 0; + return sw; +} + + +/* Do a reset for the card in reader at SLOT. */ +int +apdu_reset (int slot) +{ + int sw; + + if (DBG_READER) + log_debug ("enter: apdu_reset: slot=%d\n", slot); + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + { + if (DBG_READER) + log_debug ("leave: apdu_reset => SW_HOST_NO_DRIVER\n"); + return SW_HOST_NO_DRIVER; + } + + if ((sw = lock_slot (slot))) + { + if (DBG_READER) + log_debug ("leave: apdu_reset => sw=0x%x (lock_slot)\n", sw); + return sw; + } + + if (reader_table[slot].reset_reader) + sw = reader_table[slot].reset_reader (slot); + + unlock_slot (slot); + if (DBG_READER) + log_debug ("leave: apdu_reset => sw=0x%x\n", sw); + return sw; +} + + +/* Return the ATR or NULL if none is available. On success the length + of the ATR is stored at ATRLEN. The caller must free the returned + value. */ +unsigned char * +apdu_get_atr (int slot, size_t *atrlen) +{ + unsigned char *buf; + + if (DBG_READER) + log_debug ("enter: apdu_get_atr: slot=%d\n", slot); + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + { + if (DBG_READER) + log_debug ("leave: apdu_get_atr => NULL (bad slot)\n"); + return NULL; + } + if (!reader_table[slot].atrlen) + { + if (DBG_READER) + log_debug ("leave: apdu_get_atr => NULL (no ATR)\n"); + return NULL; + } + + buf = xtrymalloc (reader_table[slot].atrlen); + if (!buf) + { + if (DBG_READER) + log_debug ("leave: apdu_get_atr => NULL (out of core)\n"); + return NULL; + } + memcpy (buf, reader_table[slot].atr, reader_table[slot].atrlen); + *atrlen = reader_table[slot].atrlen; + if (DBG_READER) + log_debug ("leave: apdu_get_atr => atrlen=%zu\n", *atrlen); + return buf; +} + + + +/* Retrieve the status for SLOT. The function does only wait for the + card to become available if HANG is set to true. On success the + bits in STATUS will be set to + + APDU_CARD_USABLE (bit 0) = card present and usable + APDU_CARD_PRESENT (bit 1) = card present + APDU_CARD_ACTIVE (bit 2) = card active + (bit 3) = card access locked [not yet implemented] + + For most applications, testing bit 0 is sufficient. +*/ +static int +apdu_get_status_internal (int slot, int hang, unsigned int *status, int on_wire) +{ + int sw; + unsigned int s = 0; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if ((sw = hang? lock_slot (slot) : trylock_slot (slot))) + return sw; + + if (reader_table[slot].get_status_reader) + sw = reader_table[slot].get_status_reader (slot, &s, on_wire); + + unlock_slot (slot); + + if (sw) + { + if (on_wire) + reader_table[slot].atrlen = 0; + s = 0; + } + + if (status) + *status = s; + return sw; +} + + +/* See above for a description. */ +int +apdu_get_status (int slot, int hang, unsigned int *status) +{ + int sw; + + if (DBG_READER) + log_debug ("enter: apdu_get_status: slot=%d hang=%d\n", slot, hang); + sw = apdu_get_status_internal (slot, hang, status, 0); + if (DBG_READER) + { + if (status) + log_debug ("leave: apdu_get_status => sw=0x%x status=%u\n", + sw, *status); + else + log_debug ("leave: apdu_get_status => sw=0x%x\n", sw); + } + return sw; +} + + +/* Check whether the reader supports the ISO command code COMMAND on + the pinpad. Return 0 on success. For a description of the pin + parameters, see ccid-driver.c */ +int +apdu_check_pinpad (int slot, int command, pininfo_t *pininfo) +{ + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (opt.enable_pinpad_varlen) + pininfo->fixedlen = 0; + + if (reader_table[slot].check_pinpad) + { + int sw; + + if ((sw = lock_slot (slot))) + return sw; + + sw = reader_table[slot].check_pinpad (slot, command, pininfo); + unlock_slot (slot); + return sw; + } + else + return SW_HOST_NOT_SUPPORTED; +} + + +int +apdu_pinpad_verify (int slot, int class, int ins, int p0, int p1, + pininfo_t *pininfo) +{ + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (reader_table[slot].pinpad_verify) + { + int sw; + + if ((sw = lock_slot (slot))) + return sw; + + sw = reader_table[slot].pinpad_verify (slot, class, ins, p0, p1, + pininfo); + unlock_slot (slot); + return sw; + } + else + return SW_HOST_NOT_SUPPORTED; +} + + +int +apdu_pinpad_modify (int slot, int class, int ins, int p0, int p1, + pininfo_t *pininfo) +{ + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (reader_table[slot].pinpad_modify) + { + int sw; + + if ((sw = lock_slot (slot))) + return sw; + + sw = reader_table[slot].pinpad_modify (slot, class, ins, p0, p1, + pininfo); + unlock_slot (slot); + return sw; + } + else + return SW_HOST_NOT_SUPPORTED; +} + + +/* Dispatcher for the actual send_apdu function. Note, that this + function should be called in locked state. */ +static int +send_apdu (int slot, unsigned char *apdu, size_t apdulen, + unsigned char *buffer, size_t *buflen, pininfo_t *pininfo) +{ + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (reader_table[slot].send_apdu_reader) + return reader_table[slot].send_apdu_reader (slot, + apdu, apdulen, + buffer, buflen, + pininfo); + else + return SW_HOST_NOT_SUPPORTED; +} + + +/* Core APDU tranceiver function. Parameters are described at + apdu_send_le with the exception of PININFO which indicates pinpad + related operations if not NULL. If EXTENDED_MODE is not 0 + command chaining or extended length will be used according to these + values: + n < 0 := Use command chaining with the data part limited to -n + in each chunk. If -1 is used a default value is used. + n == 0 := No extended mode or command chaining. + n == 1 := Use extended length for input and output without a + length limit. + n > 1 := Use extended length with up to N bytes. + +*/ +static int +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, + pininfo_t *pininfo, int extended_mode) +{ +#define SHORT_RESULT_BUFFER_SIZE 258 + /* We allocate 8 extra bytes as a safety margin towards a driver bug. */ + unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10]; + unsigned char *result_buffer = NULL; + size_t result_buffer_size; + unsigned char *result; + size_t resultlen; + unsigned char short_apdu_buffer[5+256+1]; + unsigned char *apdu_buffer = NULL; + size_t apdu_buffer_size; + unsigned char *apdu; + size_t apdulen; + int sw; + long rc; /* We need a long here due to PC/SC. */ + int did_exact_length_hack = 0; + int use_chaining = 0; + int use_extended_length = 0; + int lc_chunk; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (DBG_CARD_IO) + log_debug ("send apdu: c=%02X i=%02X p1=%02X p2=%02X lc=%d le=%d em=%d\n", + class, ins, p0, p1, lc, le, extended_mode); + + if (lc != -1 && (lc > 255 || lc < 0)) + { + /* Data does not fit into an APDU. What we do now depends on + the EXTENDED_MODE parameter. */ + if (!extended_mode) + return SW_WRONG_LENGTH; /* No way to send such an APDU. */ + else if (extended_mode > 0) + use_extended_length = 1; + else if (extended_mode < 0) + { + /* Send APDU using chaining mode. */ + if (lc > 16384) + return SW_WRONG_LENGTH; /* Sanity check. */ + if ((class&0xf0) != 0) + return SW_HOST_INV_VALUE; /* Upper 4 bits need to be 0. */ + use_chaining = extended_mode == -1? 255 : -extended_mode; + use_chaining &= 0xff; + } + else + return SW_HOST_INV_VALUE; + } + else if (lc == -1 && extended_mode > 0) + use_extended_length = 1; + + if (le != -1 && (le > (extended_mode > 0? 255:256) || le < 0)) + { + /* Expected Data does not fit into an APDU. What we do now + depends on the EXTENDED_MODE parameter. Note that a check + for command chaining does not make sense because we are + looking at Le. */ + if (!extended_mode) + return SW_WRONG_LENGTH; /* No way to send such an APDU. */ + else if (use_extended_length) + ; /* We are already using extended length. */ + else if (extended_mode > 0) + use_extended_length = 1; + else + return SW_HOST_INV_VALUE; + } + + if ((!data && lc != -1) || (data && lc == -1)) + return SW_HOST_INV_VALUE; + + if (use_extended_length) + { + if (reader_table[slot].is_t0) + return SW_HOST_NOT_SUPPORTED; + + /* Space for: cls/ins/p1/p2+Z+2_byte_Lc+Lc+2_byte_Le. */ + apdu_buffer_size = 4 + 1 + (lc >= 0? (2+lc):0) + 2; + apdu_buffer = xtrymalloc (apdu_buffer_size + 10); + if (!apdu_buffer) + return SW_HOST_OUT_OF_CORE; + apdu = apdu_buffer; + } + else + { + apdu_buffer_size = sizeof short_apdu_buffer; + apdu = short_apdu_buffer; + } + + if (use_extended_length && (le > 256 || le < 0)) + { + /* Two more bytes are needed for status bytes. */ + result_buffer_size = le < 0? 4096 : (le + 2); + result_buffer = xtrymalloc (result_buffer_size); + if (!result_buffer) + { + xfree (apdu_buffer); + return SW_HOST_OUT_OF_CORE; + } + result = result_buffer; + } + else + { + result_buffer_size = SHORT_RESULT_BUFFER_SIZE; + result = short_result_buffer; + } +#undef SHORT_RESULT_BUFFER_SIZE + + if ((sw = lock_slot (slot))) + { + xfree (apdu_buffer); + xfree (result_buffer); + return sw; + } + + do + { + if (use_extended_length) + { + use_chaining = 0; + apdulen = 0; + apdu[apdulen++] = class; + apdu[apdulen++] = ins; + apdu[apdulen++] = p0; + apdu[apdulen++] = p1; + if (lc > 0) + { + apdu[apdulen++] = 0; /* Z byte: Extended length marker. */ + apdu[apdulen++] = ((lc >> 8) & 0xff); + apdu[apdulen++] = (lc & 0xff); + memcpy (apdu+apdulen, data, lc); + data += lc; + apdulen += lc; + } + if (le != -1) + { + if (lc <= 0) + apdu[apdulen++] = 0; /* Z byte: Extended length marker. */ + apdu[apdulen++] = ((le >> 8) & 0xff); + apdu[apdulen++] = (le & 0xff); + } + } + else + { + apdulen = 0; + apdu[apdulen] = class; + if (use_chaining && lc > 255) + { + apdu[apdulen] |= 0x10; + log_assert (use_chaining < 256); + lc_chunk = use_chaining; + lc -= use_chaining; + } + else + { + use_chaining = 0; + lc_chunk = lc; + } + apdulen++; + apdu[apdulen++] = ins; + apdu[apdulen++] = p0; + apdu[apdulen++] = p1; + if (lc_chunk != -1) + { + apdu[apdulen++] = lc_chunk; + memcpy (apdu+apdulen, data, lc_chunk); + data += lc_chunk; + apdulen += lc_chunk; + /* T=0 does not allow the use of Lc together with Le; + thus disable Le in this case. */ + if (reader_table[slot].is_t0) + le = -1; + } + if (le != -1 && !use_chaining) + apdu[apdulen++] = le; /* Truncation is okay (0 means 256). */ + } + + exact_length_hack: + /* As a safeguard don't pass any garbage to the driver. */ + log_assert (apdulen <= apdu_buffer_size); + memset (apdu+apdulen, 0, apdu_buffer_size - apdulen); + resultlen = result_buffer_size; + rc = send_apdu (slot, apdu, apdulen, result, &resultlen, pininfo); + if (rc || resultlen < 2) + { + log_info ("apdu_send_simple(%d) failed: %s\n", + slot, apdu_strerror (rc)); + unlock_slot (slot); + xfree (apdu_buffer); + xfree (result_buffer); + return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE; + } + sw = (result[resultlen-2] << 8) | result[resultlen-1]; + if (!use_extended_length + && !did_exact_length_hack && SW_EXACT_LENGTH_P (sw)) + { + apdu[apdulen-1] = (sw & 0x00ff); + did_exact_length_hack = 1; + goto exact_length_hack; + } + } + while (use_chaining && sw == SW_SUCCESS); + + if (apdu_buffer) + { + xfree (apdu_buffer); + apdu_buffer = NULL; + } + + /* Store away the returned data but strip the statusword. */ + resultlen -= 2; + if (DBG_CARD_IO) + { + log_debug (" response: sw=%04X datalen=%d\n", + sw, (unsigned int)resultlen); + if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA)) + { + if (all_zero_p (result, resultlen)) + log_debug (" dump: [all zero]\n"); + else + log_printhex (result, resultlen, " dump:"); + } + } + + if (sw == SW_SUCCESS || sw == SW_EOF_REACHED) + { + if (retbuf) + { + *retbuf = xtrymalloc (resultlen? resultlen : 1); + if (!*retbuf) + { + unlock_slot (slot); + xfree (result_buffer); + 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) + { + unlock_slot (slot); + xfree (result_buffer); + return SW_HOST_OUT_OF_CORE; + } + log_assert (resultlen < bufsize); + memcpy (p, result, resultlen); + p += resultlen; + } + + do + { + int len = (sw & 0x00ff); + + if (DBG_CARD_IO) + log_debug ("apdu_send_simple(%d): %d more bytes available\n", + slot, len); + apdu_buffer_size = sizeof short_apdu_buffer; + apdu = short_apdu_buffer; + apdulen = 0; + apdu[apdulen++] = class; + apdu[apdulen++] = 0xC0; + apdu[apdulen++] = 0; + apdu[apdulen++] = 0; + apdu[apdulen++] = len; + log_assert (apdulen <= apdu_buffer_size); + memset (apdu+apdulen, 0, apdu_buffer_size - apdulen); + resultlen = result_buffer_size; + rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL); + if (rc || resultlen < 2) + { + log_error ("apdu_send_simple(%d) for get response failed: %s\n", + slot, apdu_strerror (rc)); + unlock_slot (slot); + xfree (result_buffer); + return rc? rc : 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, (unsigned int)resultlen); + if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA)) + { + if (all_zero_p (result, resultlen)) + log_debug ( " dump: [all zero]\n"); + else + log_printhex (result, resultlen, " dump:"); + } + } + + if ((sw & 0xff00) == SW_MORE_DATA + || sw == SW_SUCCESS + || sw == SW_EOF_REACHED ) + { + if (retbuf && resultlen) + { + if (p - *retbuf + resultlen > bufsize) + { + bufsize += resultlen > 4096? resultlen: 4096; + tmp = xtryrealloc (*retbuf, bufsize); + if (!tmp) + { + unlock_slot (slot); + xfree (result_buffer); + 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; + } + } + + unlock_slot (slot); + xfree (result_buffer); + + if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS) + { + if (all_zero_p (*retbuf, *retbuflen)) + log_debug (" dump: [all zero]\n"); + else + log_printhex (*retbuf, *retbuflen, " dump:"); + } + + return sw; +} + +/* 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. If EXTENDED_MODE is not 0 command + chaining or extended length will be used; see send_le for details. + 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 responsible + for releasing the buffer even in case of errors. */ +int +apdu_send_le(int slot, int extended_mode, + int class, int ins, int p0, int p1, + int lc, const char *data, int le, + unsigned char **retbuf, size_t *retbuflen) +{ + return send_le (slot, class, ins, p0, p1, + lc, data, le, + retbuf, retbuflen, + NULL, extended_mode); +} + + +/* 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. If EXTENDED_MODE is not 0 command chaining + or extended length will be used; see send_le for details. 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 responsible for + releasing the buffer even in case of errors. */ +int +apdu_send (int slot, int extended_mode, + int class, int ins, int p0, int p1, + int lc, const char *data, unsigned char **retbuf, size_t *retbuflen) +{ + return send_le (slot, class, ins, p0, p1, lc, data, 256, + retbuf, retbuflen, NULL, extended_mode); +} + +/* 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. If EXTENDED_MODE is not 0 command chaining + or extended length will be used; see send_le for details. 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 extended_mode, + int class, int ins, int p0, int p1, + int lc, const char *data) +{ + return send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL, NULL, + extended_mode); +} + + +/* This is a more generic version of the apdu sending routine. It + * takes an already formatted APDU in APDUDATA or length APDUDATALEN + * and returns with an APDU including the status word. With + * HANDLE_MORE set to true this function will handle the MORE DATA + * status and return all APDUs concatenated with one status word at + * the end. If EXTENDED_LENGTH is != 0 extended lengths are allowed + * with a max. result data length of EXTENDED_LENGTH bytes. The + * function does not return a regular status word but 0 on success. + * If the slot is locked, the function returns immediately with an + * error. + * + * Out of historical reasons the function returns 0 on success and + * outs the status word at the end of the result to be able to get the + * status word in the case of a not provided RETBUF, R_SW can be used + * to store the SW. But note that R_SW qill only be set if the + * function returns 0. */ +int +apdu_send_direct (int slot, size_t extended_length, + const unsigned char *apdudata, size_t apdudatalen, + int handle_more, unsigned int *r_sw, + unsigned char **retbuf, size_t *retbuflen) +{ +#define SHORT_RESULT_BUFFER_SIZE 258 + unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10]; + unsigned char *result_buffer = NULL; + size_t result_buffer_size; + unsigned char *result; + size_t resultlen; + unsigned char short_apdu_buffer[5+256+10]; + unsigned char *apdu_buffer = NULL; + unsigned char *apdu; + size_t apdulen; + int sw; + long rc; /* we need a long here due to PC/SC. */ + int class; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (apdudatalen > 65535) + return SW_HOST_INV_VALUE; + + if (apdudatalen > sizeof short_apdu_buffer - 5) + { + apdu_buffer = xtrymalloc (apdudatalen + 5); + if (!apdu_buffer) + return SW_HOST_OUT_OF_CORE; + apdu = apdu_buffer; + } + else + { + apdu = short_apdu_buffer; + } + apdulen = apdudatalen; + memcpy (apdu, apdudata, apdudatalen); + class = apdulen? *apdu : 0; + + if (extended_length >= 256 && extended_length <= 65536) + { + result_buffer_size = extended_length; + result_buffer = xtrymalloc (result_buffer_size + 10); + if (!result_buffer) + { + xfree (apdu_buffer); + return SW_HOST_OUT_OF_CORE; + } + result = result_buffer; + } + else + { + result_buffer_size = SHORT_RESULT_BUFFER_SIZE; + result = short_result_buffer; + } +#undef SHORT_RESULT_BUFFER_SIZE + + if ((sw = lock_slot (slot))) + { + xfree (apdu_buffer); + xfree (result_buffer); + return sw; + } + + resultlen = result_buffer_size; + rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL); + xfree (apdu_buffer); + apdu_buffer = NULL; + if (rc || resultlen < 2) + { + log_error ("apdu_send_direct(%d) failed: %s\n", + slot, apdu_strerror (rc)); + unlock_slot (slot); + xfree (result_buffer); + return rc? rc : 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, (unsigned int)resultlen); + if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA)) + log_printhex (result, resultlen, " dump: "); + } + + if (handle_more && (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 + 2); + if (!*retbuf) + { + unlock_slot (slot); + xfree (result_buffer); + return SW_HOST_OUT_OF_CORE; + } + log_assert (resultlen < bufsize); + memcpy (p, result, resultlen); + p += resultlen; + } + + do + { + int len = (sw & 0x00ff); + + if (DBG_CARD_IO) + log_debug ("apdu_send_direct(%d): %d more bytes available\n", + slot, len); + apdu = short_apdu_buffer; + apdulen = 0; + apdu[apdulen++] = class; + apdu[apdulen++] = 0xC0; + apdu[apdulen++] = 0; + apdu[apdulen++] = 0; + apdu[apdulen++] = len; + memset (apdu+apdulen, 0, sizeof (short_apdu_buffer) - apdulen); + resultlen = result_buffer_size; + rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL); + if (rc || resultlen < 2) + { + log_error ("apdu_send_direct(%d) for get response failed: %s\n", + slot, apdu_strerror (rc)); + unlock_slot (slot); + xfree (result_buffer); + return rc ? rc : 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, (unsigned int)resultlen); + if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA)) + log_printhex (result, resultlen, " dump: "); + } + + if ((sw & 0xff00) == SW_MORE_DATA + || sw == SW_SUCCESS + || sw == SW_EOF_REACHED ) + { + if (retbuf && resultlen) + { + if (p - *retbuf + resultlen > bufsize) + { + bufsize += resultlen > 4096? resultlen: 4096; + tmp = xtryrealloc (*retbuf, bufsize + 2); + if (!tmp) + { + unlock_slot (slot); + xfree (result_buffer); + return SW_HOST_OUT_OF_CORE; + } + p = tmp + (p - *retbuf); + *retbuf = tmp; + } + memcpy (p, result, resultlen); + p += resultlen; + } + } + else + log_info ("apdu_send_direct(%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 + 2); + if (tmp) + *retbuf = tmp; + } + } + else + { + if (retbuf) + { + *retbuf = xtrymalloc ((resultlen? resultlen : 1)+2); + if (!*retbuf) + { + unlock_slot (slot); + xfree (result_buffer); + return SW_HOST_OUT_OF_CORE; + } + *retbuflen = resultlen; + memcpy (*retbuf, result, resultlen); + } + } + + unlock_slot (slot); + xfree (result_buffer); + + /* Append the status word. Note that we reserved the two extra + bytes while allocating the buffer. */ + if (retbuf) + { + (*retbuf)[(*retbuflen)++] = (sw >> 8); + (*retbuf)[(*retbuflen)++] = sw; + } + + if (r_sw) + *r_sw = sw; + + if (DBG_CARD_IO && retbuf) + log_printhex (*retbuf, *retbuflen, " dump: "); + + + return 0; +} + + +const char * +apdu_get_reader_name (int slot) +{ + return reader_table[slot].rdrname; +} + + +/* Return the list of currently known readers. Caller must free the + * returned value. Might return NULL. */ +char * +apdu_get_reader_list (void) +{ + membuf_t mb; + char *ccidlist = NULL; + + init_membuf (&mb, 256); +#ifdef HAVE_LIBUSB + ccidlist = ccid_get_reader_list (); +#endif + + if (ccidlist && *ccidlist) + put_membuf_str (&mb, ccidlist); + if (pcsc.reader_list && *pcsc.reader_list) + { + if (ccidlist && *ccidlist) + put_membuf (&mb, "\n", 1); + put_membuf_str (&mb, pcsc.reader_list); + } + xfree (ccidlist); + put_membuf (&mb, "", 1); + + return get_membuf (&mb, NULL); +} + + +gpg_error_t +apdu_init (void) +{ +#ifdef USE_NPTH + gpg_error_t err; + int i; + + pcsc.context = -1; + pcsc.context_valid = 0; + pcsc.reader_list = NULL; + + if (npth_mutex_init (&reader_table_lock, NULL)) + goto leave; + + for (i = 0; i < MAX_READER; i++) + if (npth_mutex_init (&reader_table[i].lock, NULL)) + goto leave; + + /* All done well. */ + return 0; + + leave: + err = gpg_error_from_syserror (); + log_error ("apdu: error initializing mutex: %s\n", gpg_strerror (err)); + return err; +#endif /*USE_NPTH*/ + return 0; +} diff --git a/scd/apdu.h b/scd/apdu.h new file mode 100644 index 0000000..32b8e9e --- /dev/null +++ b/scd/apdu.h @@ -0,0 +1,156 @@ +/* apdu.h - ISO 7816 APDU functions and low level I/O + * Copyright (C) 2003, 2008 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * + * $Id$ + */ + +#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_EOF_REACHED = 0x6282, + SW_TERM_STATE = 0x6285, /* Selected file is in termination state. */ + SW_EEPROM_FAILURE = 0x6581, + SW_WRONG_LENGTH = 0x6700, + SW_SM_NOT_SUP = 0x6882, /* Secure Messaging is not supported. */ + SW_CC_NOT_SUP = 0x6884, /* Command Chaining is not supported. */ + SW_FILE_STRUCT = 0x6981, /* Command can't be used for file structure. */ + SW_CHV_WRONG = 0x6982, + SW_CHV_BLOCKED = 0x6983, + SW_REF_DATA_INV = 0x6984, /* Referenced data invalidated. */ + SW_USE_CONDITIONS = 0x6985, + SW_NO_CURRENT_EF = 0x6986, /* No current EF selected. */ + SW_BAD_PARAMETER = 0x6a80, /* (in the data field) */ + SW_NOT_SUPPORTED = 0x6a81, + SW_FILE_NOT_FOUND = 0x6a82, + SW_RECORD_NOT_FOUND = 0x6a83, + SW_NOT_ENOUGH_MEMORY= 0x6a84, /* Not enough memory space in the file. */ + SW_INCONSISTENT_LC = 0x6a85, /* Lc inconsistent with TLV structure. */ + SW_INCORRECT_P0_P1 = 0x6a86, + SW_BAD_LC = 0x6a87, /* Lc does not match command or p1/p2. */ + SW_REF_NOT_FOUND = 0x6a88, + SW_BAD_P0_P1 = 0x6b00, + SW_EXACT_LENGTH = 0x6c00, + SW_INS_NOT_SUP = 0x6d00, + SW_CLA_NOT_SUP = 0x6e00, + SW_SUCCESS = 0x9000, + + /* The following 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, + SW_HOST_NO_DRIVER = 0x10004, + SW_HOST_NOT_SUPPORTED = 0x10005, + SW_HOST_LOCKING_FAILED= 0x10006, + SW_HOST_BUSY = 0x10007, + SW_HOST_NO_CARD = 0x10008, + SW_HOST_CARD_INACTIVE = 0x10009, + SW_HOST_CARD_IO_ERROR = 0x1000a, + SW_HOST_GENERAL_ERROR = 0x1000b, + SW_HOST_NO_READER = 0x1000c, + SW_HOST_ABORTED = 0x1000d, + SW_HOST_NO_PINPAD = 0x1000e, + SW_HOST_ALREADY_CONNECTED = 0x1000f, + SW_HOST_CANCELLED = 0x10010, + SW_HOST_DEVICE_ACCESS = 0x10011, + SW_HOST_USB_OTHER = 0x10020, + SW_HOST_USB_IO = 0x10021, + SW_HOST_USB_ACCESS = 0x10023, + SW_HOST_USB_NO_DEVICE = 0x10024, + SW_HOST_USB_BUSY = 0x10026, + SW_HOST_USB_TIMEOUT = 0x10027, + SW_HOST_USB_OVERFLOW = 0x10028 +}; + +struct dev_list; + +#define SW_EXACT_LENGTH_P(a) (((a)&~0xff) == SW_EXACT_LENGTH) + + +/* Bit flags for the card status. */ +#define APDU_CARD_USABLE (1) /* Card is present and ready for use. */ +#define APDU_CARD_PRESENT (2) /* Card is just present. */ +#define APDU_CARD_ACTIVE (4) /* Card is active. */ + + +gpg_error_t apdu_init (void); + +gpg_error_t apdu_dev_list_start (const char *portstr, struct dev_list **l_p); +void apdu_dev_list_finish (struct dev_list *l); + +/* Note, that apdu_open_reader returns no status word but -1 on error. */ +int apdu_open_reader (struct dev_list *l, int app_empty); +int apdu_open_remote_reader (const char *portstr, + const unsigned char *cookie, size_t length, + int (*readfnc) (void *opaque, + void *buffer, size_t size), + void *readfnc_value, + int (*writefnc) (void *opaque, + const void *buffer, size_t size), + void *writefnc_value, + void (*closefnc) (void *opaque), + void *closefnc_value); +int apdu_close_reader (int slot); +void apdu_prepare_exit (void); +int apdu_enum_reader (int slot, int *used); +unsigned char *apdu_get_atr (int slot, size_t *atrlen); + +const char *apdu_strerror (int rc); + + +/* These APDU functions return status words. */ + +int apdu_connect (int slot); +int apdu_disconnect (int slot); + +int apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg); +int apdu_set_prompt_cb (int slot, void (*cb) (void *, int), void *cb_arg); + +int apdu_reset (int slot); +int apdu_get_status (int slot, int hang, unsigned int *status); +int apdu_check_pinpad (int slot, int command, pininfo_t *pininfo); +int apdu_pinpad_verify (int slot, int class, int ins, int p0, int p1, + pininfo_t *pininfo); +int apdu_pinpad_modify (int slot, int class, int ins, int p0, int p1, + pininfo_t *pininfo); +int apdu_send_simple (int slot, int extended_mode, + int class, int ins, int p0, int p1, + int lc, const char *data); +int apdu_send (int slot, int extended_mode, + 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 extended_mode, + int class, int ins, int p0, int p1, + int lc, const char *data, int le, + unsigned char **retbuf, size_t *retbuflen); +int apdu_send_direct (int slot, size_t extended_length, + const unsigned char *apdudata, size_t apdudatalen, + int handle_more, unsigned int *r_sw, + unsigned char **retbuf, size_t *retbuflen); +const char *apdu_get_reader_name (int slot); +char *apdu_get_reader_list (void); + +#endif /*APDU_H*/ diff --git a/scd/app-common.h b/scd/app-common.h new file mode 100644 index 0000000..f95db74 --- /dev/null +++ b/scd/app-common.h @@ -0,0 +1,306 @@ +/* app-common.h - Common declarations for all card applications + * Copyright (C) 2003, 2005, 2008 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * + * $Id$ + */ + +#ifndef GNUPG_SCD_APP_COMMON_H +#define GNUPG_SCD_APP_COMMON_H + +#include <npth.h> +#include <ksba.h> + +/* Flags used with app_change_pin. */ +#define APP_CHANGE_FLAG_RESET 1 /* PIN Reset mode. */ +#define APP_CHANGE_FLAG_NULLPIN 2 /* NULL PIN mode. */ +#define APP_CHANGE_FLAG_CLEAR 4 /* Clear the given PIN. */ + +/* Flags used with app_genkey. */ +#define APP_GENKEY_FLAG_FORCE 1 /* Force overwriting existing key. */ + +/* Flags used with app_writekey. */ +#define APP_WRITEKEY_FLAG_FORCE 1 /* Force overwriting existing key. */ + +/* Flags used with app_readkey. */ +#define APP_READKEY_FLAG_INFO 1 /* Send also a KEYPAIRINFO line. */ +#define APP_READKEY_FLAG_ADVANCED 2 /* (gnupg 2.2 only) */ + +/* Bit flags set by the decipher function into R_INFO. */ +#define APP_DECIPHER_INFO_NOPAD 1 /* Padding has been removed. */ + +/* Flags used by the app_write_learn_status. */ +#define APP_LEARN_FLAG_KEYPAIRINFO 1 /* Return only keypair infos. */ +#define APP_LEARN_FLAG_MULTI 2 /* Return info for all apps. */ +#define APP_LEARN_FLAG_REREAD 4 /* Re-read infos from the token. */ + + +/* List of supported card types. Generic is the usual ISO7817-4 + * compliant card. More specific card or token versions can be given + * here. Introduced in 2.2 for easier backporting from 2.3. */ +typedef enum + { + CARDTYPE_GENERIC = 0, + CARDTYPE_GNUK, + CARDTYPE_YUBIKEY, + CARDTYPE_ZEITCONTROL + } cardtype_t; + + +/* List of supported card applications. The source code for each + * application can usually be found in an app-NAME.c file. Introduced + * in 2.2 for easier backporting from 2.3. */ +typedef enum + { + APPTYPE_NONE = 0, + APPTYPE_UNDEFINED, + APPTYPE_OPENPGP, + APPTYPE_PIV, + APPTYPE_NKS, + APPTYPE_P15, + APPTYPE_GELDKARTE, + APPTYPE_DINSIG, + APPTYPE_SC_HSM + } apptype_t; + + +/* Forward declarations. */ +struct app_ctx_s; +struct app_local_s; /* Defined by all app-*.c. */ + +typedef struct app_ctx_s *app_t; + +struct app_ctx_s { + struct app_ctx_s *next; + + npth_mutex_t lock; + + /* Number of connections currently using this application context. + If this is not 0 the application has been initialized and the + function pointers may be used. Note that for unsupported + operations the particular function pointer is set to NULL */ + unsigned int ref_count; + + /* Used reader slot. */ + int slot; + + unsigned char *serialno; /* Serialnumber in raw form, allocated. */ + size_t serialnolen; /* Length in octets of serialnumber. */ + apptype_t apptype; + unsigned int appversion; /* Version of the application or 0. */ + cardtype_t cardtype; /* The token's type. */ + unsigned int cardversion;/* Firmware version of the token or 0. */ + unsigned int card_status; + unsigned int reset_requested:1; + unsigned int periodical_check_needed:1; + unsigned int did_chv1:1; + unsigned int force_chv1:1; /* True if the card does not cache CHV1. */ + unsigned int did_chv2:1; + unsigned int did_chv3:1; + struct app_local_s *app_local; /* Local to the application. */ + struct { + void (*deinit) (app_t app); + + /* prep_reselect and reselect are not used in this version of scd. */ + gpg_error_t (*prep_reselect) (app_t app, ctrl_t ctrl); + gpg_error_t (*reselect) (app_t app, ctrl_t ctrl); + + gpg_error_t (*learn_status) (app_t app, ctrl_t ctrl, unsigned int flags); + gpg_error_t (*readcert) (app_t app, const char *certid, + unsigned char **cert, size_t *certlen); + gpg_error_t (*readkey) (app_t app, ctrl_t ctrl, + const char *certid, unsigned int flags, + unsigned char **pk, size_t *pklen); + gpg_error_t (*getattr) (app_t app, ctrl_t ctrl, const char *name); + gpg_error_t (*setattr) (app_t app, ctrl_t ctrl, const char *name, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen); + gpg_error_t (*sign) (app_t app, ctrl_t ctrl, + const char *keyidstr, int hashalgo, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ); + gpg_error_t (*auth) (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); + gpg_error_t (*decipher) (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen, + unsigned int *r_info); + gpg_error_t (*writecert) (app_t app, ctrl_t ctrl, + const char *certid, + gpg_error_t (*pincb)(void*,const char *,char **), + void *pincb_arg, + const unsigned char *data, size_t datalen); + gpg_error_t (*writekey) (app_t app, ctrl_t ctrl, + const char *keyid, unsigned int flags, + gpg_error_t (*pincb)(void*,const char *,char **), + void *pincb_arg, + const unsigned char *pk, size_t pklen); + gpg_error_t (*genkey) (app_t app, ctrl_t ctrl, + const char *keyref, const char *keytype, + unsigned int flags, time_t createtime, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); + gpg_error_t (*change_pin) (app_t app, ctrl_t ctrl, + const char *chvnostr, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); + gpg_error_t (*check_pin) (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); + + /* with_keygrip is not used in this version of scd but having it + * makes back porting app-*.c from later versions easier. */ + gpg_error_t (*with_keygrip) (app_t app, ctrl_t ctrl, int action, + const char *keygrip_str, int capability); + } fnc; +}; + + +/* Action values for app_do_with_keygrip. */ +enum + { + KEYGRIP_ACTION_SEND_DATA, + KEYGRIP_ACTION_WRITE_STATUS, + KEYGRIP_ACTION_LOOKUP + }; + + +/* Helper to get the slot from an APP object. */ +static inline int +app_get_slot (app_t app) +{ + /* Note that this is a similar function of the one in 2.3 which we + * use to make back porting easier. */ + if (app) + return app->slot; + return -1; +} + +/* Macro to access members in app_t which are found in 2.3 in a linked + * card_t member. */ +#define APP_CARD(a) (a) + + +/*-- app-help.c --*/ +unsigned int app_help_count_bits (const unsigned char *a, size_t len); +gpg_error_t app_help_get_keygrip_string_pk (const void *pk, size_t pklen, + char *hexkeygrip, + gcry_sexp_t *r_pkey, + int *r_algo, char **r_algostr); +gpg_error_t app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip, + gcry_sexp_t *r_pkey, int *r_algo); +gpg_error_t app_help_pubkey_from_cert (const void *cert, size_t certlen, + unsigned char **r_pk, size_t *r_pklen); +size_t app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff); + + +/*-- app.c --*/ +void app_send_card_list (ctrl_t ctrl); +char *app_get_serialno (app_t app); +char *app_get_dispserialno (app_t app, int nofallback); + +void app_dump_state (void); +void application_notify_card_reset (int slot); +gpg_error_t check_application_conflict (const char *name, app_t app); +gpg_error_t app_reset (app_t app, ctrl_t ctrl, int send_reset); +gpg_error_t select_application (ctrl_t ctrl, const char *name, app_t *r_app, + int scan, const unsigned char *serialno_bin, + size_t serialno_bin_len); +char *get_supported_applications (void); +void release_application (app_t app, int locked_already); +gpg_error_t app_munge_serialno (app_t app); +gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl, + unsigned int flags); +gpg_error_t app_readcert (app_t app, ctrl_t ctrl, const char *certid, + unsigned char **cert, size_t *certlen); +gpg_error_t app_readkey (app_t app, ctrl_t ctrl, int advanced, + const char *keyid, unsigned char **pk, size_t *pklen); +gpg_error_t app_getattr (app_t app, ctrl_t ctrl, const char *name); +gpg_error_t app_setattr (app_t app, ctrl_t ctrl, const char *name, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen); +gpg_error_t app_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ); +gpg_error_t app_auth (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); +gpg_error_t app_decipher (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen, + unsigned int *r_info); +gpg_error_t app_writecert (app_t app, ctrl_t ctrl, + const char *certidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *keydata, size_t keydatalen); +gpg_error_t app_writekey (app_t app, ctrl_t ctrl, + const char *keyidstr, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *keydata, size_t keydatalen); +gpg_error_t app_genkey (app_t app, ctrl_t ctrl, + const char *keynostr, const char *keytype, + unsigned int flags, time_t createtime, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); +gpg_error_t app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, + unsigned char *buffer); +gpg_error_t app_change_pin (app_t app, ctrl_t ctrl, + const char *chvnostr, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); +gpg_error_t app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg); + + +/*-- app-openpgp.c --*/ +gpg_error_t app_select_openpgp (app_t app); + +/*-- app-nks.c --*/ +gpg_error_t app_select_nks (app_t app); + +/*-- app-dinsig.c --*/ +gpg_error_t app_select_dinsig (app_t app); + +/*-- app-p15.c --*/ +gpg_error_t app_select_p15 (app_t app); + +/*-- app-geldkarte.c --*/ +gpg_error_t app_select_geldkarte (app_t app); + +/*-- app-sc-hsm.c --*/ +gpg_error_t app_select_sc_hsm (app_t app); + + +#endif /*GNUPG_SCD_APP_COMMON_H*/ diff --git a/scd/app-dinsig.c b/scd/app-dinsig.c new file mode 100644 index 0000000..5a2713e --- /dev/null +++ b/scd/app-dinsig.c @@ -0,0 +1,574 @@ +/* app-dinsig.c - The DINSIG (DIN V 66291-1) card application. + * Copyright (C) 2002, 2004, 2005, 2007, 2008 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + + +/* The German signature law and its bylaw (SigG and SigV) is currently + used with an interface specification described in DIN V 66291-1. + The AID to be used is: 'D27600006601'. + + The file IDs for certificates utilize the generic format: + Cxyz + C being the hex digit 'C' (12). + x being the service indicator: + '0' := SigG conform digital signature. + '1' := entity authentication. + '2' := key encipherment. + '3' := data encipherment. + '4' := key agreement. + other values are reserved for future use. + y being the security environment number using '0' for cards + not supporting a SE number. + z being the certificate type: + '0' := C.CH (base certificate of card holder) or C.ICC. + '1' .. '7' := C.CH (business or professional certificate + of card holder. + '8' .. 'D' := C.CA (certificate of a CA issue by the Root-CA). + 'E' := C.RCA (self certified certificate of the Root-CA). + 'F' := reserved. + + The file IDs used by default are: + '1F00' EF.SSD (security service descriptor). [o,o] + '2F02' EF.GDO (global data objects) [m,m] + 'A000' EF.PROT (signature log). Cyclic file with 20 records of 53 byte. + Read and update after user authentication. [o,o] + 'B000' EF.PK.RCA.DS (public keys of Root-CA). Size is 512b or size + of keys. [m (unless a 'C00E' is present),m] + 'B001' EF.PK.CA.DS (public keys of CAs). Size is 512b or size + of keys. [o,o] + 'C00n' EF.C.CH.DS (digital signature certificate of card holder) + with n := 0 .. 7. Size is 2k or size of cert. Read and + update allowed after user authentication. [m,m] + 'C00m' EF.C.CA.DS (digital signature certificate of CA) + with m := 8 .. E. Size is 1k or size of cert. Read always + allowed, update after user authentication. [o,o] + 'C100' EF.C.ICC.AUT (AUT certificate of ICC) [o,m] + 'C108' EF.C.CA.AUT (AUT certificate of CA) [o,m] + 'D000' EF.DM (display message) [-,m] + + The letters in brackets indicate optional or mandatory files: The + first for card terminals under full control and the second for + "business" card terminals. +*/ + + + + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <time.h> + +#include "scdaemon.h" + +#include "../common/i18n.h" +#include "iso7816.h" +#include "../common/tlv.h" + + +static gpg_error_t +do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) +{ + gpg_error_t err; + char ct_buf[100], id_buf[100]; + char hexkeygrip[41]; + size_t len, certoff; + unsigned char *der; + size_t derlen; + ksba_cert_t cert; + int fid; + + (void)flags; + + /* Return the certificate of the card holder. */ + fid = 0xC000; + len = app_help_read_length_of_cert (app->slot, fid, &certoff); + if (!len) + return 0; /* Card has not been personalized. */ + + sprintf (ct_buf, "%d", 101); + sprintf (id_buf, "DINSIG.%04X", fid); + send_status_info (ctrl, "CERTINFO", + ct_buf, strlen (ct_buf), + id_buf, strlen (id_buf), + NULL, (size_t)0); + + /* Now we need to read the certificate, so that we can get the + public key out of it. */ + err = iso7816_read_binary (app->slot, certoff, len-certoff, &der, &derlen); + if (err) + { + log_info ("error reading entire certificate from FID 0x%04X: %s\n", + fid, gpg_strerror (err)); + return 0; + } + + err = ksba_cert_new (&cert); + if (err) + { + xfree (der); + return err; + } + err = ksba_cert_init_from_mem (cert, der, derlen); + xfree (der); der = NULL; + if (err) + { + log_error ("failed to parse the certificate at FID 0x%04X: %s\n", + fid, gpg_strerror (err)); + ksba_cert_release (cert); + return err; + } + err = app_help_get_keygrip_string (cert, hexkeygrip, NULL, NULL); + if (err) + { + log_error ("failed to calculate the keygrip for FID 0x%04X\n", fid); + ksba_cert_release (cert); + return gpg_error (GPG_ERR_CARD); + } + ksba_cert_release (cert); + + sprintf (id_buf, "DINSIG.%04X", fid); + send_status_info (ctrl, "KEYPAIRINFO", + hexkeygrip, 40, + id_buf, strlen (id_buf), + NULL, (size_t)0); + return 0; +} + + + + +/* Read the certificate with id CERTID (as returned by learn_status in + the CERTINFO status lines) and return it in the freshly allocated + buffer put into CERT and the length of the certificate put into + CERTLEN. + + FIXME: This needs some cleanups and caching with do_learn_status. +*/ +static gpg_error_t +do_readcert (app_t app, const char *certid, + unsigned char **cert, size_t *certlen) +{ + int fid; + gpg_error_t err; + unsigned char *buffer; + const unsigned char *p; + size_t buflen, n; + int class, tag, constructed, ndef; + size_t totobjlen, objlen, hdrlen; + int rootca = 0; + + *cert = NULL; + *certlen = 0; + if (strncmp (certid, "DINSIG.", 7) ) + return gpg_error (GPG_ERR_INV_ID); + certid += 7; + if (!hexdigitp (certid) || !hexdigitp (certid+1) + || !hexdigitp (certid+2) || !hexdigitp (certid+3) + || certid[4]) + return gpg_error (GPG_ERR_INV_ID); + fid = xtoi_4 (certid); + if (fid != 0xC000 ) + return gpg_error (GPG_ERR_NOT_FOUND); + + /* Read the entire file. fixme: This could be optimized by first + reading the header to figure out how long the certificate + actually is. */ + err = iso7816_select_file (app->slot, fid, 0); + if (err) + { + log_error ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err)); + return err; + } + + err = iso7816_read_binary (app->slot, 0, 0, &buffer, &buflen); + if (err) + { + log_error ("error reading certificate from FID 0x%04X: %s\n", + fid, gpg_strerror (err)); + return err; + } + + if (!buflen || *buffer == 0xff) + { + log_info ("no certificate contained in FID 0x%04X\n", fid); + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + + /* Now figure something out about the object. */ + p = buffer; + n = buflen; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + if ( class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed ) + ; + else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed ) + rootca = 1; + else + return gpg_error (GPG_ERR_INV_OBJ); + totobjlen = objlen + hdrlen; + assert (totobjlen <= buflen); + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + + if (rootca) + ; + else if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed) + { + const unsigned char *save_p; + + /* The certificate seems to be contained in a userCertificate + container. Skip this and assume the following sequence is + the certificate. */ + if (n < objlen) + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + p += objlen; + n -= objlen; + save_p = p; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) ) + return gpg_error (GPG_ERR_INV_OBJ); + totobjlen = objlen + hdrlen; + assert (save_p + totobjlen <= buffer + buflen); + memmove (buffer, save_p, totobjlen); + } + + *cert = buffer; + buffer = NULL; + *certlen = totobjlen; + + leave: + xfree (buffer); + return err; +} + + +/* Verify the PIN if required. */ +static gpg_error_t +verify_pin (app_t app, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + const char *s; + int rc; + pininfo_t pininfo; + + if ( app->did_chv1 && !app->force_chv1 ) + return 0; /* No need to verify it again. */ + + memset (&pininfo, 0, sizeof pininfo); + pininfo.fixedlen = -1; + pininfo.minlen = 6; + pininfo.maxlen = 8; + + if (!opt.disable_pinpad + && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) ) + { + rc = pincb (pincb_arg, + _("||Please enter your PIN at the reader's pinpad"), + NULL); + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + return rc; + } + rc = iso7816_verify_kp (app->slot, 0x81, &pininfo); + /* Dismiss the prompt. */ + pincb (pincb_arg, NULL, NULL); + } + else /* No Pinpad. */ + { + char *pinvalue; + + rc = pincb (pincb_arg, "PIN", &pinvalue); + if (rc) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); + return rc; + } + + /* We require the PIN to be at least 6 and at max 8 bytes. + According to the specs, this should all be ASCII. */ + for (s=pinvalue; digitp (s); s++) + ; + if (*s) + { + log_error ("Non-numeric digits found in PIN\n"); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + + if (strlen (pinvalue) < pininfo.minlen) + { + log_error ("PIN is too short; minimum length is %d\n", + pininfo.minlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + else if (strlen (pinvalue) > pininfo.maxlen) + { + log_error ("PIN is too large; maximum length is %d\n", + pininfo.maxlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + + rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); + if (gpg_err_code (rc) == GPG_ERR_INV_VALUE) + { + /* We assume that ISO 9564-1 encoding is used and we failed + because the first nibble we passed was 3 and not 2. DIN + says something about looking up such an encoding in the + SSD but I was not able to find any tag relevant to + this. */ + char paddedpin[8]; + int i, ndigits; + + for (ndigits=0, s=pinvalue; *s; ndigits++, s++) + ; + i = 0; + paddedpin[i++] = 0x20 | (ndigits & 0x0f); + for (s=pinvalue; i < sizeof paddedpin && *s && s[1]; s = s+2 ) + paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f)); + if (i < sizeof paddedpin && *s) + paddedpin[i++] = (((*s - '0') << 4) | 0x0f); + while (i < sizeof paddedpin) + paddedpin[i++] = 0xff; + rc = iso7816_verify (app->slot, 0x81, paddedpin, sizeof paddedpin); + } + xfree (pinvalue); + } + + if (rc) + { + log_error ("verify PIN failed\n"); + return rc; + } + app->did_chv1 = 1; + return 0; +} + + + +/* 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; + that callback should return the PIN in an allocated buffer and + store that in the 3rd argument. */ +static gpg_error_t +do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, + gpg_error_t (*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 }; + static unsigned char sha256_prefix[19] = /* OID is 2.16.840.1.101.3.4.2.1 */ + { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20 }; + int rc; + int fid; + unsigned char data[19+32]; /* Must be large enough for a SHA-256 digest + + the largest OID _prefix above. */ + int datalen; + + (void)ctrl; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + if (indatalen != 20 && indatalen != 16 && indatalen != 32 + && indatalen != (15+20) && indatalen != (19+32)) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check that the provided ID is vaid. This is not really needed + but we do it to enforce correct usage by the caller. */ + if (strncmp (keyidstr, "DINSIG.", 7) ) + return gpg_error (GPG_ERR_INV_ID); + keyidstr += 7; + if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1) + || !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3) + || keyidstr[4]) + return gpg_error (GPG_ERR_INV_ID); + fid = xtoi_4 (keyidstr); + if (fid != 0xC000) + return gpg_error (GPG_ERR_NOT_FOUND); + + /* Prepare the DER object from INDATA. */ + datalen = 35; + if (indatalen == 15+20) + { + /* Alright, the caller was so kind to send us an already + prepared DER object. Check that it is what we want and that + it matches the hash algorithm. */ + if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15)) + ; + else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata, rmd160_prefix,15)) + ; + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + memcpy (data, indata, indatalen); + } + else if (indatalen == 19+32) + { + /* Alright, the caller was so kind to send us an already + prepared DER object. Check that it is what we want and that + it matches the hash algorithm. */ + datalen = indatalen; + if (hashalgo == GCRY_MD_SHA256 && !memcmp (indata, sha256_prefix, 19)) + ; + else if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha256_prefix, 19)) + { + /* Fixme: This is a kludge. A better solution is not to use + SHA1 as default but use an autodetection. However this + needs changes in all app-*.c */ + datalen = indatalen; + } + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + memcpy (data, indata, indatalen); + } + else + { + int len = 15; + if (hashalgo == GCRY_MD_SHA1) + memcpy (data, sha1_prefix, len); + else if (hashalgo == GCRY_MD_RMD160) + memcpy (data, rmd160_prefix, len); + else if (hashalgo == GCRY_MD_SHA256) + { + len = 19; + datalen = len + indatalen; + memcpy (data, sha256_prefix, len); + } + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + memcpy (data+len, indata, indatalen); + } + + rc = verify_pin (app, pincb, pincb_arg); + if (!rc) + rc = iso7816_compute_ds (app->slot, 0, data, datalen, 0, + outdata, outdatalen); + return rc; +} + + +#if 0 +#warning test function - works but may brick your card +/* Handle the PASSWD command. CHVNOSTR is currently ignored; we + always use VHV0. RESET_MODE is not yet implemented. */ +static gpg_error_t +do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, + unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + char *pinvalue; + const char *oldpin; + size_t oldpinlen; + + if ((flags & APP_CHANGE_FLAG_RESET)) + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + if ((flags & APP_CHANGE_FLAG_NULLPIN)) + { + /* With the nullpin flag, we do not verify the PIN - it would fail + if the Nullpin is still set. */ + oldpin = "\0\0\0\0\0"; + oldpinlen = 6; + } + else + { + err = verify_pin (app, pincb, pincb_arg); + if (err) + return err; + oldpin = NULL; + oldpinlen = 0; + } + + /* TRANSLATORS: Do not translate the "|*|" prefixes but + keep it at the start of the string. We need this elsewhere + to get some infos on the string. */ + err = pincb (pincb_arg, _("|N|Initial New PIN"), &pinvalue); + if (err) + { + log_error (_("error getting new PIN: %s\n"), gpg_strerror (err)); + return err; + } + + err = iso7816_change_reference_data (app->slot, 0x81, + oldpin, oldpinlen, + pinvalue, strlen (pinvalue)); + xfree (pinvalue); + return err; +} +#endif /*0*/ + + +/* Select the DINSIG application on the card in SLOT. This function + must be used before any other DINSIG application functions. */ +gpg_error_t +app_select_dinsig (app_t app) +{ + static char const aid[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 }; + int slot = app->slot; + int rc; + + rc = iso7816_select_application (slot, aid, sizeof aid, 0); + if (!rc) + { + app->apptype = APPTYPE_DINSIG; + + app->fnc.learn_status = do_learn_status; + app->fnc.readcert = do_readcert; + app->fnc.getattr = NULL; + app->fnc.setattr = NULL; + app->fnc.genkey = NULL; + app->fnc.sign = do_sign; + app->fnc.auth = NULL; + app->fnc.decipher = NULL; + app->fnc.change_pin = NULL /*do_change_pin*/; + app->fnc.check_pin = NULL; + + app->force_chv1 = 1; + } + + return rc; +} diff --git a/scd/app-geldkarte.c b/scd/app-geldkarte.c new file mode 100644 index 0000000..5437263 --- /dev/null +++ b/scd/app-geldkarte.c @@ -0,0 +1,408 @@ +/* app-geldkarte.c - The German Geldkarte application + * Copyright (C) 2004 g10 Code GmbH + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + + +/* This is a read-only application to quickly dump information of a + German Geldkarte (debit card for small amounts). We only support + newer Geldkarte (with the AID DF_BOERSE_NEU) issued since 2000 or + even earlier. +*/ + + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <time.h> +#include <ctype.h> + +#include "scdaemon.h" + +#include "../common/i18n.h" +#include "iso7816.h" +#include "../common/tlv.h" + + + +/* Object with application (i.e. Geldkarte) specific data. */ +struct app_local_s +{ + char kblz[2+1+4+1]; + const char *banktype; + char *cardno; + char expires[7+1]; + char validfrom[10+1]; + char *country; + char currency[3+1]; + unsigned int currency_mult100; + unsigned char chipid; + unsigned char osvers; + int balance; + int maxamount; + int maxamount1; +}; + + + + +/* Deconstructor. */ +static void +do_deinit (app_t app) +{ + if (app && app->app_local) + { + xfree (app->app_local->cardno); + xfree (app->app_local->country); + xfree (app->app_local); + app->app_local = NULL; + } +} + + +static gpg_error_t +send_one_string (ctrl_t ctrl, const char *name, const char *string) +{ + if (!name || !string) + return 0; + send_status_info (ctrl, name, string, strlen (string), NULL, 0); + return 0; +} + +/* Implement the GETATTR command. This is similar to the LEARN + command but returns just one value via the status interface. */ +static gpg_error_t +do_getattr (app_t app, ctrl_t ctrl, const char *name) +{ + gpg_error_t err; + struct app_local_s *ld = app->app_local; + char numbuf[100]; + + if (!strcmp (name, "X-KBLZ")) + err = send_one_string (ctrl, name, ld->kblz); + else if (!strcmp (name, "X-BANKINFO")) + err = send_one_string (ctrl, name, ld->banktype); + else if (!strcmp (name, "X-CARDNO")) + err = send_one_string (ctrl, name, ld->cardno); + else if (!strcmp (name, "X-EXPIRES")) + err = send_one_string (ctrl, name, ld->expires); + else if (!strcmp (name, "X-VALIDFROM")) + err = send_one_string (ctrl, name, ld->validfrom); + else if (!strcmp (name, "X-COUNTRY")) + err = send_one_string (ctrl, name, ld->country); + else if (!strcmp (name, "X-CURRENCY")) + err = send_one_string (ctrl, name, ld->currency); + else if (!strcmp (name, "X-ZKACHIPID")) + { + snprintf (numbuf, sizeof numbuf, "0x%02X", ld->chipid); + err = send_one_string (ctrl, name, numbuf); + } + else if (!strcmp (name, "X-OSVERSION")) + { + snprintf (numbuf, sizeof numbuf, "0x%02X", ld->osvers); + err = send_one_string (ctrl, name, numbuf); + } + else if (!strcmp (name, "X-BALANCE")) + { + snprintf (numbuf, sizeof numbuf, "%.2f", + (double)ld->balance / 100 * ld->currency_mult100); + err = send_one_string (ctrl, name, numbuf); + } + else if (!strcmp (name, "X-MAXAMOUNT")) + { + snprintf (numbuf, sizeof numbuf, "%.2f", + (double)ld->maxamount / 100 * ld->currency_mult100); + err = send_one_string (ctrl, name, numbuf); + } + else if (!strcmp (name, "X-MAXAMOUNT1")) + { + snprintf (numbuf, sizeof numbuf, "%.2f", + (double)ld->maxamount1 / 100 * ld->currency_mult100); + err = send_one_string (ctrl, name, numbuf); + } + else + err = gpg_error (GPG_ERR_INV_NAME); + + return err; +} + + +static gpg_error_t +do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) +{ + static const char *names[] = { + "X-KBLZ", + "X-BANKINFO", + "X-CARDNO", + "X-EXPIRES", + "X-VALIDFROM", + "X-COUNTRY", + "X-CURRENCY", + "X-ZKACHIPID", + "X-OSVERSION", + "X-BALANCE", + "X-MAXAMOUNT", + "X-MAXAMOUNT1", + NULL + }; + gpg_error_t err = 0; + int idx; + + (void)flags; + + for (idx=0; names[idx] && !err; idx++) + err = do_getattr (app, ctrl, names[idx]); + return err; +} + + +static char * +copy_bcd (const unsigned char *string, size_t length) +{ + const unsigned char *s; + size_t n; + size_t needed; + char *buffer, *dst; + + if (!length) + return xtrystrdup (""); + + /* Skip leading zeroes. */ + for (; length && !*string; length--, string++) + ; + s = string; + n = length; + needed = 0; + for (; n ; n--, s++) + { + if (!needed && !(*s & 0xf0)) + ; /* Skip the leading zero in the first nibble. */ + else + { + if ( ((*s >> 4) & 0x0f) > 9 ) + { + errno = EINVAL; + return NULL; + } + needed++; + } + if ( n == 1 && (*s & 0x0f) > 9 ) + ; /* Ignore the last digit if it has the sign. */ + else + { + needed++; + if ( (*s & 0x0f) > 9 ) + { + errno = EINVAL; + return NULL; + } + } + + } + if (!needed) /* If it is all zero, print a "0". */ + needed++; + + buffer = dst = xtrymalloc (needed+1); + if (!buffer) + return NULL; + + s = string; + n = length; + needed = 0; + for (; n ; n--, s++) + { + if (!needed && !(*s & 0xf0)) + ; /* Skip the leading zero in the first nibble. */ + else + { + *dst++ = '0' + ((*s >> 4) & 0x0f); + needed++; + } + + if ( n == 1 && (*s & 0x0f) > 9 ) + ; /* Ignore the last digit if it has the sign. */ + else + { + *dst++ = '0' + (*s & 0x0f); + needed++; + } + } + if (!needed) + *dst++ = '0'; + *dst = 0; + + return buffer; +} + + +/* Convert the BCD number at STING of LENGTH into an integer and store + that at RESULT. Return 0 on success. */ +static gpg_error_t +bcd_to_int (const unsigned char *string, size_t length, int *result) +{ + char *tmp; + + tmp = copy_bcd (string, length); + if (!tmp) + return gpg_error (GPG_ERR_BAD_DATA); + *result = strtol (tmp, NULL, 10); + xfree (tmp); + return 0; +} + + +/* Select the Geldkarte application. */ +gpg_error_t +app_select_geldkarte (app_t app) +{ + static char const aid[] = + { 0xD2, 0x76, 0x00, 0x00, 0x25, 0x45, 0x50, 0x02, 0x00 }; + gpg_error_t err; + int slot = app->slot; + unsigned char *result = NULL; + size_t resultlen; + struct app_local_s *ld; + const char *banktype; + + err = iso7816_select_application (slot, aid, sizeof aid, 0); + if (err) + goto leave; + + /* Read the first record of EF_ID (SFI=0x17). We require this + record to be at least 24 bytes with the first byte 0x67 and a + correct filler byte. */ + err = iso7816_read_record (slot, 1, 1, ((0x17 << 3)|4), &result, &resultlen); + if (err) + goto leave; /* Oops - not a Geldkarte. */ + if (resultlen < 24 || *result != 0x67 || result[22]) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + + /* The short Bankleitzahl consists of 3 bytes at offset 1. */ + switch (result[1]) + { + case 0x21: banktype = "Oeffentlich-rechtliche oder private Bank"; break; + case 0x22: banktype = "Privat- oder Geschaeftsbank"; break; + case 0x25: banktype = "Sparkasse"; break; + case 0x26: + case 0x29: banktype = "Genossenschaftsbank"; break; + default: + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; /* Probably not a Geldkarte. */ + } + + app->apptype = APPTYPE_GELDKARTE; + app->fnc.deinit = do_deinit; + + /* If we don't have a serialno yet construct it from the EF_ID. */ + if (!app->serialno) + { + app->serialno = xtrymalloc (10); + if (!app->serialno) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (app->serialno, result, 10); + app->serialnolen = 10; + err = app_munge_serialno (app); + if (err) + goto leave; + } + + + app->app_local = ld = xtrycalloc (1, sizeof *app->app_local); + if (!app->app_local) + { + err = gpg_err_code_from_syserror (); + goto leave; + } + + snprintf (ld->kblz, sizeof ld->kblz, "%02X-%02X%02X", + result[1], result[2], result[3]); + ld->banktype = banktype; + ld->cardno = copy_bcd (result+4, 5); + if (!ld->cardno) + { + err = gpg_err_code_from_syserror (); + goto leave; + } + + snprintf (ld->expires, sizeof ld->expires, "20%02X-%02X", + result[10], result[11]); + snprintf (ld->validfrom, sizeof ld->validfrom, "20%02X-%02X-%02X", + result[12], result[13], result[14]); + + ld->country = copy_bcd (result+15, 2); + if (!ld->country) + { + err = gpg_err_code_from_syserror (); + goto leave; + } + + snprintf (ld->currency, sizeof ld->currency, "%c%c%c", + isascii (result[17])? result[17]:' ', + isascii (result[18])? result[18]:' ', + isascii (result[19])? result[19]:' '); + + ld->currency_mult100 = (result[20] == 0x01? 1: + result[20] == 0x02? 10: + result[20] == 0x04? 100: + result[20] == 0x08? 1000: + result[20] == 0x10? 10000: + result[20] == 0x20? 100000:0); + + ld->chipid = result[21]; + ld->osvers = result[23]; + + /* Read the first record of EF_BETRAG (SFI=0x18). */ + xfree (result); + err = iso7816_read_record (slot, 1, 1, ((0x18 << 3)|4), &result, &resultlen); + if (err) + goto leave; /* It does not make sense to continue. */ + if (resultlen < 12) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + err = bcd_to_int (result+0, 3, &ld->balance); + if (!err) + err = bcd_to_int (result+3, 3, &ld->maxamount); + if (!err) + err = bcd_to_int (result+6, 3, &ld->maxamount1); + /* The next 3 bytes are the maximum amount chargable without using a + MAC. This is usually 0. */ + if (err) + goto leave; + + /* Setup the rest of the methods. */ + app->fnc.learn_status = do_learn_status; + app->fnc.getattr = do_getattr; + + + leave: + xfree (result); + if (err) + do_deinit (app); + return err; +} diff --git a/scd/app-help.c b/scd/app-help.c new file mode 100644 index 0000000..8d225ef --- /dev/null +++ b/scd/app-help.c @@ -0,0 +1,285 @@ +/* app-help.c - Application helper functions + * Copyright (C) 2004, 2009 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "scdaemon.h" +#include "iso7816.h" +#include "../common/tlv.h" + + +/* Count the number of bits, assuming the A represents an unsigned big + integer of length LEN bytes. If A is NULL a length of 0 is + returned. */ +unsigned int +app_help_count_bits (const unsigned char *a, size_t len) +{ + unsigned int n = len * 8; + int i; + + if (!a) + return 0; + + for (; len && !*a; len--, a++, n -=8) + ; + if (len) + { + for (i=7; i && !(*a & (1<<i)); i--) + n--; + } + return n; +} + + +/* Return the KEYGRIP for the canonical encoded public key (PK,PKLEN) + * as an hex encoded string in the user provided buffer HEXKEYGRIP + * which must be of at least 41 bytes. If R_PKEY is not NULL and the + * function succeeded, the S-expression representing the key is stored + * there. The caller needs to call gcry_sexp_release on that. If + * R_ALGO is not NULL the public key algorithm id of Libgcrypt is + * stored there. If R_ALGOSTR is not NULL and the function succeeds a + * newly allocated algo string (e.g. "rsa2048") is stored there. + * HEXKEYGRIP may be NULL if the caller is not interested in it. */ +gpg_error_t +app_help_get_keygrip_string_pk (const void *pk, size_t pklen, char *hexkeygrip, + gcry_sexp_t *r_pkey, int *r_algo, + char **r_algostr) +{ + gpg_error_t err; + gcry_sexp_t s_pkey; + unsigned char array[KEYGRIP_LEN]; + + if (r_pkey) + *r_pkey = NULL; + if (r_algostr) + *r_algostr = NULL; + + err = gcry_sexp_sscan (&s_pkey, NULL, pk, pklen); + if (err) + return err; /* Can't parse that S-expression. */ + if (hexkeygrip && !gcry_pk_get_keygrip (s_pkey, array)) + { + gcry_sexp_release (s_pkey); + return gpg_error (GPG_ERR_GENERAL); /* Failed to calculate the keygrip.*/ + } + + if (r_algo) + *r_algo = get_pk_algo_from_key (s_pkey); + + if (r_algostr) + { + *r_algostr = pubkey_algo_string (s_pkey, NULL); + if (!*r_algostr) + { + err = gpg_error_from_syserror (); + gcry_sexp_release (s_pkey); + return err; + } + } + + if (r_pkey) + *r_pkey = s_pkey; + else + gcry_sexp_release (s_pkey); + + if (hexkeygrip) + bin2hex (array, KEYGRIP_LEN, hexkeygrip); + + return 0; +} + + +/* Return the KEYGRIP for the certificate CERT as an hex encoded + * string in the user provided buffer HEXKEYGRIP which must be of at + * least 41 bytes. If R_PKEY is not NULL and the function succeeded, + * the S-expression representing the key is stored there. The caller + * needs to call gcry_sexp_release on that. If R_ALGO is not NULL the + * public key algorithm id of Libgcrypt is stored there. */ +gpg_error_t +app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip, + gcry_sexp_t *r_pkey, int *r_algo) +{ + gpg_error_t err; + ksba_sexp_t p; + size_t n; + + if (r_pkey) + *r_pkey = NULL; + + p = ksba_cert_get_public_key (cert); + if (!p) + return gpg_error (GPG_ERR_BUG); + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + err = app_help_get_keygrip_string_pk ((void*)p, n, hexkeygrip, + r_pkey, r_algo, NULL); + ksba_free (p); + return err; +} + + +/* Get the public key from the binary encoded (CERT,CERTLEN). */ +gpg_error_t +app_help_pubkey_from_cert (const void *cert, size_t certlen, + unsigned char **r_pk, size_t *r_pklen) +{ + gpg_error_t err; + ksba_cert_t kc; + unsigned char *pk, *fixed_pk; + size_t pklen, fixed_pklen; + + *r_pk = NULL; + *r_pklen = 0; + + pk = NULL; /*(avoid cc warning)*/ + + err = ksba_cert_new (&kc); + if (err) + return err; + + err = ksba_cert_init_from_mem (kc, cert, certlen); + if (err) + goto leave; + + pk = ksba_cert_get_public_key (kc); + if (!pk) + { + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + pklen = gcry_sexp_canon_len (pk, 0, NULL, &err); + + err = uncompress_ecc_q_in_canon_sexp (pk, pklen, &fixed_pk, &fixed_pklen); + if (err) + goto leave; + if (fixed_pk) + { + ksba_free (pk); pk = NULL; + pk = fixed_pk; + pklen = fixed_pklen; + } + + leave: + if (!err) + { + *r_pk = pk; + *r_pklen = pklen; + } + else + ksba_free (pk); + ksba_cert_release (kc); + return err; +} + + +/* Given the SLOT and the File ID FID, return the length of the + certificate contained in that file. Returns 0 if the file does not + exists or does not contain a certificate. If R_CERTOFF is not + NULL, the length the header will be stored at this address; thus to + parse the X.509 certificate a read should start at that offset. + + On success the file is still selected. +*/ +size_t +app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff) +{ + gpg_error_t err; + unsigned char *buffer; + const unsigned char *p; + size_t buflen, n; + int class, tag, constructed, ndef; + size_t resultlen, objlen, hdrlen; + + err = iso7816_select_file (slot, fid, 0); + if (err) + { + log_info ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err)); + return 0; + } + + err = iso7816_read_binary (slot, 0, 32, &buffer, &buflen); + if (err) + { + log_info ("error reading certificate from FID 0x%04X: %s\n", + fid, gpg_strerror (err)); + return 0; + } + + if (!buflen || *buffer == 0xff) + { + log_info ("no certificate contained in FID 0x%04X\n", fid); + xfree (buffer); + return 0; + } + + p = buffer; + n = buflen; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + { + log_info ("error parsing certificate in FID 0x%04X: %s\n", + fid, gpg_strerror (err)); + xfree (buffer); + return 0; + } + + /* All certificates should commence with a SEQUENCE except for the + special ROOT CA which are enclosed in a SET. */ + if ( !(class == CLASS_UNIVERSAL && constructed + && (tag == TAG_SEQUENCE || tag == TAG_SET))) + { + log_info ("data at FID 0x%04X does not look like a certificate\n", fid); + return 0; + } + + resultlen = objlen + hdrlen; + if (r_certoff) + { + /* The callers want the offset to the actual certificate. */ + *r_certoff = hdrlen; + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + return 0; + + if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed) + { + /* The certificate seems to be contained in a + userCertificate container. Assume the following sequence + is the certificate. */ + *r_certoff += hdrlen + objlen; + if (*r_certoff > resultlen) + { + *r_certoff = 0; + return 0; /* That should never happen. */ + } + } + else + *r_certoff = 0; + } + + return resultlen; +} diff --git a/scd/app-nks.c b/scd/app-nks.c new file mode 100644 index 0000000..1f59321 --- /dev/null +++ b/scd/app-nks.c @@ -0,0 +1,1428 @@ +/* app-nks.c - The Telesec NKS card application. + * Copyright (C) 2004, 2007, 2008, 2009 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +/* Notes: + + - We are now targeting TCOS 3 cards and it may happen that there is + a regression towards TCOS 2 cards. Please report. + + - The TKS3 AUT key is not used. It seems that it is only useful for + the internal authentication command and not accessible by other + applications. The key itself is in the encryption class but the + corresponding certificate has only the digitalSignature + capability. + + - If required, we automagically switch between the NKS application + and the SigG application. This avoids to use the DINSIG + application which is somewhat limited, has no support for Secure + Messaging as required by TCOS 3 and has no way to change the PIN + or even set the NullPIN. + + - We use the prefix NKS-DF01 for TCOS 2 cards and NKS-NKS3 for newer + cards. This is because the NKS application has moved to DF02 with + TCOS 3 and thus we better use a DF independent tag. + + - We use only the global PINs for the NKS application. + + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <time.h> + +#include "scdaemon.h" +#include "../common/i18n.h" +#include "iso7816.h" +#include "../common/tlv.h" +#include "apdu.h" +#include "../common/host2net.h" + +static char const aid_nks[] = { 0xD2, 0x76, 0x00, 0x00, 0x03, 0x01, 0x02 }; +static char const aid_sigg[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 }; + + +static struct +{ + int is_sigg; /* Valid for SigG application. */ + int fid; /* File ID. */ + int nks_ver; /* 0 for NKS version 2, 3 for version 3. */ + int certtype; /* Type of certificate or 0 if it is not a certificate. */ + int iskeypair; /* If true has the FID of the corresponding certificate. */ + int issignkey; /* True if file is a key usable for signing. */ + int isenckey; /* True if file is a key usable for decryption. */ + unsigned char kid; /* Corresponding key references. */ +} filelist[] = { + { 0, 0x4531, 0, 0, 0xC000, 1, 0, 0x80 }, /* EF_PK.NKS.SIG */ + { 0, 0xC000, 0, 101 }, /* EF_C.NKS.SIG */ + { 0, 0x4331, 0, 100 }, + { 0, 0x4332, 0, 100 }, + { 0, 0xB000, 0, 110 }, /* EF_PK.RCA.NKS */ + { 0, 0x45B1, 0, 0, 0xC200, 0, 1, 0x81 }, /* EF_PK.NKS.ENC */ + { 0, 0xC200, 0, 101 }, /* EF_C.NKS.ENC */ + { 0, 0x43B1, 0, 100 }, + { 0, 0x43B2, 0, 100 }, +/* The authentication key is not used. */ +/* { 0, 0x4571, 3, 0, 0xC500, 0, 0, 0x82 }, /\* EF_PK.NKS.AUT *\/ */ +/* { 0, 0xC500, 3, 101 }, /\* EF_C.NKS.AUT *\/ */ + { 0, 0x45B2, 3, 0, 0xC201, 0, 1, 0x83 }, /* EF_PK.NKS.ENC1024 */ + { 0, 0xC201, 3, 101 }, /* EF_C.NKS.ENC1024 */ + { 1, 0x4531, 3, 0, 0xC000, 1, 1, 0x84 }, /* EF_PK.CH.SIG */ + { 1, 0xC000, 0, 101 }, /* EF_C.CH.SIG */ + { 1, 0xC008, 3, 101 }, /* EF_C.CA.SIG */ + { 1, 0xC00E, 3, 111 }, /* EF_C.RCA.SIG */ + { 0, 0 } +}; + + + +/* Object with application (i.e. NKS) specific data. */ +struct app_local_s { + int nks_version; /* NKS version. */ + + int sigg_active; /* True if switched to the SigG application. */ + int sigg_msig_checked;/* True if we checked for a mass signature card. */ + int sigg_is_msig; /* True if this is a mass signature card. */ + + int need_app_select; /* Need to re-select the application. */ + +}; + + + +static gpg_error_t switch_application (app_t app, int enable_sigg); + + + +/* Release local data. */ +static void +do_deinit (app_t app) +{ + if (app && app->app_local) + { + xfree (app->app_local); + app->app_local = NULL; + } +} + + +static int +all_zero_p (void *buffer, size_t length) +{ + char *p; + + for (p=buffer; length; length--, p++) + if (*p) + return 0; + return 1; +} + + +/* Read the file with FID, assume it contains a public key and return + its keygrip in the caller provided 41 byte buffer R_GRIPSTR. */ +static gpg_error_t +keygripstr_from_pk_file (app_t app, int fid, char *r_gripstr) +{ + gpg_error_t err; + unsigned char grip[20]; + unsigned char *buffer[2]; + size_t buflen[2]; + gcry_sexp_t sexp; + int i; + int offset[2] = { 0, 0 }; + + err = iso7816_select_file (app->slot, fid, 0); + if (err) + return err; + err = iso7816_read_record (app->slot, 1, 1, 0, &buffer[0], &buflen[0]); + if (err) + return err; + err = iso7816_read_record (app->slot, 2, 1, 0, &buffer[1], &buflen[1]); + if (err) + { + xfree (buffer[0]); + return err; + } + + if (app->app_local->nks_version < 3) + { + /* Old versions of NKS store the values in a TLV encoded format. + We need to do some checks. */ + for (i=0; i < 2; i++) + { + /* Check that the value appears like an integer encoded as + Simple-TLV. We don't check the tag because the tests cards I + have use 1 for both, the modulus and the exponent - the + example in the documentation gives 2 for the exponent. */ + if (buflen[i] < 3) + err = gpg_error (GPG_ERR_TOO_SHORT); + else if (buffer[i][1] != buflen[i]-2 ) + err = gpg_error (GPG_ERR_INV_OBJ); + else + offset[i] = 2; + } + } + else + { + /* Remove leading zeroes to get a correct keygrip. Take care of + negative numbers. We should also fix it the same way in + libgcrypt but we can't yet rely on it yet. */ + for (i=0; i < 2; i++) + { + while (buflen[i]-offset[i] > 1 + && !buffer[i][offset[i]] + && !(buffer[i][offset[i]+1] & 0x80)) + offset[i]++; + } + } + + /* Check whether negative values are not prefixed with a zero and + fix that. */ + for (i=0; i < 2; i++) + { + if ((buflen[i]-offset[i]) && (buffer[i][offset[i]] & 0x80)) + { + unsigned char *newbuf; + size_t newlen; + + newlen = 1 + buflen[i] - offset[i]; + newbuf = xtrymalloc (newlen); + if (!newbuf) + { + xfree (buffer[0]); + xfree (buffer[1]); + return gpg_error_from_syserror (); + } + newbuf[0] = 0; + memcpy (newbuf+1, buffer[i]+offset[i], buflen[i] - offset[i]); + xfree (buffer[i]); + buffer[i] = newbuf; + buflen[i] = newlen; + offset[i] = 0; + } + } + + if (!err) + err = gcry_sexp_build (&sexp, NULL, + "(public-key (rsa (n %b) (e %b)))", + (int)buflen[0]-offset[0], buffer[0]+offset[0], + (int)buflen[1]-offset[1], buffer[1]+offset[1]); + + xfree (buffer[0]); + xfree (buffer[1]); + if (err) + return err; + + if (!gcry_pk_get_keygrip (sexp, grip)) + { + err = gpg_error (GPG_ERR_INTERNAL); /* i.e. RSA not supported by + libgcrypt. */ + } + else + { + bin2hex (grip, 20, r_gripstr); + } + gcry_sexp_release (sexp); + return err; +} + + +/* TCOS responds to a verify with empty data (i.e. without the Lc + * byte) with the status of the PIN. PWID is the PIN ID, If SIGG is + * true, the application is switched into SigG mode. Returns: + * ISO7816_VERIFY_* codes or non-negative number of verification + * attempts left. */ +static int +get_chv_status (app_t app, int sigg, int pwid) +{ + if (switch_application (app, sigg)) + return sigg? -2 : -1; /* No such PIN / General error. */ + + return iso7816_verify_status (app_get_slot (app), pwid); +} + + +/* Implement the GETATTR command. This is similar to the LEARN + command but returns just one value via the status interface. */ +static gpg_error_t +do_getattr (app_t app, ctrl_t ctrl, const char *name) +{ + static struct { + const char *name; + int special; + } table[] = { + { "$AUTHKEYID", 1 }, + { "$ENCRKEYID", 2 }, + { "$SIGNKEYID", 3 }, + { "NKS-VERSION", 4 }, + { "CHV-STATUS", 5 }, + { NULL, 0 } + }; + gpg_error_t err = 0; + int idx; + char buffer[100]; + + err = switch_application (app, 0); + if (err) + return err; + + for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++) + ; + if (!table[idx].name) + return gpg_error (GPG_ERR_INV_NAME); + + switch (table[idx].special) + { + case 1: /* $AUTHKEYID */ + { + /* NetKey 3.0 cards define an authentication key but according + to the specs this key is only usable for encryption and not + signing. it might work anyway but it has not yet been + tested - fixme. Thus for now we use the NKS signature key + for authentication. */ + char const tmp[] = "NKS-NKS3.4531"; + send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); + } + break; + + case 2: /* $ENCRKEYID */ + { + char const tmp[] = "NKS-NKS3.45B1"; + send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); + } + break; + + case 3: /* $SIGNKEYID */ + { + char const tmp[] = "NKS-NKS3.4531"; + send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); + } + break; + + case 4: /* NKS-VERSION */ + snprintf (buffer, sizeof buffer, "%d", app->app_local->nks_version); + send_status_info (ctrl, table[idx].name, + buffer, strlen (buffer), NULL, 0); + break; + + case 5: /* CHV-STATUS */ + { + /* Returns: PW1.CH PW2.CH PW1.CH.SIG PW2.CH.SIG That are the + two global passwords followed by the two SigG passwords. + For the values, see the function get_chv_status. */ + int tmp[4]; + + /* We use a helper array so that we can control that there is + no superfluous application switch. Note that PW2.CH.SIG + really has the identifier 0x83 and not 0x82 as one would + expect. */ + tmp[0] = get_chv_status (app, 0, 0x00); + tmp[1] = get_chv_status (app, 0, 0x01); + tmp[2] = get_chv_status (app, 1, 0x81); + tmp[3] = get_chv_status (app, 1, 0x83); + snprintf (buffer, sizeof buffer, + "%d %d %d %d", tmp[0], tmp[1], tmp[2], tmp[3]); + send_status_info (ctrl, table[idx].name, + buffer, strlen (buffer), NULL, 0); + } + break; + + + default: + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + break; + } + + return err; +} + + + +static void +do_learn_status_core (app_t app, ctrl_t ctrl, unsigned int flags, int is_sigg) +{ + gpg_error_t err; + char ct_buf[100], id_buf[100]; + int i; + const char *tag; + const char *usage; + + if (is_sigg) + tag = "SIGG"; + else if (app->app_local->nks_version < 3) + tag = "DF01"; + else + tag = "NKS3"; + + /* Output information about all useful objects in the NKS application. */ + for (i=0; filelist[i].fid; i++) + { + if (filelist[i].nks_ver > app->app_local->nks_version) + continue; + + if (!!filelist[i].is_sigg != !!is_sigg) + continue; + + if (filelist[i].certtype && !(flags &1)) + { + size_t len; + + len = app_help_read_length_of_cert (app->slot, + filelist[i].fid, NULL); + if (len) + { + /* FIXME: We should store the length in the application's + context so that a following readcert does only need to + read that many bytes. */ + snprintf (ct_buf, sizeof ct_buf, "%d", filelist[i].certtype); + snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X", + tag, filelist[i].fid); + send_status_info (ctrl, "CERTINFO", + ct_buf, strlen (ct_buf), + id_buf, strlen (id_buf), + NULL, (size_t)0); + } + } + else if (filelist[i].iskeypair) + { + char gripstr[40+1]; + + err = keygripstr_from_pk_file (app, filelist[i].fid, gripstr); + if (err) + log_error ("can't get keygrip from FID 0x%04X: %s\n", + filelist[i].fid, gpg_strerror (err)); + else + { + snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X", + tag, filelist[i].fid); + if (filelist[i].issignkey && filelist[i].isenckey) + usage = "sae"; + else if (filelist[i].issignkey) + usage = "sa"; + else if (filelist[i].isenckey) + usage = "e"; + else + usage = ""; + + send_status_info (ctrl, "KEYPAIRINFO", + gripstr, 40, + id_buf, strlen (id_buf), + usage, strlen (usage), + NULL, (size_t)0); + } + } + } + + +} + + +static gpg_error_t +do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) +{ + gpg_error_t err; + + err = switch_application (app, 0); + if (err) + return err; + + do_learn_status_core (app, ctrl, flags, 0); + + err = switch_application (app, 1); + if (err) + return 0; /* Silently ignore if we can't switch to SigG. */ + + do_learn_status_core (app, ctrl, flags, 1); + + return 0; +} + + + + +/* Read the certificate with id CERTID (as returned by learn_status in + the CERTINFO status lines) and return it in the freshly allocated + buffer put into CERT and the length of the certificate put into + CERTLEN. */ +static gpg_error_t +do_readcert (app_t app, const char *certid, + unsigned char **cert, size_t *certlen) +{ + int i, fid; + gpg_error_t err; + unsigned char *buffer; + const unsigned char *p; + size_t buflen, n; + int class, tag, constructed, ndef; + size_t totobjlen, objlen, hdrlen; + int rootca = 0; + int is_sigg = 0; + + *cert = NULL; + *certlen = 0; + + if (!strncmp (certid, "NKS-NKS3.", 9)) + ; + else if (!strncmp (certid, "NKS-DF01.", 9)) + ; + else if (!strncmp (certid, "NKS-SIGG.", 9)) + is_sigg = 1; + else + return gpg_error (GPG_ERR_INV_ID); + + err = switch_application (app, is_sigg); + if (err) + return err; + + certid += 9; + if (!hexdigitp (certid) || !hexdigitp (certid+1) + || !hexdigitp (certid+2) || !hexdigitp (certid+3) + || certid[4]) + return gpg_error (GPG_ERR_INV_ID); + fid = xtoi_4 (certid); + for (i=0; filelist[i].fid; i++) + if ((filelist[i].certtype || filelist[i].iskeypair) + && filelist[i].fid == fid) + break; + if (!filelist[i].fid) + return gpg_error (GPG_ERR_NOT_FOUND); + + /* If the requested objects is a plain public key, redirect it to + the corresponding certificate. The whole system is a bit messy + because we sometime use the key directly or let the caller + retrieve the key from the certificate. The rationale for + that is to support not-yet stored certificates. */ + if (filelist[i].iskeypair) + fid = filelist[i].iskeypair; + + + /* Read the entire file. fixme: This could be optimized by first + reading the header to figure out how long the certificate + actually is. */ + err = iso7816_select_file (app->slot, fid, 0); + if (err) + { + log_error ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err)); + return err; + } + + err = iso7816_read_binary (app->slot, 0, 0, &buffer, &buflen); + if (err) + { + log_error ("error reading certificate from FID 0x%04X: %s\n", + fid, gpg_strerror (err)); + return err; + } + + if (!buflen || *buffer == 0xff) + { + log_info ("no certificate contained in FID 0x%04X\n", fid); + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + + /* Now figure something out about the object. */ + p = buffer; + n = buflen; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + if ( class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed ) + ; + else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed ) + rootca = 1; + else + return gpg_error (GPG_ERR_INV_OBJ); + totobjlen = objlen + hdrlen; + assert (totobjlen <= buflen); + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + + if (rootca) + ; + else if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed) + { + const unsigned char *save_p; + + /* The certificate seems to be contained in a userCertificate + container. Skip this and assume the following sequence is + the certificate. */ + if (n < objlen) + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + p += objlen; + n -= objlen; + save_p = p; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) ) + return gpg_error (GPG_ERR_INV_OBJ); + totobjlen = objlen + hdrlen; + assert (save_p + totobjlen <= buffer + buflen); + memmove (buffer, save_p, totobjlen); + } + + *cert = buffer; + buffer = NULL; + *certlen = totobjlen; + + leave: + xfree (buffer); + return err; +} + + +/* Handle the READKEY command. On success a canonical encoded + S-expression with the public key will get stored at PK and its + length at PKLEN; the caller must release that buffer. On error PK + and PKLEN are not changed and an error code is returned. As of now + this function is only useful for the internal authentication key. + Other keys are automagically retrieved via by means of the + certificate parsing code in commands.c:cmd_readkey. For internal + use PK and PKLEN may be NULL to just check for an existing key. */ +static gpg_error_t +do_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned int flags, + unsigned char **pk, size_t *pklen) +{ + gpg_error_t err; + unsigned char *buffer[2]; + size_t buflen[2]; + unsigned short path[1] = { 0x4500 }; + + (void)ctrl; + + if ((flags & APP_READKEY_FLAG_ADVANCED)) + return GPG_ERR_NOT_SUPPORTED; + + /* We use a generic name to retrieve PK.AUT.IFD-SPK. */ + if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3) + ; + else /* Return the error code expected by cmd_readkey. */ + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + /* Access the KEYD file which is always in the master directory. */ + err = iso7816_select_path (app_get_slot (app), path, DIM (path), 0); + if (err) + return err; + /* Due to the above select we need to re-select our application. */ + app->app_local->need_app_select = 1; + /* Get the two records. */ + err = iso7816_read_record (app->slot, 5, 1, 0, &buffer[0], &buflen[0]); + if (err) + return err; + if (all_zero_p (buffer[0], buflen[0])) + { + xfree (buffer[0]); + return gpg_error (GPG_ERR_NOT_FOUND); + } + err = iso7816_read_record (app->slot, 6, 1, 0, &buffer[1], &buflen[1]); + if (err) + { + xfree (buffer[0]); + return err; + } + + if (pk && pklen) + { + *pk = make_canon_sexp_from_rsa_pk (buffer[0], buflen[0], + buffer[1], buflen[1], + pklen); + if (!*pk) + err = gpg_error_from_syserror (); + } + + xfree (buffer[0]); + xfree (buffer[1]); + return err; +} + + +/* Handle the WRITEKEY command for NKS. This function expects a + canonical encoded S-expression with the public key in KEYDATA and + its length in KEYDATALEN. The only supported KEYID is + "$IFDAUTHKEY" to store the terminal key on the card. Bit 0 of + FLAGS indicates whether an existing key shall get overwritten. + PINCB and PINCB_ARG are the usual arguments for the pinentry + callback. */ +static gpg_error_t +do_writekey (app_t app, ctrl_t ctrl, + const char *keyid, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *keydata, size_t keydatalen) +{ + gpg_error_t err; + int force = (flags & 1); + const unsigned char *rsa_n = NULL; + const unsigned char *rsa_e = NULL; + size_t rsa_n_len, rsa_e_len; + unsigned int nbits; + + (void)pincb; + (void)pincb_arg; + + if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3) + ; + else + return gpg_error (GPG_ERR_INV_ID); + + if (!force && !do_readkey (app, ctrl, keyid, 0, NULL, NULL)) + return gpg_error (GPG_ERR_EEXIST); + + /* Parse the S-expression. */ + err = get_rsa_pk_from_canon_sexp (keydata, keydatalen, + &rsa_n, &rsa_n_len, &rsa_e, &rsa_e_len); + if (err) + goto leave; + + /* Check that the parameters match the requirements. */ + nbits = app_help_count_bits (rsa_n, rsa_n_len); + if (nbits != 1024) + { + log_error (_("RSA modulus missing or not of size %d bits\n"), 1024); + err = gpg_error (GPG_ERR_BAD_PUBKEY); + goto leave; + } + + nbits = app_help_count_bits (rsa_e, rsa_e_len); + if (nbits < 2 || nbits > 32) + { + log_error (_("RSA public exponent missing or larger than %d bits\n"), + 32); + err = gpg_error (GPG_ERR_BAD_PUBKEY); + goto leave; + } + +/* /\* Store them. *\/ */ +/* err = verify_pin (app, 0, NULL, pincb, pincb_arg); */ +/* if (err) */ +/* goto leave; */ + + /* Send the MSE:Store_Public_Key. */ + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); +/* mse = xtrymalloc (1000); */ + +/* mse[0] = 0x80; /\* Algorithm reference. *\/ */ +/* mse[1] = 1; */ +/* mse[2] = 0x17; */ +/* mse[3] = 0x84; /\* Private key reference. *\/ */ +/* mse[4] = 1; */ +/* mse[5] = 0x77; */ +/* mse[6] = 0x7F; /\* Public key parameter. *\/ */ +/* mse[7] = 0x49; */ +/* mse[8] = 0x81; */ +/* mse[9] = 3 + 0x80 + 2 + rsa_e_len; */ +/* mse[10] = 0x81; /\* RSA modulus of 128 byte. *\/ */ +/* mse[11] = 0x81; */ +/* mse[12] = rsa_n_len; */ +/* memcpy (mse+12, rsa_n, rsa_n_len); */ +/* mse[10] = 0x82; /\* RSA public exponent of up to 4 bytes. *\/ */ +/* mse[12] = rsa_e_len; */ +/* memcpy (mse+12, rsa_e, rsa_e_len); */ +/* err = iso7816_manage_security_env (app->slot, 0x81, 0xB6, */ +/* mse, sizeof mse); */ + + leave: + return err; +} + + +static gpg_error_t +basic_pin_checks (const char *pinvalue, int minlen, int maxlen) +{ + if (strlen (pinvalue) < minlen) + { + log_error ("PIN is too short; minimum length is %d\n", minlen); + return gpg_error (GPG_ERR_BAD_PIN); + } + if (strlen (pinvalue) > maxlen) + { + log_error ("PIN is too large; maximum length is %d\n", maxlen); + return gpg_error (GPG_ERR_BAD_PIN); + } + return 0; +} + + +/* Verify the PIN if required. */ +static gpg_error_t +verify_pin (app_t app, int pwid, const char *desc, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + pininfo_t pininfo; + int rc; + + if (!desc) + desc = "PIN"; + + memset (&pininfo, 0, sizeof pininfo); + pininfo.fixedlen = -1; + pininfo.minlen = 6; + pininfo.maxlen = 16; + + if (!opt.disable_pinpad + && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) ) + { + rc = pincb (pincb_arg, desc, NULL); + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + return rc; + } + + rc = iso7816_verify_kp (app->slot, pwid, &pininfo); + pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */ + } + else + { + char *pinvalue; + + rc = pincb (pincb_arg, desc, &pinvalue); + if (rc) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); + return rc; + } + + rc = basic_pin_checks (pinvalue, pininfo.minlen, pininfo.maxlen); + if (rc) + { + xfree (pinvalue); + return rc; + } + + rc = iso7816_verify (app->slot, pwid, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + } + + if (rc) + { + if ( gpg_err_code (rc) == GPG_ERR_USE_CONDITIONS ) + log_error (_("the NullPIN has not yet been changed\n")); + else + log_error ("verify PIN failed\n"); + return rc; + } + + return 0; +} + + +/* 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; + that callback should return the PIN in an allocated buffer and + store that in the 3rd argument. */ +static gpg_error_t +do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, + gpg_error_t (*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, i; + int is_sigg = 0; + int fid; + unsigned char kid; + unsigned char data[83]; /* Must be large enough for a SHA-1 digest + + the largest OID prefix. */ + size_t datalen; + + (void)ctrl; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + switch (indatalen) + { + case 16: case 20: case 35: case 47: case 51: case 67: case 83: break; + default: return gpg_error (GPG_ERR_INV_VALUE); + } + + /* Check that the provided ID is valid. This is not really needed + but we do it to enforce correct usage by the caller. */ + if (!strncmp (keyidstr, "NKS-NKS3.", 9) ) + ; + else if (!strncmp (keyidstr, "NKS-DF01.", 9) ) + ; + else if (!strncmp (keyidstr, "NKS-SIGG.", 9) ) + is_sigg = 1; + else + return gpg_error (GPG_ERR_INV_ID); + keyidstr += 9; + + rc = switch_application (app, is_sigg); + if (rc) + return rc; + + if (is_sigg && app->app_local->sigg_is_msig) + { + log_info ("mass signature cards are not allowed\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1) + || !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3) + || keyidstr[4]) + return gpg_error (GPG_ERR_INV_ID); + fid = xtoi_4 (keyidstr); + for (i=0; filelist[i].fid; i++) + if (filelist[i].iskeypair && filelist[i].fid == fid) + break; + if (!filelist[i].fid) + return gpg_error (GPG_ERR_NOT_FOUND); + if (!filelist[i].issignkey) + return gpg_error (GPG_ERR_INV_ID); + kid = filelist[i].kid; + + /* Prepare the DER object from INDATA. */ + if (app->app_local->nks_version > 2 && (indatalen == 35 + || indatalen == 47 + || indatalen == 51 + || indatalen == 67 + || indatalen == 83)) + { + /* The caller send data matching the length of the ASN.1 encoded + hash for SHA-{1,224,256,384,512}. Assume that is okay. */ + assert (indatalen <= sizeof data); + memcpy (data, indata, indatalen); + datalen = indatalen; + } + else if (indatalen == 35) + { + /* Alright, the caller was so kind to send us an already + prepared DER object. This is for TCOS 2. */ + if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15)) + ; + else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata,rmd160_prefix,15)) + ; + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + memcpy (data, indata, indatalen); + datalen = 35; + } + else if (indatalen == 20) + { + 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); + datalen = 35; + } + else + return gpg_error (GPG_ERR_INV_VALUE); + + + /* Send an MSE for PSO:Computer_Signature. */ + if (app->app_local->nks_version > 2) + { + unsigned char mse[6]; + + mse[0] = 0x80; /* Algorithm reference. */ + mse[1] = 1; + mse[2] = 2; /* RSA, card does pkcs#1 v1.5 padding, no ASN.1 check. */ + mse[3] = 0x84; /* Private key reference. */ + mse[4] = 1; + mse[5] = kid; + rc = iso7816_manage_security_env (app->slot, 0x41, 0xB6, + mse, sizeof mse); + } + /* Verify using PW1.CH. */ + if (!rc) + rc = verify_pin (app, 0, NULL, pincb, pincb_arg); + /* Compute the signature. */ + if (!rc) + rc = iso7816_compute_ds (app->slot, 0, data, datalen, 0, + outdata, outdatalen); + 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. */ +static gpg_error_t +do_decipher (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen, + unsigned int *r_info) +{ + int rc, i; + int is_sigg = 0; + int fid; + int kid; + + (void)ctrl; + (void)r_info; + + if (!keyidstr || !*keyidstr || !indatalen) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check that the provided ID is valid. This is not really needed + but we do it to enforce correct usage by the caller. */ + if (!strncmp (keyidstr, "NKS-NKS3.", 9) ) + ; + else if (!strncmp (keyidstr, "NKS-DF01.", 9) ) + ; + else if (!strncmp (keyidstr, "NKS-SIGG.", 9) ) + is_sigg = 1; + else + return gpg_error (GPG_ERR_INV_ID); + keyidstr += 9; + + rc = switch_application (app, is_sigg); + if (rc) + return rc; + + if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1) + || !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3) + || keyidstr[4]) + return gpg_error (GPG_ERR_INV_ID); + fid = xtoi_4 (keyidstr); + for (i=0; filelist[i].fid; i++) + if (filelist[i].iskeypair && filelist[i].fid == fid) + break; + if (!filelist[i].fid) + return gpg_error (GPG_ERR_NOT_FOUND); + if (!filelist[i].isenckey) + return gpg_error (GPG_ERR_INV_ID); + kid = filelist[i].kid; + + if (app->app_local->nks_version > 2) + { + unsigned char mse[6]; + mse[0] = 0x80; /* Algorithm reference. */ + mse[1] = 1; + mse[2] = 0x0a; /* RSA no padding. (0x1A is pkcs#1.5 padding.) */ + mse[3] = 0x84; /* Private key reference. */ + mse[4] = 1; + mse[5] = kid; + rc = iso7816_manage_security_env (app->slot, 0x41, 0xB8, + mse, sizeof mse); + } + else + { + static const unsigned char mse[] = + { + 0x80, 1, 0x10, /* Select algorithm RSA. */ + 0x84, 1, 0x81 /* Select local secret key 1 for decryption. */ + }; + rc = iso7816_manage_security_env (app->slot, 0xC1, 0xB8, + mse, sizeof mse); + + } + + if (!rc) + rc = verify_pin (app, 0, NULL, pincb, pincb_arg); + + /* Note that we need to use extended length APDUs for TCOS 3 cards. + Command chaining does not work. */ + if (!rc) + rc = iso7816_decipher (app->slot, app->app_local->nks_version > 2? 1:0, + indata, indatalen, 0, 0x81, + outdata, outdatalen); + return rc; +} + + + +/* Parse a password ID string. Returns NULL on error or a string + suitable as passphrase prompt on success. On success stores the + reference value for the password at R_PWID and a flag indicating + that the SigG application is to be used at R_SIGG. If NEW_MODE is + true, the returned description is suitable for a new Password. + Supported values for PWIDSTR are: + + PW1.CH - Global password 1 + PW2.CH - Global password 2 + PW1.CH.SIG - SigG password 1 + PW2.CH.SIG - SigG password 2 + */ +static const char * +parse_pwidstr (const char *pwidstr, int new_mode, int *r_sigg, int *r_pwid) +{ + const char *desc; + + if (!pwidstr) + desc = NULL; + else if (!strcmp (pwidstr, "PW1.CH")) + { + *r_sigg = 0; + *r_pwid = 0x00; + /* TRANSLATORS: Do not translate the "|*|" prefixes but keep + them verbatim at the start of the string. */ + desc = (new_mode + ? _("|N|Please enter a new PIN for the standard keys.") + : _("||Please enter the PIN for the standard keys.")); + } + else if (!strcmp (pwidstr, "PW2.CH")) + { + *r_pwid = 0x01; + desc = (new_mode + ? _("|NP|Please enter a new PIN Unblocking Code (PUK) " + "for the standard keys.") + : _("|P|Please enter the PIN Unblocking Code (PUK) " + "for the standard keys.")); + } + else if (!strcmp (pwidstr, "PW1.CH.SIG")) + { + *r_pwid = 0x81; + *r_sigg = 1; + desc = (new_mode + ? _("|N|Please enter a new PIN for the key to create " + "qualified signatures.") + : _("||Please enter the PIN for the key to create " + "qualified signatures.")); + } + else if (!strcmp (pwidstr, "PW2.CH.SIG")) + { + *r_pwid = 0x83; /* Yes, that is 83 and not 82. */ + *r_sigg = 1; + desc = (new_mode + ? _("|NP|Please enter a new PIN Unblocking Code (PUK) " + "for the key to create qualified signatures.") + : _("|P|Please enter the PIN Unblocking Code (PUK) " + "for the key to create qualified signatures.")); + } + else + { + *r_pwid = 0; /* Only to avoid gcc warning in calling function. */ + desc = NULL; /* Error. */ + } + + return desc; +} + + +/* Handle the PASSWD command. See parse_pwidstr() for allowed values + for CHVNOSTR. */ +static gpg_error_t +do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr, + unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + char *newpin = NULL; + char *oldpin = NULL; + size_t newpinlen; + size_t oldpinlen; + int is_sigg; + const char *newdesc; + int pwid; + pininfo_t pininfo; + + (void)ctrl; + + /* The minimum length is enforced by TCOS, the maximum length is + just a reasonable value. */ + memset (&pininfo, 0, sizeof pininfo); + pininfo.minlen = 6; + pininfo.maxlen = 16; + + newdesc = parse_pwidstr (pwidstr, 1, &is_sigg, &pwid); + if (!newdesc) + return gpg_error (GPG_ERR_INV_ID); + + if ((flags & APP_CHANGE_FLAG_CLEAR)) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + err = switch_application (app, is_sigg); + if (err) + return err; + + if ((flags & APP_CHANGE_FLAG_NULLPIN)) + { + /* With the nullpin flag, we do not verify the PIN - it would + fail if the Nullpin is still set. */ + oldpin = xtrycalloc (1, 6); + if (!oldpin) + { + err = gpg_error_from_syserror (); + goto leave; + } + oldpinlen = 6; + } + else + { + const char *desc; + int dummy1, dummy2; + + if ((flags & APP_CHANGE_FLAG_RESET)) + { + /* Reset mode: Ask for the alternate PIN. */ + const char *altpwidstr; + + if (!strcmp (pwidstr, "PW1.CH")) + altpwidstr = "PW2.CH"; + else if (!strcmp (pwidstr, "PW2.CH")) + altpwidstr = "PW1.CH"; + else if (!strcmp (pwidstr, "PW1.CH.SIG")) + altpwidstr = "PW2.CH.SIG"; + else if (!strcmp (pwidstr, "PW2.CH.SIG")) + altpwidstr = "PW1.CH.SIG"; + else + { + err = gpg_error (GPG_ERR_BUG); + goto leave; + } + desc = parse_pwidstr (altpwidstr, 0, &dummy1, &dummy2); + } + else + { + /* Regular change mode: Ask for the old PIN. */ + desc = parse_pwidstr (pwidstr, 0, &dummy1, &dummy2); + } + err = pincb (pincb_arg, desc, &oldpin); + if (err) + { + log_error ("error getting old PIN: %s\n", gpg_strerror (err)); + goto leave; + } + oldpinlen = strlen (oldpin); + err = basic_pin_checks (oldpin, pininfo.minlen, pininfo.maxlen); + if (err) + goto leave; + } + + err = pincb (pincb_arg, newdesc, &newpin); + if (err) + { + log_error (_("error getting new PIN: %s\n"), gpg_strerror (err)); + goto leave; + } + newpinlen = strlen (newpin); + + err = basic_pin_checks (newpin, pininfo.minlen, pininfo.maxlen); + if (err) + goto leave; + + if ((flags & APP_CHANGE_FLAG_RESET)) + { + char *data; + size_t datalen = oldpinlen + newpinlen; + + data = xtrymalloc (datalen); + if (!data) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (data, oldpin, oldpinlen); + memcpy (data+oldpinlen, newpin, newpinlen); + err = iso7816_reset_retry_counter_with_rc (app->slot, pwid, + data, datalen); + wipememory (data, datalen); + xfree (data); + } + else + err = iso7816_change_reference_data (app->slot, pwid, + oldpin, oldpinlen, + newpin, newpinlen); + leave: + xfree (oldpin); + xfree (newpin); + return err; +} + + +/* Perform a simple verify operation. KEYIDSTR should be NULL or empty. */ +static gpg_error_t +do_check_pin (app_t app, ctrl_t ctrl, const char *pwidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + int pwid; + int is_sigg; + const char *desc; + + (void)ctrl; + + desc = parse_pwidstr (pwidstr, 0, &is_sigg, &pwid); + if (!desc) + return gpg_error (GPG_ERR_INV_ID); + + err = switch_application (app, is_sigg); + if (err) + return err; + + return verify_pin (app, pwid, desc, pincb, pincb_arg); +} + + +/* Return the version of the NKS application. */ +static int +get_nks_version (int slot) +{ + unsigned char *result = NULL; + size_t resultlen; + int type; + + if (iso7816_apdu_direct (slot, "\x80\xaa\x06\x00\x00", 5, 0, + NULL, &result, &resultlen)) + return 2; /* NKS 2 does not support this command. */ + + /* Example value: 04 11 19 22 21 6A 20 80 03 03 01 01 01 00 00 00 + vv tt ccccccccccccccccc aa bb cc vvvvvvvvvvv xx + vendor (Philips) -+ | | | | | | | + chip type -----------+ | | | | | | + chip id ----------------+ | | | | | + card type (3 - tcos 3) -------------------+ | | | | + OS version of card type ---------------------+ | | | + OS release of card type ------------------------+ | | + OS vendor internal version ------------------------+ | + RFU -----------------------------------------------------------+ + */ + if (resultlen < 16) + type = 0; /* Invalid data returned. */ + else + type = result[8]; + xfree (result); + + return type; +} + + +/* If ENABLE_SIGG is true switch to the SigG application if not yet + active. If false switch to the NKS application if not yet active. + Returns 0 on success. */ +static gpg_error_t +switch_application (app_t app, int enable_sigg) +{ + gpg_error_t err; + + if (((app->app_local->sigg_active && enable_sigg) + || (!app->app_local->sigg_active && !enable_sigg)) + && !app->app_local->need_app_select) + return 0; /* Already switched. */ + + log_info ("app-nks: switching to %s\n", enable_sigg? "SigG":"NKS"); + if (enable_sigg) + err = iso7816_select_application (app->slot, aid_sigg, sizeof aid_sigg, 0); + else + err = iso7816_select_application (app->slot, aid_nks, sizeof aid_nks, 0); + + if (!err && enable_sigg && app->app_local->nks_version >= 3 + && !app->app_local->sigg_msig_checked) + { + /* Check whether this card is a mass signature card. */ + unsigned char *buffer; + size_t buflen; + const unsigned char *tmpl; + size_t tmpllen; + + app->app_local->sigg_msig_checked = 1; + app->app_local->sigg_is_msig = 1; + err = iso7816_select_file (app->slot, 0x5349, 0); + if (!err) + err = iso7816_read_record (app->slot, 1, 1, 0, &buffer, &buflen); + if (!err) + { + tmpl = find_tlv (buffer, buflen, 0x7a, &tmpllen); + if (tmpl && tmpllen == 12 + && !memcmp (tmpl, + "\x93\x02\x00\x01\xA4\x06\x83\x01\x81\x83\x01\x83", + 12)) + app->app_local->sigg_is_msig = 0; + xfree (buffer); + } + if (app->app_local->sigg_is_msig) + log_info ("This is a mass signature card\n"); + } + + if (!err) + { + app->app_local->need_app_select = 0; + app->app_local->sigg_active = enable_sigg; + } + else + log_error ("app-nks: error switching to %s: %s\n", + enable_sigg? "SigG":"NKS", gpg_strerror (err)); + + return err; +} + + +/* Select the NKS application. */ +gpg_error_t +app_select_nks (app_t app) +{ + int slot = app->slot; + int rc; + + rc = iso7816_select_application (slot, aid_nks, sizeof aid_nks, 0); + if (!rc) + { + app->apptype = APPTYPE_NKS; + + app->app_local = xtrycalloc (1, sizeof *app->app_local); + if (!app->app_local) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + goto leave; + } + + app->app_local->nks_version = get_nks_version (slot); + if (opt.verbose) + log_info ("Detected NKS version: %d\n", app->app_local->nks_version); + + app->fnc.deinit = do_deinit; + app->fnc.learn_status = do_learn_status; + app->fnc.readcert = do_readcert; + app->fnc.readkey = do_readkey; + app->fnc.getattr = do_getattr; + app->fnc.setattr = NULL; + app->fnc.writekey = do_writekey; + app->fnc.genkey = NULL; + app->fnc.sign = do_sign; + app->fnc.auth = NULL; + app->fnc.decipher = do_decipher; + app->fnc.change_pin = do_change_pin; + app->fnc.check_pin = do_check_pin; + } + + leave: + if (rc) + do_deinit (app); + return rc; +} diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c new file mode 100644 index 0000000..da6dc7a --- /dev/null +++ b/scd/app-openpgp.c @@ -0,0 +1,5480 @@ +/* app-openpgp.c - The OpenPGP card application. + * Copyright (C) 2003-2005, 2007-2009, + * 2013-2015 Free Software Foundation, Inc. + * Copyright (C) 2003-2005, 2007-2009, 2013-2015, 2020 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +/* Some notes: + + CHV means Card Holder Verification and is nothing else than a PIN + or password. That term seems to have been used originally with GSM + cards. Version v2 of the specs changes the term to the clearer + term PW for password. We use the terms here interchangeable + because we do not want to change existing strings i18n wise. + + Version 2 of the specs also drops the separate PW2 which was + required in v1 due to ISO requirements. It is now possible to have + one physical PW but two reference to it so that they can be + individually be verified (e.g. to implement a forced verification + for one key). Thus you will noticed the use of PW2 with the verify + command but not with change_reference_data because the latter + operates directly on the physical PW. + + The Reset Code (RC) as implemented by v2 cards uses the same error + counter as the PW2 of v1 cards. By default no RC is set and thus + that error counter is set to 0. After setting the RC the error + counter will be initialized to 3. + + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <assert.h> +#include <time.h> + +#include "scdaemon.h" + +#include "../common/util.h" +#include "../common/i18n.h" +#include "iso7816.h" +#include "../common/tlv.h" +#include "../common/host2net.h" +#include "../common/openpgpdefs.h" + + +#define KDF_DATA_LENGTH_MIN 90 +#define KDF_DATA_LENGTH_MAX 110 + +/* A table describing the DOs of the card. */ +static struct { + int tag; + int constructed; + int get_from; /* Constructed DO with this DO or 0 for direct access. */ + unsigned int binary:1; + unsigned int dont_cache:1; + unsigned int flush_on_error:1; + unsigned int get_immediate_in_v11:1; /* Enable a hack to bypass the cache of + this data object if it is used in 1.1 + and later versions of the card. This + does not work with composite DO and + is currently only useful for the CHV + status bytes. */ + unsigned int try_extlen:2; /* Large object; try to use an extended + length APDU when !=0. The size is + determined by extcap.max_certlen_3 + when == 1, and by extcap.max_special_do + when == 2. */ + char *desc; +} data_objects[] = { + { 0x005E, 0, 0, 1, 0, 0, 0, 2, "Login Data" }, + { 0x5F50, 0, 0, 0, 0, 0, 0, 2, "URL" }, + { 0x5F52, 0, 0, 1, 0, 0, 0, 0, "Historical Bytes" }, + { 0x0065, 1, 0, 1, 0, 0, 0, 0, "Cardholder Related Data"}, + { 0x005B, 0, 0x65, 0, 0, 0, 0, 0, "Name" }, + { 0x5F2D, 0, 0x65, 0, 0, 0, 0, 0, "Language preferences" }, + { 0x5F35, 0, 0x65, 0, 0, 0, 0, 0, "Salutation" }, + { 0x006E, 1, 0, 1, 0, 0, 0, 0, "Application Related Data" }, + { 0x004F, 0, 0x6E, 1, 0, 0, 0, 0, "AID" }, + { 0x0073, 1, 0, 1, 0, 0, 0, 0, "Discretionary Data Objects" }, + { 0x0047, 0, 0x6E, 1, 1, 0, 0, 0, "Card Capabilities" }, + { 0x00C0, 0, 0x6E, 1, 1, 0, 0, 0, "Extended Card Capabilities" }, + { 0x00C1, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Signature" }, + { 0x00C2, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Decryption" }, + { 0x00C3, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Authentication" }, + { 0x00C4, 0, 0x6E, 1, 0, 1, 1, 0, "CHV Status Bytes" }, + { 0x00C5, 0, 0x6E, 1, 0, 0, 0, 0, "Fingerprints" }, + { 0x00C6, 0, 0x6E, 1, 0, 0, 0, 0, "CA Fingerprints" }, + { 0x00CD, 0, 0x6E, 1, 0, 0, 0, 0, "Generation time" }, + { 0x007A, 1, 0, 1, 0, 0, 0, 0, "Security Support Template" }, + { 0x0093, 0, 0x7A, 1, 1, 0, 0, 0, "Digital Signature Counter" }, + { 0x0101, 0, 0, 0, 0, 0, 0, 2, "Private DO 1"}, + { 0x0102, 0, 0, 0, 0, 0, 0, 2, "Private DO 2"}, + { 0x0103, 0, 0, 0, 0, 0, 0, 2, "Private DO 3"}, + { 0x0104, 0, 0, 0, 0, 0, 0, 2, "Private DO 4"}, + { 0x7F21, 1, 0, 1, 0, 0, 0, 1, "Cardholder certificate"}, + /* V3.0 */ + { 0x7F74, 0, 0, 1, 0, 0, 0, 0, "General Feature Management"}, + { 0x00D5, 0, 0, 1, 0, 0, 0, 0, "AES key data"}, + { 0x00F9, 0, 0, 1, 0, 0, 0, 0, "KDF data object"}, + { 0 } +}; + + +/* Type of keys. */ +typedef enum + { + KEY_TYPE_ECC, + KEY_TYPE_RSA, + } +key_type_t; + + +/* The format of RSA private keys. */ +typedef enum + { + RSA_UNKNOWN_FMT, + RSA_STD, + RSA_STD_N, + RSA_CRT, + RSA_CRT_N + } +rsa_key_format_t; + + +/* One cache item for DOs. */ +struct cache_s { + struct cache_s *next; + int tag; + size_t length; + unsigned char data[1]; +}; + + +/* Object with application (i.e. OpenPGP card) specific data. */ +struct app_local_s { + /* A linked list with cached DOs. */ + struct cache_s *cache; + + /* Keep track of the public keys. */ + struct + { + int read_done; /* True if we have at least tried to read them. */ + unsigned char *key; /* This is a malloced buffer with a canonical + encoded S-expression encoding a public + key. Might be NULL if key is not + available. */ + size_t keylen; /* The length of the above S-expression. This + is usually only required for cross checks + because the length of an S-expression is + implicitly available. */ + unsigned char keygrip_str[41]; /* The keygrip, null terminated */ + } pk[3]; + + unsigned char status_indicator; /* The card status indicator. */ + + unsigned int manufacturer:16; /* Manufacturer ID from the s/n. */ + + /* Keep track of the ISO card capabilities. */ + struct + { + unsigned int cmd_chaining:1; /* Command chaining is supported. */ + unsigned int ext_lc_le:1; /* Extended Lc and Le are supported. */ + } cardcap; + + /* Keep track of extended card capabilities. */ + struct + { + unsigned int is_v2:1; /* Compatible to v2 or later. */ + unsigned int extcap_v3:1; /* Extcap is in v3 format. */ + unsigned int has_button:1; /* Has confirmation button or not. */ + + unsigned int sm_supported:1; /* Secure Messaging is supported. */ + unsigned int get_challenge:1; + unsigned int key_import:1; + unsigned int change_force_chv:1; + unsigned int private_dos:1; + unsigned int algo_attr_change:1; /* Algorithm attributes changeable. */ + unsigned int has_decrypt:1; /* Support symmetric decryption. */ + unsigned int kdf_do:1; /* Support KDF DO. */ + + unsigned int sm_algo:2; /* Symmetric crypto algo for SM. */ + unsigned int pin_blk2:1; /* PIN block 2 format supported. */ + unsigned int mse:1; /* MSE command supported. */ + unsigned int max_certlen_3:16; + unsigned int max_get_challenge:16; /* Maximum size for get_challenge. */ + unsigned int max_special_do:16; /* Maximum size for special DOs. */ + } extcap; + + /* Flags used to control the application. */ + struct + { + unsigned int no_sync:1; /* Do not sync CHV1 and CHV2 */ + unsigned int def_chv2:1; /* Use 123456 for CHV2. */ + } flags; + + /* Pinpad request specified on card. */ + struct + { + unsigned int disabled:1; /* No pinpad use because of KDF DO. */ + unsigned int specified:1; + int fixedlen_user; + int fixedlen_admin; + } pinpad; + + struct + { + key_type_t key_type; + union { + struct { + unsigned int n_bits; /* Size of the modulus in bits. The rest + of this strucuire is only valid if + this is not 0. */ + unsigned int e_bits; /* Size of the public exponent in bits. */ + rsa_key_format_t format; + } rsa; + struct { + const char *curve; + int flags; + } ecc; + }; + } keyattr[3]; +}; + +#define ECC_FLAG_DJB_TWEAK (1 << 0) +#define ECC_FLAG_PUBKEY (1 << 1) + + +/***** Local prototypes *****/ +static unsigned long convert_sig_counter_value (const unsigned char *value, + size_t valuelen); +static unsigned long get_sig_counter (app_t app); +static gpg_error_t do_auth (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); +static gpg_error_t parse_algorithm_attribute (app_t app, int keyno); +static gpg_error_t change_keyattr_from_string + (app_t app, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *value, size_t valuelen); + + +/* Return the OpenPGP card manufacturer name. */ +static const char * +get_manufacturer (unsigned int no) +{ + /* Note: Make sure that there is no colon or linefeed in the string. */ + switch (no) + { + case 0x0001: return "PPC Card Systems"; + case 0x0002: return "Prism"; + case 0x0003: return "OpenFortress"; + case 0x0004: return "Wewid"; + case 0x0005: return "ZeitControl"; + case 0x0006: return "Yubico"; + case 0x0007: return "OpenKMS"; + case 0x0008: return "LogoEmail"; + case 0x0009: return "Fidesmo"; + case 0x000A: return "VivoKey"; + case 0x000B: return "Feitian Technologies"; + case 0x000D: return "Dangerous Things"; + case 0x000E: return "Excelsecu"; + + case 0x002A: return "Magrathea"; + case 0x0042: return "GnuPG e.V."; + + case 0x1337: return "Warsaw Hackerspace"; + case 0x2342: return "warpzone"; /* hackerspace Muenster. */ + case 0x4354: return "Confidential Technologies"; /* cotech.de */ + case 0x5343: return "SSE Carte à puce"; + case 0x5443: return "TIF-IT e.V."; + case 0x63AF: return "Trustica"; + case 0xBA53: return "c-base e.V."; + case 0xBD0E: return "Paranoidlabs"; + case 0xF517: return "FSIJ"; + case 0xF5EC: return "F-Secure"; + + /* 0x0000 and 0xFFFF are defined as test cards per spec, + * 0xFF00 to 0xFFFE are assigned for use with randomly created + * serial numbers. */ + case 0x0000: + case 0xffff: return "test card"; + default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown"; + } +} + + + + +/* Deconstructor. */ +static void +do_deinit (app_t app) +{ + if (app && app->app_local) + { + struct cache_s *c, *c2; + int i; + + for (c = app->app_local->cache; c; c = c2) + { + c2 = c->next; + xfree (c); + } + + for (i=0; i < DIM (app->app_local->pk); i++) + { + xfree (app->app_local->pk[i].key); + app->app_local->pk[i].read_done = 0; + } + xfree (app->app_local); + app->app_local = NULL; + } +} + + +/* Wrapper around iso7816_get_data which first tries to get the data + from the cache. With GET_IMMEDIATE passed as true, the cache is + bypassed. With TRY_EXTLEN extended lengths APDUs are use if + supported by the card. */ +static gpg_error_t +get_cached_data (app_t app, int tag, + unsigned char **result, size_t *resultlen, + int get_immediate, int try_extlen) +{ + gpg_error_t err; + int i; + unsigned char *p; + size_t len; + struct cache_s *c; + int exmode; + + *result = NULL; + *resultlen = 0; + + if (!get_immediate) + { + for (c=app->app_local->cache; c; c = c->next) + if (c->tag == tag) + { + if(c->length) + { + p = xtrymalloc (c->length); + if (!p) + return gpg_error_from_syserror (); + memcpy (p, c->data, c->length); + *result = p; + } + + *resultlen = c->length; + + return 0; + } + } + + if (try_extlen && app->app_local->cardcap.ext_lc_le) + { + if (try_extlen == 1) + exmode = app->app_local->extcap.max_certlen_3; + else if (try_extlen == 2 && app->app_local->extcap.extcap_v3) + exmode = app->app_local->extcap.max_special_do; + else + exmode = 0; + } + else + exmode = 0; + + err = iso7816_get_data (app->slot, exmode, tag, &p, &len); + if (err) + return err; + if (len) + *result = p; + *resultlen = len; + + /* Check whether we should cache this object. */ + if (get_immediate) + return 0; + + for (i=0; data_objects[i].tag; i++) + if (data_objects[i].tag == tag) + { + if (data_objects[i].dont_cache) + return 0; + break; + } + + /* Okay, cache it. */ + for (c=app->app_local->cache; c; c = c->next) + assert (c->tag != tag); + + c = xtrymalloc (sizeof *c + len); + if (c) + { + if (len) + memcpy (c->data, p, len); + else + xfree (p); + c->length = len; + c->tag = tag; + c->next = app->app_local->cache; + app->app_local->cache = c; + } + + return 0; +} + +/* Remove DO at TAG from the cache. */ +static void +flush_cache_item (app_t app, int tag) +{ + struct cache_s *c, *cprev; + int i; + + if (!app->app_local) + return; + + for (c=app->app_local->cache, cprev=NULL; c ; cprev=c, c = c->next) + if (c->tag == tag) + { + if (cprev) + cprev->next = c->next; + else + app->app_local->cache = c->next; + xfree (c); + + for (c=app->app_local->cache; c ; c = c->next) + { + assert (c->tag != tag); /* Oops: duplicated entry. */ + } + return; + } + + /* Try again if we have an outer tag. */ + for (i=0; data_objects[i].tag; i++) + if (data_objects[i].tag == tag && data_objects[i].get_from + && data_objects[i].get_from != tag) + flush_cache_item (app, data_objects[i].get_from); +} + +/* Flush all entries from the cache which might be out of sync after + an error. */ +static void +flush_cache_after_error (app_t app) +{ + int i; + + for (i=0; data_objects[i].tag; i++) + if (data_objects[i].flush_on_error) + flush_cache_item (app, data_objects[i].tag); +} + + +/* Flush the entire cache. */ +static void +flush_cache (app_t app) +{ + if (app && app->app_local) + { + struct cache_s *c, *c2; + + for (c = app->app_local->cache; c; c = c2) + { + c2 = c->next; + xfree (c); + } + app->app_local->cache = NULL; + } +} + + +/* 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 (app_t app, int tag, unsigned char **result, size_t *nbytes, + int *r_rc) +{ + int rc, i; + unsigned char *buffer; + size_t buflen; + unsigned char *value; + size_t valuelen; + int dummyrc; + int exmode; + + if (!r_rc) + r_rc = &dummyrc; + + *result = NULL; + *nbytes = 0; + *r_rc = 0; + for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) + ; + + if (app->appversion > 0x0100 && data_objects[i].get_immediate_in_v11) + { + exmode = 0; + rc = iso7816_get_data (app->slot, exmode, tag, &buffer, &buflen); + if (rc) + { + *r_rc = rc; + return NULL; + } + *result = buffer; + *nbytes = buflen; + return buffer; + } + + value = NULL; + rc = -1; + if (data_objects[i].tag && data_objects[i].get_from) + { + rc = get_cached_data (app, data_objects[i].get_from, + &buffer, &buflen, + (data_objects[i].dont_cache + || data_objects[i].get_immediate_in_v11), + data_objects[i].try_extlen); + if (!rc) + { + const unsigned char *s; + + s = find_tlv_unchecked (buffer, buflen, tag, &valuelen); + 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 = get_cached_data (app, tag, &buffer, &buflen, + (data_objects[i].dont_cache + || data_objects[i].get_immediate_in_v11), + data_objects[i].try_extlen); + if (!rc) + { + value = buffer; + valuelen = buflen; + } + } + + if (!rc) + { + *nbytes = valuelen; + *result = value; + return buffer; + } + *r_rc = rc; + return NULL; +} + + +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; + + /* We don't try extended length APDU because such large DO would + be pretty useless in a log file. */ + rc = iso7816_get_data (slot, 0, data_objects[i].tag, &buffer, &buflen); + if (gpg_err_code (rc) == GPG_ERR_NO_OBJ) + ; + else if (rc) + log_info ("DO '%s' not available: %s\n", + data_objects[i].desc, gpg_strerror (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_unchecked (buffer, buflen, + data_objects[j].tag, &valuelen); + 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); + if (valuelen > 200) + log_info ("[%u]\n", (unsigned int)valuelen); + else + 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; +} + +/* GnuPG makes special use of the login-data DO, this function parses + the login data to store the flags for later use. It may be called + at any time and should be called after changing the login-data DO. + + Everything up to a LF is considered a mailbox or account name. If + the first LF is followed by DC4 (0x14) control sequence are + expected up to the next LF. Control sequences are separated by FS + (0x18) and consist of key=value pairs. There are two keys defined: + + F=<flags> + + Where FLAGS is a plain hexadecimal number representing flag values. + The lsb is here the rightmost bit. Defined flags bits are: + + Bit 0 = CHV1 and CHV2 are not synchronized + Bit 1 = CHV2 has been set to the default PIN of "123456" + (this implies that bit 0 is also set). + + P=<pinpad-request> + + Where PINPAD_REQUEST is in the format of: <n> or <n>,<m>. + N for user PIN, M for admin PIN. If M is missing it means M=N. + 0 means to force not to use pinpad. + +*/ +static void +parse_login_data (app_t app) +{ + unsigned char *buffer, *p; + size_t buflen, len; + void *relptr; + + /* Set defaults. */ + app->app_local->flags.no_sync = 0; + app->app_local->flags.def_chv2 = 0; + app->app_local->pinpad.specified = 0; + app->app_local->pinpad.fixedlen_user = -1; + app->app_local->pinpad.fixedlen_admin = -1; + + /* Read the DO. */ + relptr = get_one_do (app, 0x005E, &buffer, &buflen, NULL); + if (!relptr) + return; /* Ooops. */ + for (; buflen; buflen--, buffer++) + if (*buffer == '\n') + break; + if (buflen < 2 || buffer[1] != '\x14') + { + xfree (relptr); + return; /* No control sequences. */ + } + + buflen--; + buffer++; + do + { + buflen--; + buffer++; + if (buflen > 1 && *buffer == 'F' && buffer[1] == '=') + { + /* Flags control sequence found. */ + int lastdig = 0; + + /* For now we are only interested in the last digit, so skip + any leading digits but bail out on invalid characters. */ + for (p=buffer+2, len = buflen-2; len && hexdigitp (p); p++, len--) + lastdig = xtoi_1 (p); + buffer = p; + buflen = len; + if (len && !(*p == '\n' || *p == '\x18')) + goto next; /* Invalid characters in field. */ + app->app_local->flags.no_sync = !!(lastdig & 1); + app->app_local->flags.def_chv2 = (lastdig & 3) == 3; + } + else if (buflen > 1 && *buffer == 'P' && buffer[1] == '=') + { + /* Pinpad request control sequence found. */ + buffer += 2; + buflen -= 2; + + if (buflen) + { + if (digitp (buffer)) + { + char *q; + int n, m; + + n = strtol (buffer, &q, 10); + if (q >= (char *)buffer + buflen + || *q == '\x18' || *q == '\n') + m = n; + else + { + if (*q++ != ',' || !digitp (q)) + goto next; + m = strtol (q, &q, 10); + } + + if (buflen < ((unsigned char *)q - buffer)) + break; + + buflen -= ((unsigned char *)q - buffer); + buffer = q; + + if (buflen && !(*buffer == '\n' || *buffer == '\x18')) + goto next; + app->app_local->pinpad.specified = 1; + app->app_local->pinpad.fixedlen_user = n; + app->app_local->pinpad.fixedlen_admin = m; + } + } + } + next: + /* Skip to FS (0x18) or LF (\n). */ + for (; buflen && *buffer != '\x18' && *buffer != '\n'; buflen--) + buffer++; + } + while (buflen && *buffer != '\n'); + + xfree (relptr); +} + + +#define MAX_ARGS_STORE_FPR 3 + +/* Note, that FPR must be at least 20 bytes. */ +static gpg_error_t +store_fpr (app_t app, int keynumber, u32 timestamp, unsigned char *fpr, + int algo, ...) +{ + unsigned int n, nbits; + unsigned char *buffer, *p; + int tag, tag2; + int rc; + const unsigned char *m[MAX_ARGS_STORE_FPR]; + size_t mlen[MAX_ARGS_STORE_FPR]; + va_list ap; + int argc; + int i; + + n = 6; /* key packet version, 4-byte timestamps, and algorithm */ + if (algo == PUBKEY_ALGO_ECDH) + argc = 3; + else + argc = 2; + + va_start (ap, algo); + for (i = 0; i < argc; i++) + { + m[i] = va_arg (ap, const unsigned char *); + mlen[i] = va_arg (ap, size_t); + if (algo == PUBKEY_ALGO_RSA || i == 1) + n += 2; + n += mlen[i]; + } + va_end (ap); + + p = buffer = xtrymalloc (3 + n); + if (!buffer) + return gpg_error_from_syserror (); + + *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++ = algo; + + for (i = 0; i < argc; i++) + { + if (algo == PUBKEY_ALGO_RSA || i == 1) + { + nbits = count_bits (m[i], mlen[i]); + *p++ = nbits >> 8; + *p++ = nbits; + } + memcpy (p, m[i], mlen[i]); + p += mlen[i]; + } + + gcry_md_hash_buffer (GCRY_MD_SHA1, fpr, buffer, n+3); + + xfree (buffer); + + tag = (app->appversion > 0x0007? 0xC7 : 0xC6) + keynumber; + flush_cache_item (app, 0xC5); + tag2 = 0xCE + keynumber; + flush_cache_item (app, 0xCD); + + rc = iso7816_put_data (app->slot, 0, tag, fpr, 20); + if (rc) + log_error (_("failed to store the fingerprint: %s\n"),gpg_strerror (rc)); + + if (!rc && app->appversion > 0x0100) + { + unsigned char buf[4]; + + buf[0] = timestamp >> 24; + buf[1] = timestamp >> 16; + buf[2] = timestamp >> 8; + buf[3] = timestamp; + + rc = iso7816_put_data (app->slot, 0, tag2, buf, 4); + if (rc) + log_error (_("failed to store the creation date: %s\n"), + gpg_strerror (rc)); + } + + return rc; +} + + +static void +send_fpr_if_not_null (ctrl_t 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. */ + bin2hex (fpr, 20, buf); + 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_fprtime_if_not_null (ctrl_t ctrl, const char *keyword, + int number, const unsigned char *stamp) +{ + char numbuf1[50], numbuf2[50]; + unsigned long value; + + value = buf32_to_ulong (stamp); + if (!value) + return; + sprintf (numbuf1, "%d", number); + sprintf (numbuf2, "%lu", value); + send_status_info (ctrl, keyword, + numbuf1, (size_t)strlen(numbuf1), + numbuf2, (size_t)strlen(numbuf2), NULL, 0); +} + +static void +send_key_data (ctrl_t ctrl, const char *name, + const unsigned char *a, size_t alen) +{ + char *buffer, *buf; + size_t buflen; + + buffer = buf = bin2hex (a, alen, NULL); + if (!buffer) + { + log_error ("memory allocation error in send_key_data\n"); + return; + } + buflen = strlen (buffer); + + /* 768 is the hexified size for the modulus of an 3072 bit key. We + use extra chunks to transmit larger data (i.e for 4096 bit). */ + for ( ;buflen > 768; buflen -= 768, buf += 768) + send_status_info (ctrl, "KEY-DATA", + "-", 1, + buf, 768, + NULL, 0); + send_status_info (ctrl, "KEY-DATA", + name, (size_t)strlen(name), + buf, buflen, + NULL, 0); + xfree (buffer); +} + + +static void +send_key_attr (ctrl_t ctrl, app_t app, const char *keyword, int keyno) +{ + char buffer[200]; + + assert (keyno >=0 && keyno < DIM(app->app_local->keyattr)); + + if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA) + snprintf (buffer, sizeof buffer, "%d 1 rsa%u %u %d", + keyno+1, + app->app_local->keyattr[keyno].rsa.n_bits, + app->app_local->keyattr[keyno].rsa.e_bits, + app->app_local->keyattr[keyno].rsa.format); + else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC) + { + snprintf (buffer, sizeof buffer, "%d %d %s", + keyno+1, + keyno==1? PUBKEY_ALGO_ECDH : + (app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)? + PUBKEY_ALGO_EDDSA : PUBKEY_ALGO_ECDSA, + app->app_local->keyattr[keyno].ecc.curve); + } + else + snprintf (buffer, sizeof buffer, "%d 0 0 UNKNOWN", keyno+1); + + send_status_direct (ctrl, keyword, buffer); +} + + +#define RSA_SMALL_SIZE_KEY 1952 +#define RSA_SMALL_SIZE_OP 2048 + +static int +determine_rsa_response (app_t app, int keyno) +{ + int size; + + size = 2 + 3 /* header */ + + 4 /* tag+len */ + (app->app_local->keyattr[keyno].rsa.n_bits+7)/8 + + 2 /* tag+len */ + (app->app_local->keyattr[keyno].rsa.e_bits+7)/8; + + return size; +} + + +/* Implement the GETATTR command. This is similar to the LEARN + command but returns just one value via the status interface. */ +static gpg_error_t +do_getattr (app_t app, ctrl_t ctrl, const char *name) +{ + static struct { + const char *name; + int tag; + int special; + } table[] = { + { "DISP-NAME", 0x005B }, + { "LOGIN-DATA", 0x005E }, + { "DISP-LANG", 0x5F2D }, + { "DISP-SEX", 0x5F35 }, + { "PUBKEY-URL", 0x5F50 }, + { "KEY-FPR", 0x00C5, 3 }, + { "KEY-TIME", 0x00CD, 4 }, + { "KEY-ATTR", 0x0000, -5 }, + { "CA-FPR", 0x00C6, 3 }, + { "CHV-STATUS", 0x00C4, 1 }, + { "SIG-COUNTER", 0x0093, 2 }, + { "SERIALNO", 0x004F, -1 }, + { "AID", 0x004F }, + { "EXTCAP", 0x0000, -2 }, + { "PRIVATE-DO-1", 0x0101 }, + { "PRIVATE-DO-2", 0x0102 }, + { "PRIVATE-DO-3", 0x0103 }, + { "PRIVATE-DO-4", 0x0104 }, + { "$AUTHKEYID", 0x0000, -3 }, + { "$ENCRKEYID", 0x0000, -6 }, + { "$SIGNKEYID", 0x0000, -7 }, + { "$DISPSERIALNO",0x0000, -4 }, + { "KDF", 0x00F9, 5 }, + { "MANUFACTURER", 0x0000, -8 }, + { NULL, 0 } + }; + int idx, i, rc; + void *relptr; + unsigned char *value; + size_t valuelen; + + for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++) + ; + if (!table[idx].name) + return gpg_error (GPG_ERR_INV_NAME); + + if (table[idx].special == -1) + { + /* The serial number is very special. We could have used the + AID DO to retrieve it. The AID DO is available anyway but + not hex formatted. */ + char *serial = app_get_serialno (app); + + if (serial) + { + send_status_direct (ctrl, "SERIALNO", serial); + xfree (serial); + } + return 0; + } + if (table[idx].special == -2) + { + char tmp[110]; + + snprintf (tmp, sizeof tmp, + "gc=%d ki=%d fc=%d pd=%d mcl3=%u aac=%d " + "sm=%d si=%u dec=%d bt=%d kdf=%d", + app->app_local->extcap.get_challenge, + app->app_local->extcap.key_import, + app->app_local->extcap.change_force_chv, + app->app_local->extcap.private_dos, + app->app_local->extcap.max_certlen_3, + app->app_local->extcap.algo_attr_change, + (app->app_local->extcap.sm_supported + ? (app->app_local->extcap.sm_algo == 0? CIPHER_ALGO_3DES : + (app->app_local->extcap.sm_algo == 1? + CIPHER_ALGO_AES : CIPHER_ALGO_AES256)) + : 0), + app->app_local->status_indicator, + app->app_local->extcap.has_decrypt, + app->app_local->extcap.has_button, + app->app_local->extcap.kdf_do); + send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); + return 0; + } + if (table[idx].special == -3) + { + char const tmp[] = "OPENPGP.3"; + send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); + return 0; + } + if (table[idx].special == -4) + { + char *serial = app_get_dispserialno (app, 0); + + if (serial) + { + send_status_info (ctrl, table[idx].name, + serial, strlen (serial), NULL, 0); + xfree (serial); + return 0; + } + return gpg_error (GPG_ERR_INV_NAME); + } + if (table[idx].special == -5) + { + for (i=0; i < 3; i++) + send_key_attr (ctrl, app, table[idx].name, i); + return 0; + } + if (table[idx].special == -6) + { + char const tmp[] = "OPENPGP.2"; + send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); + return 0; + } + if (table[idx].special == -7) + { + char const tmp[] = "OPENPGP.1"; + send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); + return 0; + } + if (table[idx].special == -8) + { + return send_status_printf + (ctrl, table[idx].name, "%u %s", + app->app_local->manufacturer, + get_manufacturer (app->app_local->manufacturer)); + } + + relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &rc); + if (relptr) + { + if (table[idx].special == 1) + { + 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, table[idx].name, + numbuf, strlen (numbuf), NULL, 0); + } + else if (table[idx].special == 2) + { + char numbuf[50]; + + sprintf (numbuf, "%lu", convert_sig_counter_value (value, valuelen)); + send_status_info (ctrl, table[idx].name, + numbuf, strlen (numbuf), NULL, 0); + } + else if (table[idx].special == 3) + { + if (valuelen >= 60) + for (i=0; i < 3; i++) + send_fpr_if_not_null (ctrl, table[idx].name, i+1, value+i*20); + } + else if (table[idx].special == 4) + { + if (valuelen >= 12) + for (i=0; i < 3; i++) + send_fprtime_if_not_null (ctrl, table[idx].name, i+1, value+i*4); + } + else if (table[idx].special == 5) + { + if ((valuelen == KDF_DATA_LENGTH_MIN + || valuelen == KDF_DATA_LENGTH_MAX) + && (value[2] == 0x03)) + app->app_local->pinpad.disabled = 1; + else + app->app_local->pinpad.disabled = 0; + + send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0); + } + else + send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0); + + xfree (relptr); + } + else + { + if (table[idx].special == 5) + app->app_local->pinpad.disabled = 0; + } + return rc; +} + + +/* Return the DISP-NAME without any padding characters. Caller must + * free the result. If not found or empty NULL is returned. */ +static char * +get_disp_name (app_t app) +{ + int rc; + void *relptr; + unsigned char *value; + size_t valuelen; + char *string; + char *p, *given; + char *result; + + relptr = get_one_do (app, 0x005B, &value, &valuelen, &rc); + if (!relptr) + return NULL; + + string = xtrymalloc (valuelen + 1); + if (!string) + { + xfree (relptr); + return NULL; + } + memcpy (string, value, valuelen); + string[valuelen] = 0; + xfree (relptr); + + /* Swap surname and given name. */ + given = strstr (string, "<<"); + for (p = string; *p; p++) + if (*p == '<') + *p = ' '; + + if (given && given[2]) + { + *given = 0; + given += 2; + result = strconcat (given, " ", string, NULL); + } + else + { + result = string; + string = NULL; + } + + xfree (string); + return result; +} + + +/* Return the pretty formatted serialnumber. On error NULL is + * returned. */ +static char * +get_disp_serialno (app_t app) +{ + char *serial = app_get_serialno (app); + + /* For our OpenPGP cards we do not want to show the entire serial + * number but a nicely reformatted actual serial number. */ + if (serial && strlen (serial) > 16+12) + { + memmove (serial, serial+16, 4); + serial[4] = ' '; + /* memmove (serial+5, serial+20, 4); */ + /* serial[9] = ' '; */ + /* memmove (serial+10, serial+24, 4); */ + /* serial[14] = 0; */ + memmove (serial+5, serial+20, 8); + serial[13] = 0; + } + return serial; +} + + +/* Return the number of remaining tries for the standard or the admin + * pw. Returns -1 on card error. */ +static int +get_remaining_tries (app_t app, int adminpw) +{ + void *relptr; + unsigned char *value; + size_t valuelen; + int remaining; + + relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL); + if (!relptr || valuelen < 7) + { + log_error (_("error retrieving CHV status from card\n")); + xfree (relptr); + return -1; + } + remaining = value[adminpw? 6 : 4]; + xfree (relptr); + return remaining; +} + + +/* Retrieve the fingerprint from the card inserted in SLOT and write + the according hex representation to FPR. Caller must have provide + a buffer at FPR of least 41 bytes. Returns 0 on success or an + error code. */ +static gpg_error_t +retrieve_fpr_from_card (app_t app, int keyno, char *fpr) +{ + gpg_error_t err = 0; + void *relptr; + unsigned char *value; + size_t valuelen; + + assert (keyno >=0 && keyno <= 2); + + relptr = get_one_do (app, 0x00C5, &value, &valuelen, NULL); + if (relptr && valuelen >= 60) + bin2hex (value+keyno*20, 20, fpr); + else + err = gpg_error (GPG_ERR_NOT_FOUND); + xfree (relptr); + return err; +} + + +/* Retrieve the public key material for the RSA key, whose fingerprint + is FPR, from gpg output, which can be read through the stream FP. + The RSA modulus will be stored at the address of M and MLEN, the + public exponent at E and ELEN. Returns zero on success, an error + code on failure. Caller must release the allocated buffers at M + and E if the function returns success. */ +static gpg_error_t +retrieve_key_material (FILE *fp, const char *hexkeyid, + const unsigned char **m, size_t *mlen, + const unsigned char **e, size_t *elen) +{ + gcry_error_t err = 0; + char *line = NULL; /* read_line() buffer. */ + size_t line_size = 0; /* Helper for for read_line. */ + int found_key = 0; /* Helper to find a matching key. */ + unsigned char *m_new = NULL; + unsigned char *e_new = NULL; + size_t m_new_n = 0; + size_t e_new_n = 0; + + /* Loop over all records until we have found the subkey + corresponding to the fingerprint. Inm general the first record + should be the pub record, but we don't rely on that. Given that + we only need to look at one key, it is sufficient to compare the + keyid so that we don't need to look at "fpr" records. */ + for (;;) + { + char *p; + char *fields[6] = { NULL, NULL, NULL, NULL, NULL, NULL }; + int nfields; + size_t max_length; + gcry_mpi_t mpi; + int i; + + max_length = 4096; + i = read_line (fp, &line, &line_size, &max_length); + if (!i) + break; /* EOF. */ + if (i < 0) + { + err = gpg_error_from_syserror (); + goto leave; /* Error. */ + } + if (!max_length) + { + err = gpg_error (GPG_ERR_TRUNCATED); + goto leave; /* Line truncated - we better stop processing. */ + } + + /* Parse the line into fields. */ + for (nfields=0, p=line; p && nfields < DIM (fields); nfields++) + { + fields[nfields] = p; + p = strchr (p, ':'); + if (p) + *(p++) = 0; + } + if (!nfields) + continue; /* No fields at all - skip line. */ + + if (!found_key) + { + if ( (!strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") ) + && nfields > 4 && !strcmp (fields[4], hexkeyid)) + found_key = 1; + continue; + } + + if ( !strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") ) + break; /* Next key - stop. */ + + if ( strcmp (fields[0], "pkd") ) + continue; /* Not a key data record. */ + if ( nfields < 4 || (i = atoi (fields[1])) < 0 || i > 1 + || (!i && m_new) || (i && e_new)) + { + err = gpg_error (GPG_ERR_GENERAL); + goto leave; /* Error: Invalid key data record or not an RSA key. */ + } + + err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_HEX, fields[3], 0, NULL); + if (err) + mpi = NULL; + else if (!i) + err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &m_new, &m_new_n, mpi); + else + err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &e_new, &e_new_n, mpi); + gcry_mpi_release (mpi); + if (err) + goto leave; + } + + if (m_new && e_new) + { + *m = m_new; + *mlen = m_new_n; + m_new = NULL; + *e = e_new; + *elen = e_new_n; + e_new = NULL; + } + else + err = gpg_error (GPG_ERR_GENERAL); + + leave: + xfree (m_new); + xfree (e_new); + xfree (line); + return err; +} + + +static gpg_error_t +rsa_read_pubkey (app_t app, ctrl_t ctrl, u32 created_at, int keyno, + const unsigned char *data, size_t datalen, gcry_sexp_t *r_sexp) +{ + gpg_error_t err; + const unsigned char *m, *e; + size_t mlen, elen; + unsigned char *mbuf = NULL, *ebuf = NULL; + + m = find_tlv (data, datalen, 0x0081, &mlen); + if (!m) + { + log_error (_("response does not contain the RSA modulus\n")); + return gpg_error (GPG_ERR_CARD); + } + + e = find_tlv (data, datalen, 0x0082, &elen); + if (!e) + { + log_error (_("response does not contain the RSA public exponent\n")); + return gpg_error (GPG_ERR_CARD); + } + + if (ctrl) + { + send_key_data (ctrl, "n", m, mlen); + send_key_data (ctrl, "e", e, elen); + } + + for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */ + ; + for (; elen && !*e; elen--, e++) /* strip leading zeroes */ + ; + + if (ctrl) + { + unsigned char fprbuf[20]; + + err = store_fpr (app, keyno, created_at, fprbuf, PUBKEY_ALGO_RSA, + m, mlen, e, elen); + if (err) + return err; + + send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf); + } + + mbuf = xtrymalloc (mlen + 1); + if (!mbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + /* Prepend numbers with a 0 if needed. */ + if (mlen && (*m & 0x80)) + { + *mbuf = 0; + memcpy (mbuf+1, m, mlen); + mlen++; + } + else + memcpy (mbuf, m, mlen); + + ebuf = xtrymalloc (elen + 1); + if (!ebuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + /* Prepend numbers with a 0 if needed. */ + if (elen && (*e & 0x80)) + { + *ebuf = 0; + memcpy (ebuf+1, e, elen); + elen++; + } + else + memcpy (ebuf, e, elen); + + err = gcry_sexp_build (r_sexp, NULL, "(public-key(rsa(n%b)(e%b)))", + (int)mlen, mbuf, (int)elen, ebuf); + leave: + xfree (mbuf); + xfree (ebuf); + return err; +} + + +/* Determine KDF hash algorithm and KEK encryption algorithm by CURVE. */ +static const unsigned char* +ecdh_params (const char *curve) +{ + unsigned int nbits; + + openpgp_curve_to_oid (curve, &nbits, NULL); + + /* See RFC-6637 for those constants. + 0x03: Number of bytes + 0x01: Version for this parameter format + KDF hash algo + KEK symmetric cipher algo + */ + if (nbits <= 256) + return (const unsigned char*)"\x03\x01\x08\x07"; + else if (nbits <= 384) + return (const unsigned char*)"\x03\x01\x09\x08"; + else + return (const unsigned char*)"\x03\x01\x0a\x09"; +} + +static gpg_error_t +ecc_read_pubkey (app_t app, ctrl_t ctrl, u32 created_at, int keyno, + const unsigned char *data, size_t datalen, gcry_sexp_t *r_sexp) +{ + gpg_error_t err; + unsigned char *qbuf = NULL; + const unsigned char *ecc_q; + size_t ecc_q_len; + gcry_mpi_t oid = NULL; + int n; + const char *curve; + const char *oidstr; + const unsigned char *oidbuf; + size_t oid_len; + int algo; + const char *format; + + ecc_q = find_tlv (data, datalen, 0x0086, &ecc_q_len); + if (!ecc_q) + { + log_error (_("response does not contain the EC public key\n")); + return gpg_error (GPG_ERR_CARD); + } + + curve = app->app_local->keyattr[keyno].ecc.curve; + oidstr = openpgp_curve_to_oid (curve, NULL, NULL); + err = openpgp_oid_from_str (oidstr, &oid); + if (err) + return err; + oidbuf = gcry_mpi_get_opaque (oid, &n); + if (!oidbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + oid_len = (n+7)/8; + + qbuf = xtrymalloc (ecc_q_len + 1); + if (!qbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if ((app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)) + { /* Prepend 0x40 prefix. */ + *qbuf = 0x40; + memcpy (qbuf+1, ecc_q, ecc_q_len); + ecc_q_len++; + } + else + memcpy (qbuf, ecc_q, ecc_q_len); + + if (ctrl) + { + send_key_data (ctrl, "q", qbuf, ecc_q_len); + send_key_data (ctrl, "curve", oidbuf, oid_len); + } + + if (keyno == 1) + { + if (ctrl) + send_key_data (ctrl, "kdf/kek", ecdh_params (curve), (size_t)4); + algo = PUBKEY_ALGO_ECDH; + } + else + { + if ((app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)) + algo = PUBKEY_ALGO_EDDSA; + else + algo = PUBKEY_ALGO_ECDSA; + } + + if (ctrl) + { + unsigned char fprbuf[20]; + + err = store_fpr (app, keyno, created_at, fprbuf, algo, oidbuf, oid_len, + qbuf, ecc_q_len, ecdh_params (curve), (size_t)4); + if (err) + goto leave; + + send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf); + } + + if (!(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)) + format = "(public-key(ecc(curve%s)(q%b)))"; + else if (keyno == 1) + format = "(public-key(ecc(curve%s)(flags djb-tweak)(q%b)))"; + else + format = "(public-key(ecc(curve%s)(flags eddsa)(q%b)))"; + + err = gcry_sexp_build (r_sexp, NULL, format, + app->app_local->keyattr[keyno].ecc.curve, + (int)ecc_q_len, qbuf); + leave: + gcry_mpi_release (oid); + xfree (qbuf); + return err; +} + + +/* Compute the keygrip form the local info and store it there. */ +static gpg_error_t +store_keygrip (app_t app, int keyno) +{ + gpg_error_t err; + unsigned char grip[20]; + + err = keygrip_from_canon_sexp (app->app_local->pk[keyno].key, + app->app_local->pk[keyno].keylen, + grip); + if (err) + return err; + + bin2hex (grip, 20, app->app_local->pk[keyno].keygrip_str); + return 0; +} + + +/* Parse tag-length-value data for public key in BUFFER of BUFLEN + length. Key of KEYNO in APP is updated with an S-expression of + public key. When CTRL is not NULL, fingerprint is computed with + CREATED_AT, and fingerprint is written to the card, and key data + and fingerprint are send back to the client side. + */ +static gpg_error_t +read_public_key (app_t app, ctrl_t ctrl, u32 created_at, int keyno, + const unsigned char *buffer, size_t buflen) +{ + gpg_error_t err; + const unsigned char *data; + size_t datalen; + gcry_sexp_t s_pkey = NULL; + + data = find_tlv (buffer, buflen, 0x7F49, &datalen); + if (!data) + { + log_error (_("response does not contain the public key data\n")); + return gpg_error (GPG_ERR_CARD); + } + + if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA) + err = rsa_read_pubkey (app, ctrl, created_at, keyno, + data, datalen, &s_pkey); + else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC) + err = ecc_read_pubkey (app, ctrl, created_at, keyno, + data, datalen, &s_pkey); + else + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + if (!err) + { + unsigned char *keybuf; + size_t len; + + len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); + keybuf = xtrymalloc (len); + if (!data) + { + err = gpg_error_from_syserror (); + gcry_sexp_release (s_pkey); + return err; + } + + gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len); + gcry_sexp_release (s_pkey); + + app->app_local->pk[keyno].key = keybuf; + /* Decrement for trailing '\0' */ + app->app_local->pk[keyno].keylen = len - 1; + + err = store_keygrip (app, keyno); + } + + return err; +} + + +/* Get the public key for KEYNO and store it as an S-expression with + the APP handle. On error that field gets cleared. If we already + know about the public key we will just return. Note that this does + not mean a key is available; this is solely indicated by the + presence of the app->app_local->pk[KEYNO].key field. + + Note that GnuPG 1.x does not need this and it would be too time + consuming to send it just for the fun of it. However, given that we + use the same code in gpg 1.4, we can't use the gcry S-expression + here but need to open encode it. */ +static gpg_error_t +get_public_key (app_t app, int keyno) +{ + gpg_error_t err = 0; + unsigned char *buffer; + const unsigned char *m, *e; + size_t buflen; + size_t mlen = 0; + size_t elen = 0; + char *keybuf = NULL; + gcry_sexp_t s_pkey; + size_t len; + + if (keyno < 0 || keyno > 2) + return gpg_error (GPG_ERR_INV_ID); + + /* Already cached? */ + if (app->app_local->pk[keyno].read_done) + return 0; + + xfree (app->app_local->pk[keyno].key); + app->app_local->pk[keyno].key = NULL; + app->app_local->pk[keyno].keylen = 0; + + m = e = NULL; /* (avoid cc warning) */ + + if (app->appversion > 0x0100) + { + int exmode, le_value; + + /* We may simply read the public key out of these cards. */ + if (app->app_local->cardcap.ext_lc_le + && app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA + && app->app_local->keyattr[keyno].rsa.n_bits > RSA_SMALL_SIZE_KEY) + { + exmode = 1; /* Use extended length. */ + le_value = determine_rsa_response (app, keyno); + } + else + { + exmode = 0; + le_value = 256; /* Use legacy value. */ + } + + err = iso7816_read_public_key (app->slot, exmode, + (keyno == 0? "\xB6" : + keyno == 1? "\xB8" : "\xA4"), + 2, le_value, &buffer, &buflen); + if (err) + { + /* Yubikey returns wrong code. Fix it up. */ + /* + * NOTE: It's not correct to blindly change the error code, + * however, for our experiences, it is only Yubikey... + */ + err = gpg_error (GPG_ERR_NO_OBJ); + log_error (_("reading public key failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + err = read_public_key (app, NULL, 0U, keyno, buffer, buflen); + } + else + { + /* Due to a design problem in v1.0 cards we can't get the public + key out of these cards without doing a verify on CHV3. + Clearly that is not an option and thus we try to locate the + key using an external helper. + + The helper we use here is gpg itself, which should know about + the key in any case. */ + + char fpr[41]; + char *hexkeyid; + char *command = NULL; + FILE *fp; + int ret; + + buffer = NULL; /* We don't need buffer. */ + + err = retrieve_fpr_from_card (app, keyno, fpr); + if (err) + { + log_error ("error while retrieving fpr from card: %s\n", + gpg_strerror (err)); + goto leave; + } + hexkeyid = fpr + 24; + + ret = gpgrt_asprintf + (&command, "gpg --list-keys --with-colons --with-key-data '%s'", fpr); + if (ret < 0) + { + err = gpg_error_from_syserror (); + goto leave; + } + + fp = popen (command, "r"); + xfree (command); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("running gpg failed: %s\n", gpg_strerror (err)); + goto leave; + } + + err = retrieve_key_material (fp, hexkeyid, &m, &mlen, &e, &elen); + pclose (fp); + if (err) + { + log_error ("error while retrieving key material through pipe: %s\n", + gpg_strerror (err)); + goto leave; + } + + err = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%b)(e%b)))", + (int)mlen, m, (int)elen, e); + if (err) + goto leave; + + len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); + + keybuf = xtrymalloc (len); + if (!keybuf) + { + err = gpg_error_from_syserror (); + gcry_sexp_release (s_pkey); + goto leave; + } + + gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len); + gcry_sexp_release (s_pkey); + + app->app_local->pk[keyno].key = (unsigned char*)keybuf; + /* Decrement for trailing '\0' */ + app->app_local->pk[keyno].keylen = len - 1; + + err = store_keygrip (app, keyno); + } + + leave: + /* Set a flag to indicate that we tried to read the key. */ + if (!err) + app->app_local->pk[keyno].read_done = 1; + + xfree (buffer); + return err; +} + + +/* Send the KEYPAIRINFO back. KEY needs to be in the range [1,3]. + This is used by the LEARN command. */ +static gpg_error_t +send_keypair_info (app_t app, ctrl_t ctrl, int key) +{ + int keyno = key - 1; + gpg_error_t err = 0; + char idbuf[50]; + const char *usage; + + err = get_public_key (app, keyno); + if (err) + goto leave; + + assert (keyno >= 0 && keyno <= 2); + if (!app->app_local->pk[keyno].key) + goto leave; /* No such key - ignore. */ + + switch (keyno) + { + case 0: usage = "sc"; break; + case 1: usage = "e"; break; + case 2: usage = "sa"; break; + default: usage = ""; break; + } + + sprintf (idbuf, "OPENPGP.%d", keyno+1); + send_status_info (ctrl, "KEYPAIRINFO", + app->app_local->pk[keyno].keygrip_str, 40, + idbuf, strlen (idbuf), + usage, strlen (usage), + NULL, (size_t)0); + + leave: + return err; +} + + +/* Handle the LEARN command for OpenPGP. */ +static gpg_error_t +do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) +{ + gpg_error_t err = 0; + + (void)flags; + + err = do_getattr (app, ctrl, "EXTCAP"); + if (!err) + err = do_getattr (app, ctrl, "MANUFACTURER"); + if (!err) + err = do_getattr (app, ctrl, "DISP-NAME"); + if (!err) + err = do_getattr (app, ctrl, "DISP-LANG"); + if (!err) + err = do_getattr (app, ctrl, "DISP-SEX"); + if (!err) + err = do_getattr (app, ctrl, "PUBKEY-URL"); + if (!err) + err = do_getattr (app, ctrl, "LOGIN-DATA"); + if (!err) + err = do_getattr (app, ctrl, "KEY-FPR"); + if (!err && app->appversion > 0x0100) + err = do_getattr (app, ctrl, "KEY-TIME"); + if (!err) + err = do_getattr (app, ctrl, "CA-FPR"); + if (!err) + err = do_getattr (app, ctrl, "CHV-STATUS"); + if (!err) + err = do_getattr (app, ctrl, "SIG-COUNTER"); + if (!err && app->app_local->extcap.kdf_do) + { + err = do_getattr (app, ctrl, "KDF"); + if (gpg_err_code (err) == GPG_ERR_NO_OBJ) + err = 0; + } + if (!err && app->app_local->extcap.private_dos) + { + if (!err) + err = do_getattr (app, ctrl, "PRIVATE-DO-1"); + if (gpg_err_code (err) == GPG_ERR_NO_OBJ) + err = 0; + if (!err) + err = do_getattr (app, ctrl, "PRIVATE-DO-2"); + if (gpg_err_code (err) == GPG_ERR_NO_OBJ) + err = 0; + if (!err && app->did_chv2) + err = do_getattr (app, ctrl, "PRIVATE-DO-3"); + if (gpg_err_code (err) == GPG_ERR_NO_OBJ) + err = 0; + if (!err && app->did_chv3) + err = do_getattr (app, ctrl, "PRIVATE-DO-4"); + if (gpg_err_code (err) == GPG_ERR_NO_OBJ) + err = 0; + } + if (!err) + err = send_keypair_info (app, ctrl, 1); + if (gpg_err_code (err) == GPG_ERR_NO_OBJ) + err = 0; + if (!err) + err = send_keypair_info (app, ctrl, 2); + if (gpg_err_code (err) == GPG_ERR_NO_OBJ) + err = 0; + if (!err) + err = send_keypair_info (app, ctrl, 3); + if (gpg_err_code (err) == GPG_ERR_NO_OBJ) + err = 0; + /* Note: We do not send the Cardholder Certificate, because that is + relatively long and for OpenPGP applications not really needed. */ + return err; +} + + +/* Handle the READKEY command for OpenPGP. On success a canonical + encoded S-expression with the public key will get stored at PK and + its length (for assertions) at PKLEN; the caller must release that + buffer. On error PK and PKLEN are not changed and an error code is + returned. */ +static gpg_error_t +do_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned int flags, + unsigned char **pk, size_t *pklen) +{ + gpg_error_t err; + int keyno; + unsigned char *buf; + + (void)ctrl; + + if (!strcmp (keyid, "OPENPGP.1")) + keyno = 0; + else if (!strcmp (keyid, "OPENPGP.2")) + keyno = 1; + else if (!strcmp (keyid, "OPENPGP.3")) + keyno = 2; + else + return gpg_error (GPG_ERR_INV_ID); + + err = get_public_key (app, keyno); + if (err) + return err; + + buf = app->app_local->pk[keyno].key; + if (!buf) + return gpg_error (GPG_ERR_NO_PUBKEY); + + if ((flags & APP_READKEY_FLAG_ADVANCED)) + { + gcry_sexp_t s_key; + + err = gcry_sexp_new (&s_key, buf, app->app_local->pk[keyno].keylen, 0); + if (err) + return err; + + *pklen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0); + *pk = xtrymalloc (*pklen); + if (!*pk) + { + err = gpg_error_from_syserror (); + *pklen = 0; + return err; + } + + gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, *pk, *pklen); + gcry_sexp_release (s_key); + /* Decrement for trailing '\0' */ + *pklen = *pklen - 1; + } + else + { + *pklen = app->app_local->pk[keyno].keylen; + *pk = xtrymalloc (*pklen); + if (!*pk) + { + err = gpg_error_from_syserror (); + *pklen = 0; + return err; + } + memcpy (*pk, buf, *pklen); + } + + return 0; +} + +/* Read the standard certificate of an OpenPGP v2 card. It is + returned in a freshly allocated buffer with that address stored at + CERT and the length of the certificate stored at CERTLEN. CERTID + needs to be set to "OPENPGP.3". */ +static gpg_error_t +do_readcert (app_t app, const char *certid, + unsigned char **cert, size_t *certlen) +{ + gpg_error_t err; + unsigned char *buffer; + size_t buflen; + void *relptr; + + *cert = NULL; + *certlen = 0; + if (strcmp (certid, "OPENPGP.3")) + return gpg_error (GPG_ERR_INV_ID); + if (!app->app_local->extcap.is_v2) + return gpg_error (GPG_ERR_NOT_FOUND); + + relptr = get_one_do (app, 0x7F21, &buffer, &buflen, NULL); + if (!relptr) + return gpg_error (GPG_ERR_NOT_FOUND); + + if (!buflen) + err = gpg_error (GPG_ERR_NOT_FOUND); + else if (!(*cert = xtrymalloc (buflen))) + err = gpg_error_from_syserror (); + else + { + memcpy (*cert, buffer, buflen); + *certlen = buflen; + err = 0; + } + xfree (relptr); + return err; +} + + +/* Decide if we use the pinpad of the reader for PIN input according + to the user preference on the card, and the capability of the + reader. This routine is only called when the reader has pinpad. + Returns 0 if we use pinpad, 1 otherwise. */ +static int +check_pinpad_request (app_t app, pininfo_t *pininfo, int admin_pin) +{ + if (app->app_local->pinpad.disabled) + return 1; + + if (app->app_local->pinpad.specified == 0) /* No preference on card. */ + { + if (pininfo->fixedlen == 0) /* Reader has varlen capability. */ + return 0; /* Then, use pinpad. */ + else + /* + * Reader has limited capability, and it may not match PIN of + * the card. + */ + return 1; + } + + if (admin_pin) + pininfo->fixedlen = app->app_local->pinpad.fixedlen_admin; + else + pininfo->fixedlen = app->app_local->pinpad.fixedlen_user; + + if (pininfo->fixedlen == 0 /* User requests disable pinpad. */ + || pininfo->fixedlen < pininfo->minlen + || pininfo->fixedlen > pininfo->maxlen + /* Reader doesn't have the capability to input a PIN which + * length is FIXEDLEN. */) + return 1; + + return 0; +} + + +/* Return a string with information about the card for use in a + * prompt. Returns NULL on memory failure. */ +static char * +get_prompt_info (app_t app, int chvno, unsigned long sigcount, int remaining) +{ + char *serial, *disp_name, *rembuf, *tmpbuf, *result; + + serial = get_disp_serialno (app); + if (!serial) + return NULL; + + disp_name = get_disp_name (app); + if (chvno == 1) + { + /* TRANSLATORS: Put a \x1f right before a colon. This can be + * used by pinentry to nicely align the names and values. Keep + * the %s at the start and end of the string. */ + result = xtryasprintf (_("%s" + "Number\x1f: %s%%0A" + "Holder\x1f: %s%%0A" + "Counter\x1f: %lu" + "%s"), + "\x1e", + serial, + disp_name? disp_name:"", + sigcount, + ""); + } + else + { + result = xtryasprintf (_("%s" + "Number\x1f: %s%%0A" + "Holder\x1f: %s" + "%s"), + "\x1e", + serial, + disp_name? disp_name:"", + ""); + } + xfree (disp_name); + xfree (serial); + + if (remaining != -1) + { + /* TRANSLATORS: This is the number of remaining attempts to + * enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */ + rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining); + if (!rembuf) + { + xfree (result); + return NULL; + } + tmpbuf = strconcat (result, "%0A%0A", rembuf, NULL); + xfree (rembuf); + if (!tmpbuf) + { + xfree (result); + return NULL; + } + xfree (result); + result = tmpbuf; + } + + return result; +} + +/* Compute hash if KDF-DO is available. CHVNO must be 0 for reset + code, 1 or 2 for user pin and 3 for admin pin. + */ +static gpg_error_t +pin2hash_if_kdf (app_t app, int chvno, char *pinvalue, int *r_pinlen) +{ + gpg_error_t err = 0; + void *relptr = NULL; + unsigned char *buffer; + size_t buflen; + + if (app->app_local->extcap.kdf_do + && (relptr = get_one_do (app, 0x00F9, &buffer, &buflen, NULL)) + && buflen >= KDF_DATA_LENGTH_MIN && (buffer[2] == 0x03)) + { + const char *salt; + unsigned long s2k_count; + char dek[32]; + int salt_index; + + s2k_count = (((unsigned int)buffer[8] << 24) + | (buffer[9] << 16) | (buffer[10] << 8) | buffer[11]); + + if (buflen == KDF_DATA_LENGTH_MIN) + salt_index =14; + else if (buflen == KDF_DATA_LENGTH_MAX) + salt_index = (chvno==3 ? 34 : (chvno==0 ? 24 : 14)); + else + { + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + salt = &buffer[salt_index]; + err = gcry_kdf_derive (pinvalue, strlen (pinvalue), + GCRY_KDF_ITERSALTED_S2K, + DIGEST_ALGO_SHA256, salt, 8, + s2k_count, sizeof (dek), dek); + if (!err) + { + /* pinvalue has a buffer of MAXLEN_PIN+1, 32 is OK. */ + *r_pinlen = 32; + memcpy (pinvalue, dek, *r_pinlen); + wipememory (dek, *r_pinlen); + } + } + else + *r_pinlen = strlen (pinvalue); + + leave: + xfree (relptr); + return err; +} + + +/* Verify a CHV either using the pinentry or if possible by + using a pinpad. PINCB and PINCB_ARG describe the usual callback + for the pinentry. CHVNO must be either 1 or 2. SIGCOUNT is only + used with CHV1. PINVALUE is the address of a pointer which will + receive a newly allocated block with the actual PIN (this is useful + in case that PIN shall be used for another verify operation). The + caller needs to free this value. If the function returns with + success and NULL is stored at PINVALUE, the caller should take this + as an indication that the pinpad has been used. + */ +static gpg_error_t +verify_a_chv (app_t app, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, int chvno, unsigned long sigcount, + char **pinvalue, int *pinlen) +{ + int rc = 0; + char *prompt_buffer = NULL; + const char *prompt; + pininfo_t pininfo; + int minlen = 6; + int remaining; + + log_assert (chvno == 1 || chvno == 2); + + *pinvalue = NULL; + *pinlen = 0; + + remaining = get_remaining_tries (app, 0); + if (remaining == -1) + return gpg_error (GPG_ERR_CARD); + + if (chvno == 2 && app->app_local->flags.def_chv2) + { + /* Special case for def_chv2 mechanism. */ + if (opt.verbose) + log_info (_("using default PIN as %s\n"), "CHV2"); + rc = iso7816_verify (app->slot, 0x82, "123456", 6); + if (rc) + { + /* Verification of CHV2 with the default PIN failed, + although the card pretends to have the default PIN set as + CHV2. We better disable the def_chv2 flag now. */ + log_info (_("failed to use default PIN as %s: %s" + " - disabling further default use\n"), + "CHV2", gpg_strerror (rc)); + app->app_local->flags.def_chv2 = 0; + } + return rc; + } + + memset (&pininfo, 0, sizeof pininfo); + pininfo.fixedlen = -1; + pininfo.minlen = minlen; + + { + const char *firstline = _("||Please unlock the card"); + char *infoblock = get_prompt_info (app, chvno, sigcount, + remaining < 3? remaining : -1); + + prompt_buffer = strconcat (firstline, "%0A%0A", infoblock, NULL); + if (prompt_buffer) + prompt = prompt_buffer; + else + prompt = firstline; /* ENOMEM fallback. */ + + xfree (infoblock); + } + + if (!opt.disable_pinpad + && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) + && !check_pinpad_request (app, &pininfo, 0)) + { + /* The reader supports the verify command through the pinpad. + Note that the pincb appends a text to the prompt telling the + user to use the pinpad. */ + rc = pincb (pincb_arg, prompt, NULL); + prompt = NULL; + xfree (prompt_buffer); + prompt_buffer = NULL; + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + return rc; + } + rc = iso7816_verify_kp (app->slot, 0x80+chvno, &pininfo); + /* Dismiss the prompt. */ + pincb (pincb_arg, NULL, NULL); + + log_assert (!*pinvalue); + } + else + { + /* The reader has no pinpad or we don't want to use it. */ + rc = pincb (pincb_arg, prompt, pinvalue); + prompt = NULL; + xfree (prompt_buffer); + prompt_buffer = NULL; + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + return rc; + } + + if (strlen (*pinvalue) < minlen) + { + log_error (_("PIN for CHV%d is too short;" + " minimum length is %d\n"), chvno, minlen); + xfree (*pinvalue); + *pinvalue = NULL; + return gpg_error (GPG_ERR_BAD_PIN); + } + + rc = pin2hash_if_kdf (app, chvno, *pinvalue, pinlen); + if (!rc) + rc = iso7816_verify (app->slot, 0x80+chvno, *pinvalue, *pinlen); + } + + if (rc) + { + log_error (_("verify CHV%d failed: %s\n"), chvno, gpg_strerror (rc)); + xfree (*pinvalue); + *pinvalue = NULL; + flush_cache_after_error (app); + } + + return rc; +} + + +/* Verify CHV2 if required. Depending on the configuration of the + card CHV1 will also be verified. */ +static gpg_error_t +verify_chv2 (app_t app, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc; + char *pinvalue; + int pinlen; + int i; + + if (app->did_chv2) + return 0; /* We already verified CHV2. */ + + /* Make sure we have load the public keys. */ + for (i = 0; i < 3; i++) + get_public_key (app, i); + + if (app->app_local->pk[1].key || app->app_local->pk[2].key) + { + rc = verify_a_chv (app, pincb, pincb_arg, 2, 0, &pinvalue, &pinlen); + if (rc) + return rc; + app->did_chv2 = 1; + + if (!app->did_chv1 && !app->force_chv1 && pinvalue && !opt.pcsc_shared) + { + /* For convenience we verify CHV1 here too. We do this only if + the card is not configured to require a verification before + each CHV1 controlled operation (force_chv1) and if we are not + using the pinpad (PINVALUE == NULL). */ + rc = iso7816_verify (app->slot, 0x81, pinvalue, pinlen); + if (gpg_err_code (rc) == GPG_ERR_BAD_PIN) + rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED); + if (rc) + { + log_error (_("verify CHV%d failed: %s\n"), 1, gpg_strerror (rc)); + flush_cache_after_error (app); + } + else + app->did_chv1 = 1; + } + } + else + { + rc = verify_a_chv (app, pincb, pincb_arg, 1, 0, &pinvalue, &pinlen); + if (rc) + return rc; + } + + xfree (pinvalue); + + return rc; +} + + +/* Build the prompt to enter the Admin PIN. The prompt depends on the + current sdtate of the card. */ +static gpg_error_t +build_enter_admin_pin_prompt (app_t app, char **r_prompt) +{ + int remaining; + char *prompt; + char *infoblock; + + *r_prompt = NULL; + + remaining = get_remaining_tries (app, 1); + if (remaining == -1) + return gpg_error (GPG_ERR_CARD); + if (!remaining) + { + log_info (_("card is permanently locked!\n")); + return gpg_error (GPG_ERR_BAD_PIN); + } + + log_info (ngettext("%d Admin PIN attempt remaining before card" + " is permanently locked\n", + "%d Admin PIN attempts remaining before card" + " is permanently locked\n", + remaining), remaining); + + infoblock = get_prompt_info (app, 3, 0, remaining < 3? remaining : -1); + + /* TRANSLATORS: Do not translate the "|A|" prefix but keep it at + the start of the string. Use %0A (single percent) for a linefeed. */ + prompt = strconcat (_("|A|Please enter the Admin PIN"), + "%0A%0A", infoblock, NULL); + xfree (infoblock); + if (!prompt) + return gpg_error_from_syserror (); + + *r_prompt = prompt; + return 0; +} + + +/* Verify CHV3 if required. */ +static gpg_error_t +verify_chv3 (app_t app, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc = 0; + + if (!opt.allow_admin) + { + log_info (_("access to admin commands is not configured\n")); + return gpg_error (GPG_ERR_EACCES); + } + + if (!app->did_chv3) + { + pininfo_t pininfo; + int minlen = 8; + char *prompt; + + memset (&pininfo, 0, sizeof pininfo); + pininfo.fixedlen = -1; + pininfo.minlen = minlen; + + rc = build_enter_admin_pin_prompt (app, &prompt); + if (rc) + return rc; + + if (!opt.disable_pinpad + && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) + && !check_pinpad_request (app, &pininfo, 1)) + { + /* The reader supports the verify command through the pinpad. */ + rc = pincb (pincb_arg, prompt, NULL); + xfree (prompt); + prompt = NULL; + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + return rc; + } + rc = iso7816_verify_kp (app->slot, 0x83, &pininfo); + /* Dismiss the prompt. */ + pincb (pincb_arg, NULL, NULL); + } + else + { + char *pinvalue; + int pinlen; + + rc = pincb (pincb_arg, prompt, &pinvalue); + xfree (prompt); + prompt = NULL; + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + return rc; + } + + if (strlen (pinvalue) < minlen) + { + log_error (_("PIN for CHV%d is too short;" + " minimum length is %d\n"), 3, minlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + + rc = pin2hash_if_kdf (app, 3, pinvalue, &pinlen); + if (!rc) + rc = iso7816_verify (app->slot, 0x83, pinvalue, pinlen); + xfree (pinvalue); + } + + if (rc) + { + log_error (_("verify CHV%d failed: %s\n"), 3, gpg_strerror (rc)); + flush_cache_after_error (app); + return rc; + } + app->did_chv3 = 1; + } + return rc; +} + + +/* Handle the SETATTR operation. All arguments are already basically + checked. */ +static gpg_error_t +do_setattr (app_t app, ctrl_t ctrl, const char *name, + gpg_error_t (*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; + int flush_tag; /* The tag which needs to be flushed or 0. */ + int need_chv; + int special; + unsigned int need_v2:1; + } table[] = { + { "DISP-NAME", 0x005B, 0, 3 }, + { "LOGIN-DATA", 0x005E, 0, 3, 2 }, + { "DISP-LANG", 0x5F2D, 0, 3 }, + { "DISP-SEX", 0x5F35, 0, 3 }, + { "PUBKEY-URL", 0x5F50, 0, 3 }, + { "CHV-STATUS-1", 0x00C4, 0, 3, 1 }, + { "CA-FPR-1", 0x00CA, 0x00C6, 3 }, + { "CA-FPR-2", 0x00CB, 0x00C6, 3 }, + { "CA-FPR-3", 0x00CC, 0x00C6, 3 }, + { "PRIVATE-DO-1", 0x0101, 0, 2 }, + { "PRIVATE-DO-2", 0x0102, 0, 3 }, + { "PRIVATE-DO-3", 0x0103, 0, 2 }, + { "PRIVATE-DO-4", 0x0104, 0, 3 }, + { "CERT-3", 0x7F21, 0, 3, 0, 1 }, + { "SM-KEY-ENC", 0x00D1, 0, 3, 0, 1 }, + { "SM-KEY-MAC", 0x00D2, 0, 3, 0, 1 }, + { "KEY-ATTR", 0, 0, 0, 3, 1 }, + { "AESKEY", 0x00D5, 0, 3, 0, 1 }, + { "KDF", 0x00F9, 0, 3, 4, 1 }, + { NULL, 0 } + }; + int exmode; + + (void)ctrl; + + for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++) + ; + if (!table[idx].name) + return gpg_error (GPG_ERR_INV_NAME); + if (table[idx].need_v2 && !app->app_local->extcap.is_v2) + return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Not yet supported. */ + + if (table[idx].special == 3) + return change_keyattr_from_string (app, pincb, pincb_arg, value, valuelen); + + switch (table[idx].need_chv) + { + case 2: + rc = verify_chv2 (app, pincb, pincb_arg); + break; + case 3: + rc = verify_chv3 (app, pincb, pincb_arg); + break; + default: + rc = 0; + } + if (rc) + return rc; + + /* Flush the cache before writing it, so that the next get operation + will reread the data from the card and thus get synced in case of + errors (e.g. data truncated by the card). */ + flush_cache_item (app, table[idx].flush_tag? table[idx].flush_tag + /* */ : table[idx].tag); + + if (app->app_local->cardcap.ext_lc_le && valuelen > 254) + exmode = 1; /* Use extended length w/o a limit. */ + else if (app->app_local->cardcap.cmd_chaining && valuelen > 254) + exmode = -254; /* Command chaining with max. 254 bytes. */ + else + exmode = 0; + rc = iso7816_put_data (app->slot, exmode, table[idx].tag, value, valuelen); + if (rc) + log_error ("failed to set '%s': %s\n", table[idx].name, gpg_strerror (rc)); + + if (table[idx].special == 1) + app->force_chv1 = (valuelen && *value == 0); + else if (table[idx].special == 2) + parse_login_data (app); + else if (table[idx].special == 4) + { + app->did_chv1 = 0; + app->did_chv2 = 0; + app->did_chv3 = 0; + + if ((valuelen == KDF_DATA_LENGTH_MIN || valuelen == KDF_DATA_LENGTH_MAX) + && (value[2] == 0x03)) + app->app_local->pinpad.disabled = 1; + else + app->app_local->pinpad.disabled = 0; + } + + return rc; +} + + +/* Handle the WRITECERT command for OpenPGP. This rites the standard + certifciate to the card; CERTID needs to be set to "OPENPGP.3". + PINCB and PINCB_ARG are the usual arguments for the pinentry + callback. */ +static gpg_error_t +do_writecert (app_t app, ctrl_t ctrl, + const char *certidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *certdata, size_t certdatalen) +{ + if (strcmp (certidstr, "OPENPGP.3")) + return gpg_error (GPG_ERR_INV_ID); + if (!certdata || !certdatalen) + return gpg_error (GPG_ERR_INV_ARG); + if (!app->app_local->extcap.is_v2) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + if (certdatalen > app->app_local->extcap.max_certlen_3) + return gpg_error (GPG_ERR_TOO_LARGE); + return do_setattr (app, ctrl, "CERT-3", pincb, pincb_arg, + certdata, certdatalen); +} + + + +/* Handle the PASSWD command. The following combinations are + possible: + + Flags CHVNO Vers. Description + RESET 1 1 Verify CHV3 and set a new CHV1 and CHV2 + RESET 1 2 Verify PW3 and set a new PW1. + RESET 2 1 Verify CHV3 and set a new CHV1 and CHV2. + RESET 2 2 Verify PW3 and set a new Reset Code. + RESET 3 any Returns GPG_ERR_INV_ID. + - 1 1 Verify CHV2 and set a new CHV1 and CHV2. + - 1 2 Verify PW1 and set a new PW1. + - 2 1 Verify CHV2 and set a new CHV1 and CHV2. + - 2 2 Verify Reset Code and set a new PW1. + - 3 any Verify CHV3/PW3 and set a new CHV3/PW3. + + The CHVNO can be prefixed with "OPENPGP.". + */ +static gpg_error_t +do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, + unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc = 0; + int chvno; + char *resetcode = NULL; + char *oldpinvalue = NULL; + char *pinvalue = NULL; + int reset_mode = !!(flags & APP_CHANGE_FLAG_RESET); + int set_resetcode = 0; + pininfo_t pininfo; + int use_pinpad = 0; + int minlen = 6; + int pinlen0 = 0; + int pinlen = 0; + + (void)ctrl; + + if (digitp (chvnostr)) + chvno = atoi (chvnostr); + else if (!ascii_strcasecmp (chvnostr, "OPENPGP.1")) + chvno = 1; + else if (!ascii_strcasecmp (chvnostr, "OPENPGP.2")) + chvno = 2; + else if (!ascii_strcasecmp (chvnostr, "OPENPGP.3")) + chvno = 3; + else + return gpg_error (GPG_ERR_INV_ID); + + memset (&pininfo, 0, sizeof pininfo); + pininfo.fixedlen = -1; + pininfo.minlen = minlen; + + if ((flags & APP_CHANGE_FLAG_CLEAR)) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + if (reset_mode && chvno == 3) + { + rc = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + + if (!app->app_local->extcap.is_v2) + { + /* Version 1 cards. */ + + if (reset_mode || chvno == 3) + { + /* We always require that the PIN is entered. */ + app->did_chv3 = 0; + rc = verify_chv3 (app, pincb, pincb_arg); + if (rc) + goto leave; + } + else if (chvno == 1 || chvno == 2) + { + /* On a v1.x card CHV1 and CVH2 should always have the same + value, thus we enforce it here. */ + int save_force = app->force_chv1; + + app->force_chv1 = 0; + app->did_chv1 = 0; + app->did_chv2 = 0; + rc = verify_chv2 (app, pincb, pincb_arg); + app->force_chv1 = save_force; + if (rc) + goto leave; + } + else + { + rc = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + } + else + { + /* Version 2 cards. */ + + if (!opt.disable_pinpad + && !iso7816_check_pinpad (app->slot, + ISO7816_CHANGE_REFERENCE_DATA, &pininfo) + && !check_pinpad_request (app, &pininfo, chvno == 3)) + use_pinpad = 1; + + if (reset_mode) + { + /* To reset a PIN the Admin PIN is required. */ + use_pinpad = 0; + app->did_chv3 = 0; + rc = verify_chv3 (app, pincb, pincb_arg); + if (rc) + goto leave; + + if (chvno == 2) + set_resetcode = 1; + } + else if (chvno == 1 || chvno == 3) + { + if (!use_pinpad) + { + char *promptbuf = NULL; + const char *prompt; + + if (chvno == 3) + { + minlen = 8; + rc = build_enter_admin_pin_prompt (app, &promptbuf); + if (rc) + goto leave; + prompt = promptbuf; + } + else + prompt = _("||Please enter the PIN"); + rc = pincb (pincb_arg, prompt, &oldpinvalue); + xfree (promptbuf); + promptbuf = NULL; + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + goto leave; + } + + if (strlen (oldpinvalue) < minlen) + { + log_info (_("PIN for CHV%d is too short;" + " minimum length is %d\n"), chvno, minlen); + rc = gpg_error (GPG_ERR_BAD_PIN); + goto leave; + } + } + } + else if (chvno == 2) + { + /* There is no PW2 for v2 cards. We use this condition to + allow a PW reset using the Reset Code. */ + void *relptr; + unsigned char *value; + size_t valuelen; + int remaining; + + use_pinpad = 0; + minlen = 8; + relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL); + if (!relptr || valuelen < 7) + { + log_error (_("error retrieving CHV status from card\n")); + xfree (relptr); + rc = gpg_error (GPG_ERR_CARD); + goto leave; + } + remaining = value[5]; + xfree (relptr); + if (!remaining) + { + log_error (_("Reset Code not or not anymore available\n")); + rc = gpg_error (GPG_ERR_BAD_PIN); + goto leave; + } + + rc = pincb (pincb_arg, + _("||Please enter the Reset Code for the card"), + &resetcode); + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + goto leave; + } + if (strlen (resetcode) < minlen) + { + log_info (_("Reset Code is too short; minimum length is %d\n"), + minlen); + rc = gpg_error (GPG_ERR_BAD_PIN); + goto leave; + } + } + else + { + rc = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + } + + if (chvno == 3) + app->did_chv3 = 0; + else + app->did_chv1 = app->did_chv2 = 0; + + if (!use_pinpad) + { + /* TRANSLATORS: Do not translate the "|*|" prefixes but + keep it at the start of the string. We need this elsewhere + to get some infos on the string. */ + rc = pincb (pincb_arg, set_resetcode? _("|RN|New Reset Code") : + chvno == 3? _("|AN|New Admin PIN") : _("|N|New PIN"), + &pinvalue); + if (rc || pinvalue == NULL) + { + log_error (_("error getting new PIN: %s\n"), gpg_strerror (rc)); + goto leave; + } + } + + + if (resetcode) + { + char *buffer; + + buffer = xtrymalloc (strlen (resetcode) + strlen (pinvalue) + 1); + if (!buffer) + rc = gpg_error_from_syserror (); + else + { + strcpy (buffer, resetcode); + rc = pin2hash_if_kdf (app, 0, buffer, &pinlen0); + if (!rc) + { + strcpy (buffer+pinlen0, pinvalue); + rc = pin2hash_if_kdf (app, 1, buffer+pinlen0, &pinlen); + } + if (!rc) + rc = iso7816_reset_retry_counter_with_rc (app->slot, 0x81, + buffer, pinlen0+pinlen); + wipememory (buffer, pinlen0 + pinlen); + xfree (buffer); + } + } + else if (set_resetcode) + { + if (strlen (pinvalue) < 8) + { + log_error (_("Reset Code is too short; minimum length is %d\n"), 8); + rc = gpg_error (GPG_ERR_BAD_PIN); + } + else + { + rc = pin2hash_if_kdf (app, 0, pinvalue, &pinlen); + if (!rc) + rc = iso7816_put_data (app->slot, 0, 0xD3, pinvalue, pinlen); + } + } + else if (reset_mode) + { + rc = pin2hash_if_kdf (app, 1, pinvalue, &pinlen); + if (!rc) + rc = iso7816_reset_retry_counter (app->slot, 0x81, pinvalue, pinlen); + if (!rc && !app->app_local->extcap.is_v2) + rc = iso7816_reset_retry_counter (app->slot, 0x82, pinvalue, pinlen); + } + else if (!app->app_local->extcap.is_v2) + { + /* Version 1 cards. */ + if (chvno == 1 || chvno == 2) + { + rc = iso7816_change_reference_data (app->slot, 0x81, NULL, 0, + pinvalue, strlen (pinvalue)); + if (!rc) + rc = iso7816_change_reference_data (app->slot, 0x82, NULL, 0, + pinvalue, strlen (pinvalue)); + } + else /* CHVNO == 3 */ + { + rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, NULL, 0, + pinvalue, strlen (pinvalue)); + } + } + else + { + /* Version 2 cards. */ + assert (chvno == 1 || chvno == 3); + + if (use_pinpad) + { + rc = pincb (pincb_arg, + chvno == 3 ? + _("||Please enter the Admin PIN and New Admin PIN") : + _("||Please enter the PIN and New PIN"), NULL); + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + goto leave; + } + rc = iso7816_change_reference_data_kp (app->slot, 0x80 + chvno, 0, + &pininfo); + pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */ + } + else + { + rc = pin2hash_if_kdf (app, chvno, oldpinvalue, &pinlen0); + if (!rc) + rc = pin2hash_if_kdf (app, chvno, pinvalue, &pinlen); + if (!rc) + rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, + oldpinvalue, pinlen0, + pinvalue, pinlen); + } + } + + if (pinvalue) + { + wipememory (pinvalue, pinlen); + xfree (pinvalue); + } + if (rc) + flush_cache_after_error (app); + + leave: + if (resetcode) + { + wipememory (resetcode, strlen (resetcode)); + xfree (resetcode); + } + if (oldpinvalue) + { + wipememory (oldpinvalue, pinlen0); + xfree (oldpinvalue); + } + return rc; +} + + +/* Check whether a key already exists. KEYIDX is the index of the key + (0..2). If FORCE is TRUE a diagnositic will be printed but no + error returned if the key already exists. The flag GENERATING is + only used to print correct messages. */ +static gpg_error_t +does_key_exist (app_t app, int keyidx, int generating, int force) +{ + const unsigned char *fpr; + unsigned char *buffer; + size_t buflen, n; + int i; + + assert (keyidx >=0 && keyidx <= 2); + + if (iso7816_get_data (app->slot, 0, 0x006E, &buffer, &buflen)) + { + log_error (_("error reading application data\n")); + return gpg_error (GPG_ERR_GENERAL); + } + fpr = find_tlv (buffer, buflen, 0x00C5, &n); + if (!fpr || n < 60) + { + log_error (_("error reading fingerprint DO\n")); + xfree (buffer); + return gpg_error (GPG_ERR_GENERAL); + } + fpr += 20*keyidx; + for (i=0; i < 20 && !fpr[i]; i++) + ; + xfree (buffer); + if (i!=20 && !force) + { + log_error (_("key already exists\n")); + return gpg_error (GPG_ERR_EEXIST); + } + else if (i!=20) + log_info (_("existing key will be replaced\n")); + else if (generating) + log_info (_("generating new key\n")); + else + log_info (_("writing new key\n")); + return 0; +} + + +/* Create a TLV tag and value and store it at BUFFER. Return the length + of tag and length. A LENGTH greater than 65535 is truncated. */ +static size_t +add_tlv (unsigned char *buffer, unsigned int tag, size_t length) +{ + unsigned char *p = buffer; + + assert (tag <= 0xffff); + if ( tag > 0xff ) + *p++ = tag >> 8; + *p++ = tag; + if (length < 128) + *p++ = length; + else if (length < 256) + { + *p++ = 0x81; + *p++ = length; + } + else + { + if (length > 0xffff) + length = 0xffff; + *p++ = 0x82; + *p++ = length >> 8; + *p++ = length; + } + + return p - buffer; +} + + +static gpg_error_t +build_privkey_template (app_t app, int keyno, + const unsigned char *rsa_n, size_t rsa_n_len, + const unsigned char *rsa_e, size_t rsa_e_len, + const unsigned char *rsa_p, size_t rsa_p_len, + const unsigned char *rsa_q, size_t rsa_q_len, + const unsigned char *rsa_u, size_t rsa_u_len, + const unsigned char *rsa_dp, size_t rsa_dp_len, + const unsigned char *rsa_dq, size_t rsa_dq_len, + unsigned char **result, size_t *resultlen) +{ + size_t rsa_e_reqlen; + unsigned char privkey[7*(1+3+3)]; + size_t privkey_len; + unsigned char exthdr[2+2+3]; + size_t exthdr_len; + unsigned char suffix[2+3]; + size_t suffix_len; + unsigned char *tp; + size_t datalen; + unsigned char *template; + size_t template_size; + + *result = NULL; + *resultlen = 0; + + switch (app->app_local->keyattr[keyno].rsa.format) + { + case RSA_STD: + case RSA_STD_N: + case RSA_CRT: + case RSA_CRT_N: + break; + + default: + return gpg_error (GPG_ERR_INV_VALUE); + } + + /* Get the required length for E. Rounded up to the nearest byte */ + rsa_e_reqlen = (app->app_local->keyattr[keyno].rsa.e_bits + 7) / 8; + assert (rsa_e_len <= rsa_e_reqlen); + + /* Build the 7f48 cardholder private key template. */ + datalen = 0; + tp = privkey; + + tp += add_tlv (tp, 0x91, rsa_e_reqlen); + datalen += rsa_e_reqlen; + + tp += add_tlv (tp, 0x92, rsa_p_len); + datalen += rsa_p_len; + + tp += add_tlv (tp, 0x93, rsa_q_len); + datalen += rsa_q_len; + + if (app->app_local->keyattr[keyno].rsa.format == RSA_CRT + || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N) + { + tp += add_tlv (tp, 0x94, rsa_u_len); + datalen += rsa_u_len; + tp += add_tlv (tp, 0x95, rsa_dp_len); + datalen += rsa_dp_len; + tp += add_tlv (tp, 0x96, rsa_dq_len); + datalen += rsa_dq_len; + } + + if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N + || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N) + { + tp += add_tlv (tp, 0x97, rsa_n_len); + datalen += rsa_n_len; + } + privkey_len = tp - privkey; + + /* Build the extended header list without the private key template. */ + tp = exthdr; + *tp++ = keyno ==0 ? 0xb6 : keyno == 1? 0xb8 : 0xa4; + *tp++ = 0; + tp += add_tlv (tp, 0x7f48, privkey_len); + exthdr_len = tp - exthdr; + + /* Build the 5f48 suffix of the data. */ + tp = suffix; + tp += add_tlv (tp, 0x5f48, datalen); + suffix_len = tp - suffix; + + /* Now concatenate everything. */ + template_size = (1 + 3 /* 0x4d and len. */ + + exthdr_len + + privkey_len + + suffix_len + + datalen); + tp = template = xtrymalloc_secure (template_size); + if (!template) + return gpg_error_from_syserror (); + + tp += add_tlv (tp, 0x4d, exthdr_len + privkey_len + suffix_len + datalen); + memcpy (tp, exthdr, exthdr_len); + tp += exthdr_len; + memcpy (tp, privkey, privkey_len); + tp += privkey_len; + memcpy (tp, suffix, suffix_len); + tp += suffix_len; + + memcpy (tp, rsa_e, rsa_e_len); + if (rsa_e_len < rsa_e_reqlen) + { + /* Right justify E. */ + memmove (tp + rsa_e_reqlen - rsa_e_len, tp, rsa_e_len); + memset (tp, 0, rsa_e_reqlen - rsa_e_len); + } + tp += rsa_e_reqlen; + + memcpy (tp, rsa_p, rsa_p_len); + tp += rsa_p_len; + + memcpy (tp, rsa_q, rsa_q_len); + tp += rsa_q_len; + + if (app->app_local->keyattr[keyno].rsa.format == RSA_CRT + || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N) + { + memcpy (tp, rsa_u, rsa_u_len); + tp += rsa_u_len; + memcpy (tp, rsa_dp, rsa_dp_len); + tp += rsa_dp_len; + memcpy (tp, rsa_dq, rsa_dq_len); + tp += rsa_dq_len; + } + + if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N + || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N) + { + memcpy (tp, rsa_n, rsa_n_len); + tp += rsa_n_len; + } + + /* Sanity check. We don't know the exact length because we + allocated 3 bytes for the first length header. */ + assert (tp - template <= template_size); + + *result = template; + *resultlen = tp - template; + return 0; +} + +static gpg_error_t +build_ecc_privkey_template (app_t app, int keyno, + const unsigned char *ecc_d, size_t ecc_d_len, + size_t ecc_d_fixed_len, + const unsigned char *ecc_q, size_t ecc_q_len, + unsigned char **result, size_t *resultlen) +{ + unsigned char privkey[2*(1+3)]; + size_t privkey_len; + unsigned char exthdr[2+2+3]; + size_t exthdr_len; + unsigned char suffix[2+3]; + size_t suffix_len; + unsigned char *tp; + size_t datalen; + unsigned char *template; + size_t template_size; + int pubkey_required; + + /* This case doesn't occur in GnuPG 2.3 or later, because + agent/sexp-secret.c does the fixup. */ + if (ecc_d_fixed_len < ecc_d_len) + { + if (ecc_d_fixed_len != ecc_d_len - 1 || *ecc_d) + return gpg_error (GPG_ERR_INV_OBJ); + + /* Remove the additional zero. */ + ecc_d_len--; + ecc_d++; + } + + pubkey_required = !!(app->app_local->keyattr[keyno].ecc.flags + & ECC_FLAG_PUBKEY); + + *result = NULL; + *resultlen = 0; + + /* Build the 7f48 cardholder private key template. */ + datalen = 0; + tp = privkey; + + tp += add_tlv (tp, 0x92, ecc_d_fixed_len); + datalen += ecc_d_fixed_len; + + if (pubkey_required) + { + tp += add_tlv (tp, 0x99, ecc_q_len); + datalen += ecc_q_len; + } + + privkey_len = tp - privkey; + + + /* Build the extended header list without the private key template. */ + tp = exthdr; + *tp++ = keyno ==0 ? 0xb6 : keyno == 1? 0xb8 : 0xa4; + *tp++ = 0; + tp += add_tlv (tp, 0x7f48, privkey_len); + exthdr_len = tp - exthdr; + + /* Build the 5f48 suffix of the data. */ + tp = suffix; + tp += add_tlv (tp, 0x5f48, datalen); + suffix_len = tp - suffix; + + /* Now concatenate everything. */ + template_size = (1 + 1 /* 0x4d and len. */ + + exthdr_len + + privkey_len + + suffix_len + + datalen); + if (exthdr_len + privkey_len + suffix_len + datalen >= 128) + template_size++; + tp = template = xtrymalloc_secure (template_size); + if (!template) + return gpg_error_from_syserror (); + + tp += add_tlv (tp, 0x4d, exthdr_len + privkey_len + suffix_len + datalen); + memcpy (tp, exthdr, exthdr_len); + tp += exthdr_len; + memcpy (tp, privkey, privkey_len); + tp += privkey_len; + memcpy (tp, suffix, suffix_len); + tp += suffix_len; + + if (ecc_d_fixed_len > ecc_d_len) + { + memset (tp, 0, ecc_d_fixed_len - ecc_d_len); + memcpy (tp + ecc_d_fixed_len - ecc_d_len, ecc_d, ecc_d_len); + } + else + memcpy (tp, ecc_d, ecc_d_len); + tp += ecc_d_fixed_len; + + if (pubkey_required) + { + memcpy (tp, ecc_q, ecc_q_len); + tp += ecc_q_len; + } + + assert (tp - template == template_size); + + *result = template; + *resultlen = tp - template; + return 0; +} + + +/* Helper for do_writekey to change the size of a key. Note that + this deletes the entire key without asking. */ +static gpg_error_t +change_keyattr (app_t app, int keyno, const unsigned char *buf, size_t buflen, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + + assert (keyno >=0 && keyno <= 2); + + /* Prepare for storing the key. */ + err = verify_chv3 (app, pincb, pincb_arg); + if (err) + return err; + + /* Change the attribute. */ + err = iso7816_put_data (app->slot, 0, 0xC1+keyno, buf, buflen); + if (err) + log_error ("error changing key attribute (key=%d)\n", keyno+1); + else + log_info ("key attribute changed (key=%d)\n", keyno+1); + flush_cache (app); + err = parse_algorithm_attribute (app, keyno); + app->did_chv1 = 0; + app->did_chv2 = 0; + app->did_chv3 = 0; + return err; +} + + +static gpg_error_t +change_rsa_keyattr (app_t app, int keyno, unsigned int nbits, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err = 0; + unsigned char *buf; + size_t buflen; + void *relptr; + + /* Read the current attributes into a buffer. */ + relptr = get_one_do (app, 0xC1+keyno, &buf, &buflen, NULL); + if (!relptr) + err = gpg_error (GPG_ERR_CARD); + else if (buflen < 6) + { + /* Attributes too short. */ + xfree (relptr); + err = gpg_error (GPG_ERR_CARD); + } + else + { + /* If key attribute was RSA, we only change n_bits and don't + touch anything else. Before we do so, we round up NBITS to a + sensible way in the same way as gpg's key generation does it. + This may help to sort out problems with a few bits too short + keys. */ + nbits = ((nbits + 31) / 32) * 32; + buf[1] = (nbits >> 8); + buf[2] = nbits; + + /* If it was not RSA, we need to fill other parts. */ + if (buf[0] != PUBKEY_ALGO_RSA) + { + buf[0] = PUBKEY_ALGO_RSA; + buf[3] = 0; + buf[4] = 32; + buf[5] = 0; + buflen = 6; + } + + err = change_keyattr (app, keyno, buf, buflen, pincb, pincb_arg); + xfree (relptr); + } + + return err; +} + + +/* Helper to process an setattr command for name KEY-ATTR. + In (VALUE,VALUELEN), it expects following string: + RSA: "--force <key> <algo> rsa<nbits>" + ECC: "--force <key> <algo> <curvename>" + */ +static gpg_error_t +change_keyattr_from_string (app_t app, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *value, size_t valuelen) +{ + gpg_error_t err = 0; + char *string; + int key, keyno, algo; + int n = 0; + + /* VALUE is expected to be a string but not guaranteed to be + terminated. Thus copy it to an allocated buffer first. */ + string = xtrymalloc (valuelen+1); + if (!string) + return gpg_error_from_syserror (); + memcpy (string, value, valuelen); + string[valuelen] = 0; + + /* Because this function deletes the key we require the string + "--force" in the data to make clear that something serious might + happen. */ + sscanf (string, "--force %d %d %n", &key, &algo, &n); + if (n < 12) + { + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + keyno = key - 1; + if (keyno < 0 || keyno > 2) + err = gpg_error (GPG_ERR_INV_ID); + else if (algo == PUBKEY_ALGO_RSA) + { + unsigned int nbits; + + errno = 0; + nbits = strtoul (string+n+3, NULL, 10); + if (errno) + err = gpg_error (GPG_ERR_INV_DATA); + else if (nbits < 1024) + err = gpg_error (GPG_ERR_TOO_SHORT); + else if (nbits > 4096) + err = gpg_error (GPG_ERR_TOO_LARGE); + else + err = change_rsa_keyattr (app, keyno, nbits, pincb, pincb_arg); + } + else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA) + { + const char *oidstr; + gcry_mpi_t oid; + const unsigned char *oidbuf; + size_t oid_len; + + oidstr = openpgp_curve_to_oid (string+n, NULL, NULL); + if (!oidstr) + { + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + err = openpgp_oid_from_str (oidstr, &oid); + if (err) + goto leave; + + oidbuf = gcry_mpi_get_opaque (oid, &n); + oid_len = (n+7)/8; + + /* We have enough room at STRING. */ + string[0] = algo; + memcpy (string+1, oidbuf+1, oid_len-1); + err = change_keyattr (app, keyno, string, oid_len, pincb, pincb_arg); + gcry_mpi_release (oid); + } + else + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + + leave: + xfree (string); + return err; +} + + +static gpg_error_t +rsa_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, int keyno, + const unsigned char *buf, size_t buflen, int depth) +{ + gpg_error_t err; + const unsigned char *tok; + size_t toklen; + int last_depth1, last_depth2; + const unsigned char *rsa_n = NULL; + const unsigned char *rsa_e = NULL; + const unsigned char *rsa_p = NULL; + const unsigned char *rsa_q = NULL; + size_t rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len; + unsigned int nbits; + unsigned int maxbits; + unsigned char *template = NULL; + unsigned char *tp; + size_t template_len; + unsigned char fprbuf[20]; + u32 created_at = 0; + + if (app->app_local->keyattr[keyno].key_type != KEY_TYPE_RSA) + { + log_error (_("unsupported algorithm: %s"), "RSA"); + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + last_depth1 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth1) + { + if (tok) + { + err = gpg_error (GPG_ERR_UNKNOWN_SEXP); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (tok && toklen == 1) + { + const unsigned char **mpi; + size_t *mpi_len; + + switch (*tok) + { + case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break; + case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break; + case 'p': mpi = &rsa_p; mpi_len = &rsa_p_len; break; + case 'q': mpi = &rsa_q; mpi_len = &rsa_q_len;break; + default: mpi = NULL; mpi_len = NULL; break; + } + if (mpi && *mpi) + { + err = gpg_error (GPG_ERR_DUP_VALUE); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (tok && mpi) + { + /* Strip off leading zero bytes and save. */ + for (;toklen && !*tok; toklen--, tok++) + ; + *mpi = tok; + *mpi_len = toklen; + } + } + /* Skip until end of list. */ + last_depth2 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth2) + ; + if (err) + goto leave; + } + /* Parse other attributes. */ + last_depth1 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth1) + { + if (tok) + { + err = gpg_error (GPG_ERR_UNKNOWN_SEXP); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (tok && toklen == 10 && !memcmp ("created-at", tok, toklen)) + { + if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen))) + goto leave; + if (tok) + { + for (created_at=0; toklen && *tok && *tok >= '0' && *tok <= '9'; + tok++, toklen--) + created_at = created_at*10 + (*tok - '0'); + } + } + /* Skip until end of list. */ + last_depth2 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth2) + ; + if (err) + goto leave; + } + + + /* Check that we have all parameters and that they match the card + description. */ + if (!created_at) + { + log_error (_("creation timestamp missing\n")); + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + maxbits = app->app_local->keyattr[keyno].rsa.n_bits; + nbits = rsa_n? count_bits (rsa_n, rsa_n_len) : 0; + if (opt.verbose) + log_info ("RSA modulus size is %u bits\n", nbits); + if (nbits && nbits != maxbits + && app->app_local->extcap.algo_attr_change) + { + /* Try to switch the key to a new length. */ + err = change_rsa_keyattr (app, keyno, nbits, pincb, pincb_arg); + if (!err) + maxbits = app->app_local->keyattr[keyno].rsa.n_bits; + } + if (nbits != maxbits) + { + log_error (_("RSA modulus missing or not of size %d bits\n"), + (int)maxbits); + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + maxbits = app->app_local->keyattr[keyno].rsa.e_bits; + if (maxbits > 32 && !app->app_local->extcap.is_v2) + maxbits = 32; /* Our code for v1 does only support 32 bits. */ + nbits = rsa_e? count_bits (rsa_e, rsa_e_len) : 0; + if (nbits < 2 || nbits > maxbits) + { + log_error (_("RSA public exponent missing or larger than %d bits\n"), + (int)maxbits); + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + maxbits = app->app_local->keyattr[keyno].rsa.n_bits/2; + nbits = rsa_p? count_bits (rsa_p, rsa_p_len) : 0; + if (nbits != maxbits) + { + log_error (_("RSA prime %s missing or not of size %d bits\n"), + "P", (int)maxbits); + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + nbits = rsa_q? count_bits (rsa_q, rsa_q_len) : 0; + if (nbits != maxbits) + { + log_error (_("RSA prime %s missing or not of size %d bits\n"), + "Q", (int)maxbits); + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + /* We need to remove the cached public key. */ + xfree (app->app_local->pk[keyno].key); + app->app_local->pk[keyno].key = NULL; + app->app_local->pk[keyno].keylen = 0; + app->app_local->pk[keyno].read_done = 0; + + + if (app->app_local->extcap.is_v2) + { + unsigned char *rsa_u, *rsa_dp, *rsa_dq; + size_t rsa_u_len, rsa_dp_len, rsa_dq_len; + gcry_mpi_t mpi_e, mpi_p, mpi_q; + gcry_mpi_t mpi_u = gcry_mpi_snew (0); + gcry_mpi_t mpi_dp = gcry_mpi_snew (0); + gcry_mpi_t mpi_dq = gcry_mpi_snew (0); + gcry_mpi_t mpi_tmp = gcry_mpi_snew (0); + int exmode; + + /* Calculate the u, dp and dq components needed by RSA_CRT cards */ + gcry_mpi_scan (&mpi_e, GCRYMPI_FMT_USG, rsa_e, rsa_e_len, NULL); + gcry_mpi_scan (&mpi_p, GCRYMPI_FMT_USG, rsa_p, rsa_p_len, NULL); + gcry_mpi_scan (&mpi_q, GCRYMPI_FMT_USG, rsa_q, rsa_q_len, NULL); + + gcry_mpi_invm (mpi_u, mpi_q, mpi_p); + gcry_mpi_sub_ui (mpi_tmp, mpi_p, 1); + gcry_mpi_invm (mpi_dp, mpi_e, mpi_tmp); + gcry_mpi_sub_ui (mpi_tmp, mpi_q, 1); + gcry_mpi_invm (mpi_dq, mpi_e, mpi_tmp); + + gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_u, &rsa_u_len, mpi_u); + gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dp, &rsa_dp_len, mpi_dp); + gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dq, &rsa_dq_len, mpi_dq); + + gcry_mpi_release (mpi_e); + gcry_mpi_release (mpi_p); + gcry_mpi_release (mpi_q); + gcry_mpi_release (mpi_u); + gcry_mpi_release (mpi_dp); + gcry_mpi_release (mpi_dq); + gcry_mpi_release (mpi_tmp); + + /* Build the private key template as described in section 4.3.3.7 of + the OpenPGP card specs version 2.0. */ + err = build_privkey_template (app, keyno, + rsa_n, rsa_n_len, + rsa_e, rsa_e_len, + rsa_p, rsa_p_len, + rsa_q, rsa_q_len, + rsa_u, rsa_u_len, + rsa_dp, rsa_dp_len, + rsa_dq, rsa_dq_len, + &template, &template_len); + xfree(rsa_u); + xfree(rsa_dp); + xfree(rsa_dq); + + if (err) + goto leave; + + /* Prepare for storing the key. */ + err = verify_chv3 (app, pincb, pincb_arg); + if (err) + goto leave; + + /* Store the key. */ + if (app->app_local->cardcap.ext_lc_le && template_len > 254) + exmode = 1; /* Use extended length w/o a limit. */ + else if (app->app_local->cardcap.cmd_chaining && template_len > 254) + exmode = -254; + else + exmode = 0; + err = iso7816_put_data_odd (app->slot, exmode, 0x3fff, + template, template_len); + } + else + { + /* Build the private key template as described in section 4.3.3.6 of + the OpenPGP card specs version 1.1: + 0xC0 <length> public exponent + 0xC1 <length> prime p + 0xC2 <length> prime q + */ + assert (rsa_e_len <= 4); + template_len = (1 + 1 + 4 + + 1 + 1 + rsa_p_len + + 1 + 1 + rsa_q_len); + template = tp = xtrymalloc_secure (template_len); + if (!template) + { + err = gpg_error_from_syserror (); + goto leave; + } + *tp++ = 0xC0; + *tp++ = 4; + memcpy (tp, rsa_e, rsa_e_len); + if (rsa_e_len < 4) + { + /* Right justify E. */ + memmove (tp+4-rsa_e_len, tp, rsa_e_len); + memset (tp, 0, 4-rsa_e_len); + } + tp += 4; + + *tp++ = 0xC1; + *tp++ = rsa_p_len; + memcpy (tp, rsa_p, rsa_p_len); + tp += rsa_p_len; + + *tp++ = 0xC2; + *tp++ = rsa_q_len; + memcpy (tp, rsa_q, rsa_q_len); + tp += rsa_q_len; + + assert (tp - template == template_len); + + /* Prepare for storing the key. */ + err = verify_chv3 (app, pincb, pincb_arg); + if (err) + goto leave; + + /* Store the key. */ + err = iso7816_put_data (app->slot, 0, + (app->appversion > 0x0007? 0xE0:0xE9)+keyno, + template, template_len); + } + if (err) + { + log_error (_("failed to store the key: %s\n"), gpg_strerror (err)); + goto leave; + } + + err = store_fpr (app, keyno, created_at, fprbuf, PUBKEY_ALGO_RSA, + rsa_n, rsa_n_len, rsa_e, rsa_e_len); + if (err) + goto leave; + + + leave: + xfree (template); + return err; +} + + +static gpg_error_t +ecc_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, int keyno, + const unsigned char *buf, size_t buflen, int depth) +{ + gpg_error_t err; + const unsigned char *tok; + size_t toklen; + int last_depth1, last_depth2; + const unsigned char *ecc_q = NULL; + const unsigned char *ecc_d = NULL; + size_t ecc_q_len, ecc_d_len; + const char *curve = NULL; + u32 created_at = 0; + const char *oidstr; + int flag_djb_tweak = 0; + int algo; + gcry_mpi_t oid = NULL; + const unsigned char *oidbuf; + unsigned int n; + size_t oid_len; + unsigned char fprbuf[20]; + size_t ecc_d_fixed_len; + + /* (private-key(ecc(curve%s)(q%m)(d%m))(created-at%d)): + curve = "NIST P-256" */ + /* (private-key(ecc(curve%s)(q%m)(d%m))(created-at%d)): + curve = "secp256k1" */ + /* (private-key(ecc(curve%s)(flags eddsa)(q%m)(d%m))(created-at%d)): + curve = "Ed25519" */ + last_depth1 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth1) + { + if (tok) + { + err = gpg_error (GPG_ERR_UNKNOWN_SEXP); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + if (tok && toklen == 5 && !memcmp (tok, "curve", 5)) + { + char *curve_name; + + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + curve_name = xtrymalloc (toklen+1); + if (!curve_name) + { + err = gpg_error_from_syserror (); + goto leave; + } + + memcpy (curve_name, tok, toklen); + curve_name[toklen] = 0; + curve = openpgp_is_curve_supported (curve_name, NULL, NULL); + xfree (curve_name); + } + else if (tok && toklen == 5 && !memcmp (tok, "flags", 5)) + { + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + + if (tok) + { + if ((toklen == 5 && !memcmp (tok, "eddsa", 5)) + || (toklen == 9 && !memcmp (tok, "djb-tweak", 9))) + flag_djb_tweak = 1; + } + } + else if (tok && toklen == 1) + { + const unsigned char **buf2; + size_t *buf2len; + int native = flag_djb_tweak; + + switch (*tok) + { + case 'q': buf2 = &ecc_q; buf2len = &ecc_q_len; break; + case 'd': buf2 = &ecc_d; buf2len = &ecc_d_len; native = 0; break; + default: buf2 = NULL; buf2len = NULL; break; + } + if (buf2 && *buf2) + { + err = gpg_error (GPG_ERR_DUP_VALUE); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (tok && buf2) + { + if (!native) + /* Strip off leading zero bytes and save. */ + for (;toklen && !*tok; toklen--, tok++) + ; + + *buf2 = tok; + *buf2len = toklen; + } + } + /* Skip until end of list. */ + last_depth2 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth2) + ; + if (err) + goto leave; + } + /* Parse other attributes. */ + last_depth1 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth1) + { + if (tok) + { + err = gpg_error (GPG_ERR_UNKNOWN_SEXP); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (tok && toklen == 10 && !memcmp ("created-at", tok, toklen)) + { + if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen))) + goto leave; + if (tok) + { + for (created_at=0; toklen && *tok && *tok >= '0' && *tok <= '9'; + tok++, toklen--) + created_at = created_at*10 + (*tok - '0'); + } + } + /* Skip until end of list. */ + last_depth2 = depth; + while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) + && depth && depth >= last_depth2) + ; + if (err) + goto leave; + } + + + /* Check that we have all parameters and that they match the card + description. */ + if (!curve) + { + log_error (_("unsupported curve\n")); + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + if (!created_at) + { + log_error (_("creation timestamp missing\n")); + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + if (flag_djb_tweak && keyno != 1) + algo = PUBKEY_ALGO_EDDSA; + else if (keyno == 1) + algo = PUBKEY_ALGO_ECDH; + else + algo = PUBKEY_ALGO_ECDSA; + + oidstr = openpgp_curve_to_oid (curve, &n, NULL); + ecc_d_fixed_len = (n+7)/8; + err = openpgp_oid_from_str (oidstr, &oid); + if (err) + goto leave; + oidbuf = gcry_mpi_get_opaque (oid, &n); + if (!oidbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + oid_len = (n+7)/8; + + if (app->app_local->keyattr[keyno].key_type != KEY_TYPE_ECC + || app->app_local->keyattr[keyno].ecc.curve != curve + || (flag_djb_tweak != + (app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK))) + { + if (app->app_local->extcap.algo_attr_change) + { + unsigned char *keyattr; + + if (!oid_len) + { + err = gpg_error (GPG_ERR_INTERNAL); + goto leave; + } + keyattr = xtrymalloc (oid_len); + if (!keyattr) + { + err = gpg_error_from_syserror (); + goto leave; + } + keyattr[0] = algo; + memcpy (keyattr+1, oidbuf+1, oid_len-1); + err = change_keyattr (app, keyno, keyattr, oid_len, pincb, pincb_arg); + xfree (keyattr); + if (err) + goto leave; + } + else + { + log_error ("key attribute on card doesn't match\n"); + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + } + + if (opt.verbose) + log_info ("ECC private key size is %u bytes\n", (unsigned int)ecc_d_len); + + /* We need to remove the cached public key. */ + xfree (app->app_local->pk[keyno].key); + app->app_local->pk[keyno].key = NULL; + app->app_local->pk[keyno].keylen = 0; + app->app_local->pk[keyno].read_done = 0; + + if (app->app_local->extcap.is_v2) + { + /* Build the private key template as described in section 4.3.3.7 of + the OpenPGP card specs version 2.0. */ + unsigned char *template; + size_t template_len; + int exmode; + + err = build_ecc_privkey_template (app, keyno, + ecc_d, ecc_d_len, ecc_d_fixed_len, + ecc_q, ecc_q_len, + &template, &template_len); + if (err) + goto leave; + + /* Prepare for storing the key. */ + err = verify_chv3 (app, pincb, pincb_arg); + if (err) + { + xfree (template); + goto leave; + } + + /* Store the key. */ + if (app->app_local->cardcap.ext_lc_le && template_len > 254) + exmode = 1; /* Use extended length w/o a limit. */ + else if (app->app_local->cardcap.cmd_chaining && template_len > 254) + exmode = -254; + else + exmode = 0; + err = iso7816_put_data_odd (app->slot, exmode, 0x3fff, + template, template_len); + xfree (template); + } + else + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + + if (err) + { + log_error (_("failed to store the key: %s\n"), gpg_strerror (err)); + goto leave; + } + + err = store_fpr (app, keyno, created_at, fprbuf, algo, oidbuf, oid_len, + ecc_q, ecc_q_len, ecdh_params (curve), (size_t)4); + + leave: + gcry_mpi_release (oid); + return err; +} + +/* Handle the WRITEKEY command for OpenPGP. This function expects a + canonical encoded S-expression with the secret key in KEYDATA and + its length (for assertions) in KEYDATALEN. KEYID needs to be the + usual keyid which for OpenPGP is the string "OPENPGP.n" with + n=1,2,3. Bit 0 of FLAGS indicates whether an existing key shall + get overwritten. PINCB and PINCB_ARG are the usual arguments for + the pinentry callback. */ +static gpg_error_t +do_writekey (app_t app, ctrl_t ctrl, + const char *keyid, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *keydata, size_t keydatalen) +{ + gpg_error_t err; + int force = (flags & 1); + int keyno; + const unsigned char *buf, *tok; + size_t buflen, toklen; + int depth; + + (void)ctrl; + + if (!strcmp (keyid, "OPENPGP.1")) + keyno = 0; + else if (!strcmp (keyid, "OPENPGP.2")) + keyno = 1; + else if (!strcmp (keyid, "OPENPGP.3")) + keyno = 2; + else + return gpg_error (GPG_ERR_INV_ID); + + err = does_key_exist (app, keyno, 0, force); + if (err) + return err; + + + /* + Parse the S-expression + */ + buf = keydata; + buflen = keydatalen; + depth = 0; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen)) + { + if (!tok) + ; + else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen)) + log_info ("protected-private-key passed to writekey\n"); + else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen)) + log_info ("shadowed-private-key passed to writekey\n"); + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) + goto leave; + if (tok && toklen == 3 && memcmp ("rsa", tok, toklen) == 0) + err = rsa_writekey (app, pincb, pincb_arg, keyno, buf, buflen, depth); + else if (tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0) + err = ecc_writekey (app, pincb, pincb_arg, keyno, buf, buflen, depth); + else + { + err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); + goto leave; + } + + leave: + return err; +} + + + +/* Handle the GENKEY command. */ +static gpg_error_t +do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, const char *keytype, + unsigned int flags, time_t createtime, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + char numbuf[30]; + unsigned char *buffer = NULL; + const unsigned char *keydata; + size_t buflen, keydatalen; + u32 created_at; + int keyno = atoi (keynostr) - 1; + int force = (flags & 1); + time_t start_at; + int exmode = 0; + int le_value = 256; /* Use legacy value. */ + + (void)keytype; /* Ignored for OpenPGP cards. */ + + if (keyno < 0 || keyno > 2) + return gpg_error (GPG_ERR_INV_ID); + + /* We flush the cache to increase the traffic before a key + generation. This _might_ help a card to gather more entropy. */ + flush_cache (app); + + /* Obviously we need to remove the cached public key. */ + xfree (app->app_local->pk[keyno].key); + app->app_local->pk[keyno].key = NULL; + app->app_local->pk[keyno].keylen = 0; + app->app_local->pk[keyno].read_done = 0; + + /* Check whether a key already exists. */ + err = does_key_exist (app, keyno, 1, force); + if (err) + return err; + + if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA) + { + unsigned int keybits = app->app_local->keyattr[keyno].rsa.n_bits; + + /* Because we send the key parameter back via status lines we need + to put a limit on the max. allowed keysize. 2048 bit will + already lead to a 527 byte long status line and thus a 4096 bit + key would exceed the Assuan line length limit. */ + if (keybits > 4096) + return gpg_error (GPG_ERR_TOO_LARGE); + + if (app->app_local->cardcap.ext_lc_le && keybits > RSA_SMALL_SIZE_KEY + && app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA) + { + exmode = 1; /* Use extended length w/o a limit. */ + le_value = determine_rsa_response (app, keyno); + /* No need to check le_value because it comes from a 16 bit + value and thus can't create an overflow on a 32 bit + system. */ + } + } + + /* Prepare for key generation by verifying the Admin PIN. */ + err = verify_chv3 (app, pincb, pincb_arg); + if (err) + return err; + + + log_info (_("please wait while key is being generated ...\n")); + start_at = time (NULL); + err = iso7816_generate_keypair (app->slot, exmode, 0x80, 0, + (keyno == 0? "\xB6" : + keyno == 1? "\xB8" : "\xA4"), + 2, le_value, &buffer, &buflen); + if (err) + { + log_error (_("generating key failed\n")); + return gpg_error (GPG_ERR_CARD); + } + + { + int nsecs = (int)(time (NULL) - start_at); + log_info (ngettext("key generation completed (%d second)\n", + "key generation completed (%d seconds)\n", + nsecs), nsecs); + } + + keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen); + if (!keydata) + { + err = gpg_error (GPG_ERR_CARD); + log_error (_("response does not contain the public key data\n")); + goto leave; + } + + created_at = (u32)(createtime? createtime : gnupg_get_time ()); + sprintf (numbuf, "%u", created_at); + send_status_info (ctrl, "KEY-CREATED-AT", + numbuf, (size_t)strlen(numbuf), NULL, 0); + + err = read_public_key (app, ctrl, created_at, keyno, buffer, buflen); + leave: + xfree (buffer); + return err; +} + + +static unsigned long +convert_sig_counter_value (const unsigned char *value, size_t valuelen) +{ + unsigned long ul; + + 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; + } + return ul; +} + +static unsigned long +get_sig_counter (app_t app) +{ + void *relptr; + unsigned char *value; + size_t valuelen; + unsigned long ul; + + relptr = get_one_do (app, 0x0093, &value, &valuelen, NULL); + if (!relptr) + return 0; + ul = convert_sig_counter_value (value, valuelen); + xfree (relptr); + return ul; +} + +static gpg_error_t +compare_fingerprint (app_t app, int keyno, unsigned char *sha1fpr) +{ + const unsigned char *fpr; + unsigned char *buffer; + size_t buflen, n; + int rc, i; + + assert (keyno >= 0 && keyno <= 2); + + rc = get_cached_data (app, 0x006E, &buffer, &buflen, 0, 0); + if (rc) + { + log_error (_("error reading application data\n")); + return gpg_error (GPG_ERR_GENERAL); + } + fpr = find_tlv (buffer, buflen, 0x00C5, &n); + if (!fpr || n < 60) + { + xfree (buffer); + log_error (_("error reading fingerprint DO\n")); + return gpg_error (GPG_ERR_GENERAL); + } + fpr += keyno*20; + for (i=0; i < 20; i++) + if (sha1fpr[i] != fpr[i]) + { + xfree (buffer); + log_info (_("fingerprint on card does not match requested one\n")); + return gpg_error (GPG_ERR_WRONG_SECKEY); + } + xfree (buffer); + return 0; +} + + +/* If a fingerprint has been specified check it against the one on the + card. This allows for a meaningful error message in case the key + on the card has been replaced but the shadow information known to + gpg has not been updated. If there is no fingerprint we assume + that this is okay. */ +static gpg_error_t +check_against_given_fingerprint (app_t app, const char *fpr, int key) +{ + unsigned char tmp[20]; + const char *s; + int n; + + 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[n] = xtoi_2 (s); + return compare_fingerprint (app, key-1, tmp); +} + + +/* Check KEYIDSTR, if it's valid. + When KEYNO is 0, it means it's for PIN check. + Otherwise, KEYNO corresponds to the slot (signing, decipher and auth). + KEYIDSTR is either: + (1) Serial number + (2) Serial number "/" fingerprint + (3) Serial number "[CHV3]" + (4) keygrip + + When KEYNO is 0 and KEYIDSTR is for a keygrip, the keygrip should + be to be compared is the first one (keygrip for signing). + When KEYNO is 1, KEYIDSTR is for a keygrip, and R_USE_AUTH is not + NULL, OpenPGP.1 is first tested and then OpenPGP.3. In the latter + case 1 is stored at R_USE_AUTH + */ +static int +check_keyidstr (app_t app, const char *keyidstr, int keyno, int *r_use_auth) +{ + int rc; + const char *s; + int n; + const char *fpr = NULL; + int i; + + if (r_use_auth) + *r_use_auth = 0; + + /* Make sure we have load the public keys. */ + for (i = 0; i < 3; i++) + get_public_key (app, i); + + if (strlen (keyidstr) < 32) + return gpg_error (GPG_ERR_INV_ID); + else + { + char *serial; + + for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + ; + + /* Check if it's a keygrip */ + if (n == 40) + { + const unsigned char *keygrip_str; + + keygrip_str = app->app_local->pk[keyno?keyno-1:0].keygrip_str; + if (!strncmp (keygrip_str, keyidstr, 40)) + return 0; + else if (keyno == 1 && r_use_auth + && !strncmp (app->app_local->pk[2].keygrip_str, + keyidstr, 40)) + { + *r_use_auth = 1; + return 0; + } + else + return gpg_error (GPG_ERR_INV_ID); + } + + if (n != 32 || strncmp (keyidstr, "D27600012401", 12)) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* no fingerprint given: we allow this for now. */ + else if (*s == '/') + fpr = s + 1; + + serial = app_get_serialno (app); + if (strncmp (serial, keyidstr, 32)) + { + xfree (serial); + return gpg_error (GPG_ERR_WRONG_CARD); + } + + xfree (serial); + } + + /* 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. */ + rc = (fpr&&keyno)? check_against_given_fingerprint (app, fpr, keyno) : 0; + + return rc; +} + + +/* 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 function 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). + + As a special feature a KEYIDSTR of "OPENPGP.3" redirects the + operation to the auth command. +*/ +static gpg_error_t +do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + 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 }; + static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */ + { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, + 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; + static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */ + { 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, + 0x1C }; + static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */ + { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20 }; + static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */ + { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, + 0x00, 0x04, 0x30 }; + static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */ + { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40 }; + int rc; + unsigned char data[19+64]; + size_t datalen; + unsigned long sigcount; + int use_auth = 0; + int exmode, le_value; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Strip off known prefixes. */ +#define X(a,b,c,d) \ + if (hashalgo == GCRY_MD_ ## a \ + && (d) \ + && indatalen == sizeof b ## _prefix + (c) \ + && !memcmp (indata, b ## _prefix, sizeof b ## _prefix)) \ + { \ + indata = (const char*)indata + sizeof b ## _prefix; \ + indatalen -= sizeof b ## _prefix; \ + } + + if (indatalen == 20) + ; /* Assume a plain SHA-1 or RMD160 digest has been given. */ + else X(SHA1, sha1, 20, 1) + else X(RMD160, rmd160, 20, 1) + else X(SHA224, sha224, 28, app->app_local->extcap.is_v2) + else X(SHA256, sha256, 32, app->app_local->extcap.is_v2) + else X(SHA384, sha384, 48, app->app_local->extcap.is_v2) + else X(SHA512, sha512, 64, app->app_local->extcap.is_v2) + else if ((indatalen == 28 || indatalen == 32 + || indatalen == 48 || indatalen ==64) + && app->app_local->extcap.is_v2) + ; /* Assume a plain SHA-3 digest has been given. */ + else + { + log_error (_("card does not support digest algorithm %s\n"), + gcry_md_algo_name (hashalgo)); + /* Or the supplied digest length does not match an algorithm. */ + return gpg_error (GPG_ERR_INV_VALUE); + } +#undef X + + /* Check whether an OpenPGP card of any version has been requested. */ + if (!strcmp (keyidstr, "OPENPGP.1")) + ; + else if (!strcmp (keyidstr, "OPENPGP.3")) + use_auth = 1; + else + { + rc = check_keyidstr (app, keyidstr, 1, &use_auth); + if (rc) + return rc; + } + + /* Concatenate prefix and digest. */ +#define X(a,b,d) \ + if (hashalgo == GCRY_MD_ ## a && (d) ) \ + { \ + datalen = sizeof b ## _prefix + indatalen; \ + assert (datalen <= sizeof data); \ + memcpy (data, b ## _prefix, sizeof b ## _prefix); \ + memcpy (data + sizeof b ## _prefix, indata, indatalen); \ + } + + if (use_auth + || app->app_local->keyattr[use_auth? 2: 0].key_type == KEY_TYPE_RSA) + { + X(SHA1, sha1, 1) + else X(RMD160, rmd160, 1) + else X(SHA224, sha224, app->app_local->extcap.is_v2) + else X(SHA256, sha256, app->app_local->extcap.is_v2) + else X(SHA384, sha384, app->app_local->extcap.is_v2) + else X(SHA512, sha512, app->app_local->extcap.is_v2) + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + } + else + { + datalen = indatalen; + memcpy (data, indata, indatalen); + } +#undef X + + /* Redirect to the AUTH command if asked to. */ + if (use_auth) + { + return do_auth (app, ctrl, "OPENPGP.3", pincb, pincb_arg, + data, datalen, + outdata, outdatalen); + } + + /* Show the number of signature done using this key. */ + sigcount = get_sig_counter (app); + log_info (_("signatures created so far: %lu\n"), sigcount); + + /* Check CHV if needed. */ + if (!app->did_chv1 || app->force_chv1) + { + char *pinvalue; + int pinlen; + + rc = verify_a_chv (app, pincb, pincb_arg, 1, sigcount, &pinvalue, &pinlen); + if (rc) + return rc; + + app->did_chv1 = 1; + + /* For cards with versions < 2 we want to keep CHV1 and CHV2 in + sync, thus we verify CHV2 here using the given PIN. Cards + with version2 to not have the need for a separate CHV2 and + internally use just one. Obviously we can't do that if the + pinpad has been used. */ + if (!app->did_chv2 && pinvalue && !app->app_local->extcap.is_v2) + { + rc = iso7816_verify (app->slot, 0x82, pinvalue, pinlen); + if (gpg_err_code (rc) == GPG_ERR_BAD_PIN) + rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED); + if (rc) + { + log_error (_("verify CHV%d failed: %s\n"), 2, gpg_strerror (rc)); + xfree (pinvalue); + flush_cache_after_error (app); + return rc; + } + app->did_chv2 = 1; + } + xfree (pinvalue); + } + + + if (app->app_local->cardcap.ext_lc_le + && app->app_local->keyattr[0].key_type == KEY_TYPE_RSA + && app->app_local->keyattr[0].rsa.n_bits > RSA_SMALL_SIZE_OP) + { + exmode = 1; /* Use extended length. */ + le_value = app->app_local->keyattr[0].rsa.n_bits / 8; + } + else + { + exmode = 0; + le_value = 0; + } + rc = iso7816_compute_ds (app->slot, exmode, data, datalen, le_value, + outdata, outdatalen); + if (!rc && app->force_chv1) + app->did_chv1 = 0; + + 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. Optionally the id OPENPGP.3 may + be given. + + Note that this function 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 gpg_error_t +do_auth (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + + (void)ctrl; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + if (app->app_local->keyattr[2].key_type == KEY_TYPE_RSA + && indatalen > 101) /* For a 2048 bit key. */ + return gpg_error (GPG_ERR_INV_VALUE); + + if (app->app_local->keyattr[2].key_type == KEY_TYPE_ECC) + { + if (!(app->app_local->keyattr[2].ecc.flags & ECC_FLAG_DJB_TWEAK) + && (indatalen == 51 || indatalen == 67 || indatalen == 83)) + { + const char *p = (const char *)indata + 19; + indata = p; + indatalen -= 19; + } + else + { + const char *p = (const char *)indata + 15; + indata = p; + indatalen -= 15; + } + } + + /* Check whether an OpenPGP card of any version has been requested. */ + if (!ascii_strcasecmp (keyidstr, "OPENPGP.3")) + ; + else + { + rc = check_keyidstr (app, keyidstr, 3, NULL); + if (rc) + return rc; + } + + rc = verify_chv2 (app, pincb, pincb_arg); + if (!rc) + { + int exmode, le_value; + + if (app->app_local->cardcap.ext_lc_le + && app->app_local->keyattr[2].key_type == KEY_TYPE_RSA + && app->app_local->keyattr[2].rsa.n_bits > RSA_SMALL_SIZE_OP) + { + exmode = 1; /* Use extended length. */ + le_value = app->app_local->keyattr[2].rsa.n_bits / 8; + } + else if (app->app_local->cardcap.cmd_chaining && indatalen > 254) + { + exmode = -254; /* Command chaining with max. 254 bytes. */ + le_value = 0; + } + else if (indatalen > 255) + { + if (!app->app_local->cardcap.ext_lc_le) + return gpg_error (GPG_ERR_TOO_LARGE); + + exmode = 1; + le_value = 0; + } + else + { + exmode = 0; + le_value = 0; + } + rc = iso7816_internal_authenticate (app->slot, exmode, + indata, indatalen, le_value, + outdata, outdatalen); + } + return rc; +} + + +static gpg_error_t +do_decipher (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen, + unsigned int *r_info) +{ + int rc; + int n; + int exmode, le_value; + unsigned char *fixbuf = NULL; + int padind = 0; + int fixuplen = 0; + + (void)ctrl; + + if (!keyidstr || !*keyidstr || !indatalen) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check whether an OpenPGP card of any version has been requested. */ + if (!ascii_strcasecmp (keyidstr, "OPENPGP.2")) + ; + else + { + rc = check_keyidstr (app, keyidstr, 2, NULL); + if (rc) + return rc; + } + + rc = verify_chv2 (app, pincb, pincb_arg); + if (rc) + return rc; + + if ((indatalen == 16 + 1 || indatalen == 32 + 1) + && ((char *)indata)[0] == 0x02) + { + /* PSO:DECIPHER with symmetric key. */ + padind = -1; + } + else if (app->app_local->keyattr[1].key_type == KEY_TYPE_RSA) + { + /* We might encounter a couple of leading zeroes in the + cryptogram. Due to internal use of MPIs these leading zeroes + are stripped. However the OpenPGP card expects exactly 128 + bytes for the cryptogram (for a 1k key). Thus we need to fix + it up. We do this for up to 16 leading zero bytes; a + cryptogram with more than this is with a very high + probability anyway broken. If a signed conversion was used + we may also encounter one leading zero followed by the correct + length. We fix that as well. */ + if (indatalen >= (128-16) && indatalen < 128) /* 1024 bit key. */ + fixuplen = 128 - indatalen; + else if (indatalen >= (192-16) && indatalen < 192) /* 1536 bit key. */ + fixuplen = 192 - indatalen; + else if (indatalen >= (256-16) && indatalen < 256) /* 2048 bit key. */ + fixuplen = 256 - indatalen; + else if (indatalen >= (384-16) && indatalen < 384) /* 3072 bit key. */ + fixuplen = 384 - indatalen; + else if (indatalen >= (512-16) && indatalen < 512) /* 4096 bit key. */ + fixuplen = 512 - indatalen; + else if (!*(const char *)indata && (indatalen == 129 + || indatalen == 193 + || indatalen == 257 + || indatalen == 385 + || indatalen == 513)) + fixuplen = -1; + else + fixuplen = 0; + + if (fixuplen > 0) + { + /* While we have to prepend stuff anyway, we can also + include the padding byte here so that iso1816_decipher + does not need to do another data mangling. */ + fixuplen++; + + fixbuf = xtrymalloc (fixuplen + indatalen); + if (!fixbuf) + return gpg_error_from_syserror (); + + memset (fixbuf, 0, fixuplen); + memcpy (fixbuf+fixuplen, indata, indatalen); + indata = fixbuf; + indatalen = fixuplen + indatalen; + padind = -1; /* Already padded. */ + } + else if (fixuplen < 0) + { + /* We use the extra leading zero as the padding byte. */ + padind = -1; + } + } + else if (app->app_local->keyattr[1].key_type == KEY_TYPE_ECC) + { + int old_format_len = 0; + + if ((app->app_local->keyattr[1].ecc.flags & ECC_FLAG_DJB_TWEAK)) + { + if (indatalen > 32 && (indatalen % 2)) + { /* + * Skip the prefix. It may be 0x40 (in new format), or MPI + * head of 0x00 (in old format). + */ + indata = (const char *)indata + 1; + indatalen--; + } + else if (indatalen < 32) + { /* + * Old format trancated by MPI handling. + */ + old_format_len = indatalen; + indatalen = 32; + } + } + + n = 0; + if (indatalen < 128) + fixuplen = 7; + else + fixuplen = 10; + + fixbuf = xtrymalloc (fixuplen + indatalen); + if (!fixbuf) + return gpg_error_from_syserror (); + + /* Build 'Cipher DO' */ + fixbuf[n++] = '\xa6'; + if (indatalen < 128) + fixbuf[n++] = (char)(indatalen+5); + else + { + fixbuf[n++] = 0x81; + fixbuf[n++] = (char)(indatalen+7); + } + fixbuf[n++] = '\x7f'; + fixbuf[n++] = '\x49'; + if (indatalen < 128) + fixbuf[n++] = (char)(indatalen+2); + else + { + fixbuf[n++] = 0x81; + fixbuf[n++] = (char)(indatalen+3); + } + fixbuf[n++] = '\x86'; + if (indatalen < 128) + fixbuf[n++] = (char)indatalen; + else + { + fixbuf[n++] = 0x81; + fixbuf[n++] = (char)indatalen; + } + + if (old_format_len) + { + memset (fixbuf+fixuplen, 0, 32 - old_format_len); + memcpy (fixbuf+fixuplen + 32 - old_format_len, + indata, old_format_len); + } + else + { + memcpy (fixbuf+fixuplen, indata, indatalen); + } + indata = fixbuf; + indatalen = fixuplen + indatalen; + + padind = -1; + } + else + return gpg_error (GPG_ERR_INV_VALUE); + + if (app->app_local->cardcap.ext_lc_le + && (indatalen > 254 + || (app->app_local->keyattr[1].key_type == KEY_TYPE_RSA + && app->app_local->keyattr[1].rsa.n_bits > RSA_SMALL_SIZE_OP))) + { + exmode = 1; /* Extended length w/o a limit. */ + le_value = app->app_local->keyattr[1].rsa.n_bits / 8; + } + else if (app->app_local->cardcap.cmd_chaining && indatalen > 254) + { + exmode = -254; /* Command chaining with max. 254 bytes. */ + le_value = 0; + } + else + exmode = le_value = 0; + + rc = iso7816_decipher (app->slot, exmode, + indata, indatalen, le_value, padind, + outdata, outdatalen); + xfree (fixbuf); + if (app->app_local->keyattr[1].key_type == KEY_TYPE_ECC) + { + unsigned char prefix = 0; + + if (app->app_local->keyattr[1].ecc.flags & ECC_FLAG_DJB_TWEAK) + prefix = 0x40; + else if ((*outdatalen % 2) == 0) /* No 0x04 -> x-coordinate only */ + prefix = 0x41; + + if (prefix) + { /* Add the prefix */ + fixbuf = xtrymalloc (*outdatalen + 1); + if (!fixbuf) + { + xfree (*outdata); + return gpg_error_from_syserror (); + } + fixbuf[0] = prefix; + memcpy (fixbuf+1, *outdata, *outdatalen); + xfree (*outdata); + *outdata = fixbuf; + *outdatalen = *outdatalen + 1; + } + } + + if (gpg_err_code (rc) == GPG_ERR_CARD /* actual SW is 0x640a */ + && app->app_local->manufacturer == 5 + && app->appversion == 0x0200) + log_info ("NOTE: Cards with manufacturer id 5 and s/n <= 346 (0x15a)" + " do not work with encryption keys > 2048 bits\n"); + + *r_info |= APP_DECIPHER_INFO_NOPAD; + + return rc; +} + + +/* Perform a simple verify operation for CHV1 and CHV2, so that + further operations won't ask for CHV2 and it is possible to do a + cheap check on the PIN: If there is something wrong with the PIN + entry system, only the regular CHV will get blocked and not the + dangerous CHV3. KEYIDSTR is the usual card's serial number; an + optional fingerprint part will be ignored. + + There is a special mode if the keyidstr is "<serialno>[CHV3]" with + the "[CHV3]" being a literal string: The Admin Pin is checked if + and only if the retry counter is still at 3. */ +static gpg_error_t +do_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc; + int admin_pin = 0; + + (void)ctrl; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + + rc = check_keyidstr (app, keyidstr, 0, NULL); + if (rc) + return rc; + + if ((strlen (keyidstr) >= 32+6 && !strcmp (keyidstr+32, "[CHV3]")) + || (strlen (keyidstr) >= 40+6 && !strcmp (keyidstr+40, "[CHV3]"))) + admin_pin = 1; + + /* Yes, there is a race conditions: The user might pull the card + right here and we won't notice that. However this is not a + problem and the check above is merely for a graceful failure + between operations. */ + + if (admin_pin) + { + void *relptr; + unsigned char *value; + size_t valuelen; + int count; + + relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL); + if (!relptr || valuelen < 7) + { + log_error (_("error retrieving CHV status from card\n")); + xfree (relptr); + return gpg_error (GPG_ERR_CARD); + } + count = value[6]; + xfree (relptr); + + if (!count) + { + log_info (_("card is permanently locked!\n")); + return gpg_error (GPG_ERR_BAD_PIN); + } + else if (count < 3) + { + log_info (_("verification of Admin PIN is currently prohibited " + "through this command\n")); + return gpg_error (GPG_ERR_GENERAL); + } + + app->did_chv3 = 0; /* Force verification. */ + return verify_chv3 (app, pincb, pincb_arg); + } + else + return verify_chv2 (app, pincb, pincb_arg); +} + + +/* Show information about card capabilities. */ +static void +show_caps (struct app_local_s *s) +{ + log_info ("Version-2+ .....: %s\n", s->extcap.is_v2? "yes":"no"); + log_info ("Extcap-v3 ......: %s\n", s->extcap.extcap_v3? "yes":"no"); + log_info ("Button .........: %s\n", s->extcap.has_button? "yes":"no"); + + log_info ("SM-Support .....: %s", s->extcap.sm_supported? "yes":"no"); + if (s->extcap.sm_supported) + log_printf (" (%s)", s->extcap.sm_algo==2? "3DES": + (s->extcap.sm_algo==2? "AES-128" : "AES-256")); + log_info ("Get-Challenge ..: %s", s->extcap.get_challenge? "yes":"no"); + if (s->extcap.get_challenge) + log_printf (" (%u bytes max)", s->extcap.max_get_challenge); + log_info ("Key-Import .....: %s\n", s->extcap.key_import? "yes":"no"); + log_info ("Change-Force-PW1: %s\n", s->extcap.change_force_chv? "yes":"no"); + log_info ("Private-DOs ....: %s\n", s->extcap.private_dos? "yes":"no"); + log_info ("Algo-Attr-Change: %s\n", s->extcap.algo_attr_change? "yes":"no"); + log_info ("Symmetric Crypto: %s\n", s->extcap.has_decrypt? "yes":"no"); + log_info ("KDF-Support ....: %s\n", s->extcap.kdf_do? "yes":"no"); + log_info ("Max-Cert3-Len ..: %u\n", s->extcap.max_certlen_3); + if (s->extcap.extcap_v3) + { + log_info ("PIN-Block-2 ....: %s\n", s->extcap.pin_blk2? "yes":"no"); + log_info ("MSE-Support ....: %s\n", s->extcap.mse? "yes":"no"); + log_info ("Max-Special-DOs : %u\n", s->extcap.max_special_do); + } + log_info ("Cmd-Chaining ...: %s\n", s->cardcap.cmd_chaining?"yes":"no"); + log_info ("Ext-Lc-Le ......: %s\n", s->cardcap.ext_lc_le?"yes":"no"); + log_info ("Status-Indicator: %02X\n", s->status_indicator); + + log_info ("GnuPG-No-Sync ..: %s\n", s->flags.no_sync? "yes":"no"); + log_info ("GnuPG-Def-PW2 ..: %s\n", s->flags.def_chv2? "yes":"no"); +} + + +/* Parse the historical bytes in BUFFER of BUFLEN and store them in + APPLOC. */ +static void +parse_historical (struct app_local_s *apploc, + const unsigned char * buffer, size_t buflen) +{ + /* Example buffer: 00 31 C5 73 C0 01 80 00 90 00 */ + if (buflen < 4) + { + log_error ("warning: historical bytes are too short\n"); + return; /* Too short. */ + } + if (*buffer) + { + log_error ("warning: bad category indicator in historical bytes\n"); + return; + } + + /* Skip category indicator. */ + buffer++; + buflen--; + + /* Get the status indicator. */ + apploc->status_indicator = buffer[buflen-3]; + buflen -= 3; + + /* Parse the compact TLV. */ + while (buflen) + { + unsigned int tag = (*buffer & 0xf0) >> 4; + unsigned int len = (*buffer & 0x0f); + if (len+1 > buflen) + { + log_error ("warning: bad Compact-TLV in historical bytes\n"); + return; /* Error. */ + } + buffer++; + buflen--; + if (tag == 7 && len == 3) + { + /* Card capabilities. */ + apploc->cardcap.cmd_chaining = !!(buffer[2] & 0x80); + apploc->cardcap.ext_lc_le = !!(buffer[2] & 0x40); + } + buffer += len; + buflen -= len; + } +} + + +/* + * Check if the OID in an DER encoding is available by GnuPG/libgcrypt, + * and return the curve name. Return NULL if not available. + * The constant string is not allocated dynamically, never free it. + */ +static const char * +ecc_curve (unsigned char *buf, size_t buflen) +{ + gcry_mpi_t oid; + char *oidstr; + const char *result; + unsigned char *oidbuf; + + oidbuf = xtrymalloc (buflen + 1); + if (!oidbuf) + return NULL; + + memcpy (oidbuf+1, buf, buflen); + oidbuf[0] = buflen; + oid = gcry_mpi_set_opaque (NULL, oidbuf, (buflen+1) * 8); + if (!oid) + { + xfree (oidbuf); + return NULL; + } + + oidstr = openpgp_oid_to_str (oid); + gcry_mpi_release (oid); + if (!oidstr) + return NULL; + + result = openpgp_oid_to_curve (oidstr, 1); + xfree (oidstr); + return result; +} + + +/* Parse and optionally show the algorithm attributes for KEYNO. + KEYNO must be in the range 0..2. */ +static gpg_error_t +parse_algorithm_attribute (app_t app, int keyno) +{ + unsigned char *buffer; + size_t buflen; + void *relptr; + const char desc[3][5] = {"sign", "encr", "auth"}; + gpg_error_t err = 0; + + assert (keyno >=0 && keyno <= 2); + + app->app_local->keyattr[keyno].key_type = KEY_TYPE_RSA; + app->app_local->keyattr[keyno].rsa.n_bits = 0; + + relptr = get_one_do (app, 0xC1+keyno, &buffer, &buflen, NULL); + if (!relptr) + { + log_error ("error reading DO 0x%02X\n", 0xc1+keyno); + return gpg_error (GPG_ERR_CARD); + } + if (buflen < 1) + { + log_error ("error reading DO 0x%02X\n", 0xc1+keyno); + xfree (relptr); + return gpg_error (GPG_ERR_CARD); + } + + if (opt.verbose) + log_info ("Key-Attr-%s ..: ", desc[keyno]); + if (*buffer == PUBKEY_ALGO_RSA && (buflen == 5 || buflen == 6)) + { + app->app_local->keyattr[keyno].rsa.n_bits = (buffer[1]<<8 | buffer[2]); + app->app_local->keyattr[keyno].rsa.e_bits = (buffer[3]<<8 | buffer[4]); + app->app_local->keyattr[keyno].rsa.format = 0; + if (buflen < 6) + app->app_local->keyattr[keyno].rsa.format = RSA_STD; + else + app->app_local->keyattr[keyno].rsa.format = (buffer[5] == 0? RSA_STD : + buffer[5] == 1? RSA_STD_N : + buffer[5] == 2? RSA_CRT : + buffer[5] == 3? RSA_CRT_N : + RSA_UNKNOWN_FMT); + + if (opt.verbose) + log_printf + ("RSA, n=%u, e=%u, fmt=%s\n", + app->app_local->keyattr[keyno].rsa.n_bits, + app->app_local->keyattr[keyno].rsa.e_bits, + app->app_local->keyattr[keyno].rsa.format == RSA_STD? "std" : + app->app_local->keyattr[keyno].rsa.format == RSA_STD_N?"std+n": + app->app_local->keyattr[keyno].rsa.format == RSA_CRT? "crt" : + app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N?"crt+n":"?"); + } + else if (*buffer == PUBKEY_ALGO_ECDH || *buffer == PUBKEY_ALGO_ECDSA + || *buffer == PUBKEY_ALGO_EDDSA) + { + const char *curve; + int oidlen = buflen - 1; + + app->app_local->keyattr[keyno].ecc.flags = 0; + + if (APP_CARD(app)->cardtype == CARDTYPE_YUBIKEY) + { + /* Yubikey implementations vary. + * Firmware version 5.2 returns "pubkey required"-byte with + * 0x00, but after removal and second time insertion, it + * returns bogus value there. + * Firmware version 5.4 returns none. + */ + curve = ecc_curve (buffer + 1, oidlen); + if (!curve) + curve = ecc_curve (buffer + 1, oidlen - 1); + } + else + { + if (buffer[buflen-1] == 0x00 || buffer[buflen-1] == 0xff) + { /* Found "pubkey required"-byte for private key template. */ + oidlen--; + if (buffer[buflen-1] == 0xff) + app->app_local->keyattr[keyno].ecc.flags |= ECC_FLAG_PUBKEY; + } + curve = ecc_curve (buffer + 1, oidlen); + } + + if (!curve) + { + log_printhex (buffer+1, buflen-1, "Curve with OID not supported: "); + err = gpg_error (GPG_ERR_CARD); + } + else + { + app->app_local->keyattr[keyno].key_type = KEY_TYPE_ECC; + app->app_local->keyattr[keyno].ecc.curve = curve; + if (*buffer == PUBKEY_ALGO_EDDSA + || (*buffer == PUBKEY_ALGO_ECDH + && !strcmp (app->app_local->keyattr[keyno].ecc.curve, + "Curve25519"))) + app->app_local->keyattr[keyno].ecc.flags |= ECC_FLAG_DJB_TWEAK; + if (opt.verbose) + log_printf + ("ECC, curve=%s%s\n", app->app_local->keyattr[keyno].ecc.curve, + !(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)? + "": keyno==1? " (djb-tweak)": " (eddsa)"); + } + } + else if (opt.verbose) + log_printhex (buffer, buflen, ""); + + xfree (relptr); + return err; +} + +/* Select the OpenPGP application on the card in SLOT. This function + must be used before any other OpenPGP application functions. */ +gpg_error_t +app_select_openpgp (app_t app) +{ + static char const aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 }; + int slot = app->slot; + gpg_error_t err; + unsigned char *buffer; + size_t buflen; + void *relptr; + + /* Note that the card can't cope with P2=0xCO, thus we need to pass a + special flag value. */ + err = iso7816_select_application (slot, aid, sizeof aid, 0x0001); + if (!err) + { + unsigned int manufacturer; + + app->apptype = APPTYPE_OPENPGP; + + app->did_chv1 = 0; + app->did_chv2 = 0; + app->did_chv3 = 0; + app->app_local = NULL; + + /* The OpenPGP card returns the serial number as part of the + AID; because we prefer to use OpenPGP serial numbers, we + replace a possibly already set one from a EF.GDO with this + one. Note, that for current OpenPGP cards, no EF.GDO exists + and thus it won't matter at all. */ + err = iso7816_get_data (slot, 0, 0x004F, &buffer, &buflen); + if (err) + goto leave; + if (opt.verbose) + { + log_info ("AID: "); + log_printhex (buffer, buflen, ""); + } + + app->appversion = buffer[6] << 8; + app->appversion |= buffer[7]; + manufacturer = (buffer[8]<<8 | buffer[9]); + + xfree (app->serialno); + app->serialno = buffer; + app->serialnolen = buflen; + buffer = NULL; + app->app_local = xtrycalloc (1, sizeof *app->app_local); + if (!app->app_local) + { + err = gpg_error_from_syserror (); + goto leave; + } + + app->app_local->manufacturer = manufacturer; + + if (app->appversion >= 0x0200) + app->app_local->extcap.is_v2 = 1; + + if (app->appversion >= 0x0300) + app->app_local->extcap.extcap_v3 = 1; + + /* Read the historical bytes. */ + relptr = get_one_do (app, 0x5f52, &buffer, &buflen, NULL); + if (relptr) + { + if (opt.verbose) + { + log_info ("Historical Bytes: "); + log_printhex (buffer, buflen, ""); + } + parse_historical (app->app_local, buffer, buflen); + xfree (relptr); + } + + /* Read the force-chv1 flag. */ + relptr = get_one_do (app, 0x00C4, &buffer, &buflen, NULL); + if (!relptr) + { + log_error (_("can't access %s - invalid OpenPGP card?\n"), + "CHV Status Bytes"); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + app->force_chv1 = (buflen && *buffer == 0); + xfree (relptr); + + /* Read the extended capabilities. */ + relptr = get_one_do (app, 0x00C0, &buffer, &buflen, NULL); + if (!relptr) + { + log_error (_("can't access %s - invalid OpenPGP card?\n"), + "Extended Capability Flags" ); + err = gpg_error (GPG_ERR_CARD); + goto leave; + } + if (buflen) + { + app->app_local->extcap.sm_supported = !!(*buffer & 0x80); + app->app_local->extcap.get_challenge = !!(*buffer & 0x40); + app->app_local->extcap.key_import = !!(*buffer & 0x20); + app->app_local->extcap.change_force_chv = !!(*buffer & 0x10); + app->app_local->extcap.private_dos = !!(*buffer & 0x08); + app->app_local->extcap.algo_attr_change = !!(*buffer & 0x04); + app->app_local->extcap.has_decrypt = !!(*buffer & 0x02); + app->app_local->extcap.kdf_do = !!(*buffer & 0x01); + } + if (buflen >= 10) + { + /* Available with cards of v2 or later. */ + app->app_local->extcap.sm_algo = buffer[1]; + app->app_local->extcap.max_get_challenge + = (buffer[2] << 8 | buffer[3]); + app->app_local->extcap.max_certlen_3 = (buffer[4] << 8 | buffer[5]); + + /* Interpretation is different between v2 and v3, unfortunately. */ + if (app->app_local->extcap.extcap_v3) + { + app->app_local->extcap.max_special_do + = (buffer[6] << 8 | buffer[7]); + app->app_local->extcap.pin_blk2 = !!(buffer[8] & 0x01); + app->app_local->extcap.mse= !!(buffer[9] & 0x01); + } + } + xfree (relptr); + + /* Some of the first cards accidentally don't set the + CHANGE_FORCE_CHV bit but allow it anyway. */ + if (app->appversion <= 0x0100 && manufacturer == 1) + app->app_local->extcap.change_force_chv = 1; + + /* Check optional DO of "General Feature Management" for button. */ + relptr = get_one_do (app, 0x7f74, &buffer, &buflen, NULL); + if (relptr) + /* It must be: 03 81 01 20 */ + app->app_local->extcap.has_button = 1; + + parse_login_data (app); + + if (opt.verbose) + show_caps (app->app_local); + + err = parse_algorithm_attribute (app, 0); + if (!err) + err = parse_algorithm_attribute (app, 1); + if (!err) + err = parse_algorithm_attribute (app, 2); + if (err) + goto leave; + + if (opt.verbose > 1) + dump_all_do (slot); + + app->fnc.deinit = do_deinit; + app->fnc.learn_status = do_learn_status; + app->fnc.readcert = do_readcert; + app->fnc.readkey = do_readkey; + app->fnc.getattr = do_getattr; + app->fnc.setattr = do_setattr; + app->fnc.writecert = do_writecert; + app->fnc.writekey = do_writekey; + 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; + app->fnc.check_pin = do_check_pin; + } + +leave: + if (err) + do_deinit (app); + return err; +} diff --git a/scd/app-p15.c b/scd/app-p15.c new file mode 100644 index 0000000..2884e0d --- /dev/null +++ b/scd/app-p15.c @@ -0,0 +1,6290 @@ +/* app-p15.c - The pkcs#15 card application. + * Copyright (C) 2005 Free Software Foundation, Inc. + * Copyright (C) 2020, 2021 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/* Information pertaining to the BELPIC developer card samples: + + Unblock PUK: "222222111111" + Reset PIN: "333333111111") + + e.g. the APDUs 00:20:00:02:08:2C:33:33:33:11:11:11:FF + and 00:24:01:01:08:24:12:34:FF:FF:FF:FF:FF + should change the PIN into 1234. +*/ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "scdaemon.h" + +#include "iso7816.h" +#include "../common/i18n.h" +#include "../common/tlv.h" +#include "../common/host2net.h" +#include "../common/openpgpdefs.h" +#include "apdu.h" /* fixme: we should move the card detection to a + separate file */ + + +static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3"; +static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8"; +static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9"; +static const char oid_kp_ms_documentSigning[] = "1.3.6.1.4.1.311.10.3.12"; +static const char oid_kp_ms_old_documentSigning[] = "1.3.6.1.4.1.311.3.10.3.12"; + +static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4"; + +static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1"; +static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2"; +static const char oid_kp_ms_smartcardLogon[] = "1.3.6.1.4.1.311.20.2.2"; + +static const char oid_kp_anyExtendedKeyUsage[] = "2.5.29.37.0"; + +static const char oid_kp_gpgUsageCert[] = "1.3.6.1.4.1.11591.2.6.1"; +static const char oid_kp_gpgUsageSign[] = "1.3.6.1.4.1.11591.2.6.2"; +static const char oid_kp_gpgUsageEncr[] = "1.3.6.1.4.1.11591.2.6.3"; +static const char oid_kp_gpgUsageAuth[] = "1.3.6.1.4.1.11591.2.6.4"; + +/* Types of cards we know and which needs special treatment. */ +typedef enum + { + CARD_TYPE_UNKNOWN, + CARD_TYPE_TCOS, + CARD_TYPE_MICARDO, + CARD_TYPE_CARDOS_50, + CARD_TYPE_CARDOS_53, + CARD_TYPE_AET, /* A.E.T. Europe JCOP card. */ + CARD_TYPE_BELPIC /* Belgian eID card specs. */ + } +card_type_t; + +/* The OS of card as specified by card_type_t is not always + * sufficient. Thus we also distinguish the actual product build upon + * the given OS. */ +typedef enum + { + CARD_PRODUCT_UNKNOWN, + CARD_PRODUCT_RSCS, /* Rohde&Schwarz Cybersecurity */ + CARD_PRODUCT_DTRUST, /* D-Trust GmbH (bundesdruckerei.de) */ + CARD_PRODUCT_GENUA /* GeNUA mbH */ + } +card_product_t; + + +/* A list card types with ATRs noticed with these cards. */ +#define X(a) ((unsigned char const *)(a)) +static struct +{ + size_t atrlen; + unsigned char const *atr; + card_type_t type; +} card_atr_list[] = { + { 19, X("\x3B\xBA\x13\x00\x81\x31\x86\x5D\x00\x64\x05\x0A\x02\x01\x31\x80" + "\x90\x00\x8B"), + CARD_TYPE_TCOS }, /* SLE44 */ + { 19, X("\x3B\xBA\x14\x00\x81\x31\x86\x5D\x00\x64\x05\x14\x02\x02\x31\x80" + "\x90\x00\x91"), + CARD_TYPE_TCOS }, /* SLE66S */ + { 19, X("\x3B\xBA\x96\x00\x81\x31\x86\x5D\x00\x64\x05\x60\x02\x03\x31\x80" + "\x90\x00\x66"), + CARD_TYPE_TCOS }, /* SLE66P */ + { 27, X("\x3B\xFF\x94\x00\xFF\x80\xB1\xFE\x45\x1F\x03\x00\x68\xD2\x76\x00" + "\x00\x28\xFF\x05\x1E\x31\x80\x00\x90\x00\x23"), + CARD_TYPE_MICARDO }, /* German BMI card */ + { 19, X("\x3B\x6F\x00\xFF\x00\x68\xD2\x76\x00\x00\x28\xFF\x05\x1E\x31\x80" + "\x00\x90\x00"), + CARD_TYPE_MICARDO }, /* German BMI card (ATR due to reader problem) */ + { 26, X("\x3B\xFE\x94\x00\xFF\x80\xB1\xFA\x45\x1F\x03\x45\x73\x74\x45\x49" + "\x44\x20\x76\x65\x72\x20\x31\x2E\x30\x43"), + CARD_TYPE_MICARDO }, /* EstEID (Estonian Big Brother card) */ + { 11, X("\x3b\xd2\x18\x00\x81\x31\xfe\x58\xc9\x01\x14"), + CARD_TYPE_CARDOS_50 }, /* CardOS 5.0 */ + { 11, X("\x3b\xd2\x18\x00\x81\x31\xfe\x58\xc9\x03\x16"), + CARD_TYPE_CARDOS_53 }, /* CardOS 5.3 */ + { 24, X("\x3b\xfe\x18\x00\x00\x80\x31\xfe\x45\x53\x43\x45" + "\x36\x30\x2d\x43\x44\x30\x38\x31\x2d\x6e\x46\xa9"), + CARD_TYPE_AET }, + { 0 } +}; +#undef X + + +/* Macro to test for CardOS 5.0 and 5.3. */ +#define IS_CARDOS_5(a) ((a)->app_local->card_type == CARD_TYPE_CARDOS_50 \ + || (a)->app_local->card_type == CARD_TYPE_CARDOS_53) + +/* The default PKCS-15 home DF */ +#define DEFAULT_HOME_DF 0x5015 + +/* The AID of PKCS15. */ +static char const pkcs15_aid[] = { 0xA0, 0, 0, 0, 0x63, + 0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 }; + +/* The Belgian eID variant - they didn't understood why a shared AID + is useful for a standard. Oh well. */ +static char const pkcs15be_aid[] = { 0xA0, 0, 0, 0x01, 0x77, + 0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 }; + + +/* The PIN types as defined in pkcs#15 v1.1 */ +typedef enum + { + PIN_TYPE_BCD = 0, + PIN_TYPE_ASCII_NUMERIC = 1, + PIN_TYPE_UTF8 = 2, + PIN_TYPE_HALF_NIBBLE_BCD = 3, + PIN_TYPE_ISO9564_1 = 4 + } pin_type_t; + +/* The AuthenticationTypes as defined in pkcs#15 v1.1 (6.8.1) */ +typedef enum + { + AUTH_TYPE_PIN = -1, + AUTH_TYPE_BIOMETRIC = 0, + AUTH_TYPE_AUTHKEY = 1, + AUTH_TYPE_EXTERNAL = 2, + } auth_type_t; + +/* A bit array with for the key usage flags from the + commonKeyAttributes. */ +struct keyusage_flags_s +{ + unsigned int encrypt: 1; + unsigned int decrypt: 1; + unsigned int sign: 1; + unsigned int sign_recover: 1; + unsigned int wrap: 1; + unsigned int unwrap: 1; + unsigned int verify: 1; + unsigned int verify_recover: 1; + unsigned int derive: 1; + unsigned int non_repudiation: 1; +}; +typedef struct keyusage_flags_s keyusage_flags_t; + + +/* A bit array with for the key access flags from the + commonKeyAttributes. */ +struct keyaccess_flags_s +{ + unsigned int any:1; /* Any access flag set. */ + unsigned int sensitive:1; + unsigned int extractable:1; + unsigned int always_sensitive:1; + unsigned int never_extractable:1; + unsigned int local:1; +}; +typedef struct keyaccess_flags_s keyaccess_flags_t; + + +/* A bit array with for the gpg usage flags. */ +struct gpgusage_flags_s +{ + unsigned int any:1; /* Any of the next flags are set. */ + unsigned int cert:1; /* 1.3.6.1.4.1.11591.2.6.1 */ + unsigned int sign:1; /* 1.3.6.1.4.1.11591.2.6.2 */ + unsigned int encr:1; /* 1.3.6.1.4.1.11591.2.6.3 */ + unsigned int auth:1; /* 1.3.6.1.4.1.11591.2.6.4 */ +}; +typedef struct gpgusage_flags_s gpgusage_flags_t; + + +/* This is an object to store information about a Certificate + Directory File (CDF) in a format suitable for further processing by + us. To keep memory management, simple we use a linked list of + items; i.e. one such object represents one certificate and the list + the entire CDF. */ +struct cdf_object_s +{ + /* Link to next item when used in a linked list. */ + struct cdf_object_s *next; + + /* Flags to indicate whether fields are valid. */ + unsigned int have_off:1; + + /* Length and allocated buffer with the Id of this object. + * This field is used for X.509 in PKCS#11 to make it easier to + * match a private key with a certificate. */ + size_t objidlen; + unsigned char *objid; + + /* Length and allocated buffer with the authId of this object or + NULL if no authID is known. */ + size_t authidlen; + unsigned char *authid; + + /* NULL or the malloced label of this object. */ + char *label; + + /* To avoid reading and parsing a certificate more than once, we + * cache the ksba object. */ + ksba_cert_t cert; + + /* The offset and length of the object. They are only valid if + HAVE_OFF is true and set to 0 if HAVE_OFF is false. */ + unsigned long off, len; + + /* The length of the path as given in the CDF and the path itself. + path[0] is the top DF (usually 0x3f00). The path will never be + empty. */ + size_t pathlen; + unsigned short path[1]; +}; +typedef struct cdf_object_s *cdf_object_t; + + +/* This is an object to store information about a Private Key + Directory File (PrKDF) in a format suitable for further processing + by us. To keep memory management, simple we use a linked list of + items; i.e. one such object represents one certificate and the list + the entire PrKDF. */ +struct prkdf_object_s +{ + /* Link to next item when used in a linked list. */ + struct prkdf_object_s *next; + + /* Flags to indicate whether fields are valid. */ + unsigned int keygrip_valid:1; + unsigned int key_reference_valid:1; + unsigned int have_off:1; + unsigned int have_keytime:1; + + /* Flag indicating that the corresponding PIN has already been + * verified. Note that for cards which are able to return the + * verification stus, this flag is not used. */ + unsigned int pin_verified:1; + + /* PKCS#15 info whether this is an EC key. Default is RSA. Note + * that there is also a KEYALGO field which is derived from the + * publick key via Libgcrypt. */ + unsigned int is_ecc:1; + + /* The key's usage flags. */ + keyusage_flags_t usageflags; + + /* The key's access flags. */ + keyaccess_flags_t accessflags; + + /* Extended key usage flags. Only used if .valid is set. This + * information is computed from an associated certificate15. */ + struct { + unsigned int valid:1; + unsigned int sign:1; + unsigned int encr:1; + unsigned int auth:1; + } extusage; + + /* OpenPGP key features for this key. This is taken from special + * extended key usage flags different from those tracked in EXTUSAGE + * above. There is also no valid flag as in EXTUSAGE. */ + gpgusage_flags_t gpgusage; + + /* The keygrip of the key. This is used as a cache. */ + char keygrip[2*KEYGRIP_LEN+1]; + + /* A malloced algorithm string or NULL if not known. */ + char *keyalgostr; + + /* The Gcrypt algo identifier for the key. It is valid if the + * keygrip is also valid. See also is_ecc above. */ + int keyalgo; + + /* The length of the key in bits (e.g. for RSA the length of the + * modulus). It is valid if the keygrip is also valid. */ + unsigned int keynbits; + + /* The creation time of the key or 0 if not known. */ + u32 keytime; + + /* Malloced CN from the Subject-DN of the corresponding certificate + * or NULL if not known. */ + char *common_name; + + /* Malloced SerialNumber from the Subject-DN of the corresponding + * certificate or NULL if not known. */ + char *serial_number; + + /* KDF/KEK parameter for OpenPGP's ECDH. First byte is zero if not + * availabale. .*/ + unsigned char ecdh_kdf[4]; + + /* Length and allocated buffer with the Id of this object. */ + size_t objidlen; + unsigned char *objid; + + /* Length and allocated buffer with the authId of this object or + NULL if no authID is known. */ + size_t authidlen; + unsigned char *authid; + + /* NULL or the malloced label of this object. */ + char *label; + + /* The keyReference and a flag telling whether it is valid. */ + unsigned long key_reference; + + /* The offset and length of the object. They are only valid if + * HAVE_OFF is true otherwise they are set to 0. */ + unsigned long off, len; + + /* The length of the path as given in the PrKDF and the path itself. + path[0] is the top DF (usually 0x3f00). */ + size_t pathlen; + unsigned short path[1]; +}; +typedef struct prkdf_object_s *prkdf_object_t; +typedef struct prkdf_object_s *pukdf_object_t; + + +/* This is an object to store information about a Authentication + Object Directory File (AODF) in a format suitable for further + processing by us. To keep memory management, simple we use a linked + list of items; i.e. one such object represents one authentication + object and the list the entire AOKDF. */ +struct aodf_object_s +{ + /* Link to next item when used in a linked list. */ + struct aodf_object_s *next; + + /* Flags to indicate whether fields are valid. */ + unsigned int have_off:1; + + /* Length and allocated buffer with the Id of this object. */ + size_t objidlen; + unsigned char *objid; + + /* Length and allocated buffer with the authId of this object or + NULL if no authID is known. */ + size_t authidlen; + unsigned char *authid; + + /* NULL or the malloced label of this object. */ + char *label; + + /* The file ID of this AODF. */ + unsigned short fid; + + /* The type of this authentication object. */ + auth_type_t auth_type; + + /* Info used for AUTH_TYPE_PIN: */ + + /* The PIN Flags. */ + struct + { + unsigned int case_sensitive: 1; + unsigned int local: 1; + unsigned int change_disabled: 1; + unsigned int unblock_disabled: 1; + unsigned int initialized: 1; + unsigned int needs_padding: 1; + unsigned int unblocking_pin: 1; + unsigned int so_pin: 1; + unsigned int disable_allowed: 1; + unsigned int integrity_protected: 1; + unsigned int confidentiality_protected: 1; + unsigned int exchange_ref_data: 1; + } pinflags; + + /* The PIN Type. */ + pin_type_t pintype; + + /* The minimum length of a PIN. */ + unsigned long min_length; + + /* The stored length of a PIN. */ + unsigned long stored_length; + + /* The maximum length of a PIN and a flag telling whether it is valid. */ + unsigned long max_length; + int max_length_valid; + + /* The pinReference and a flag telling whether it is valid. */ + unsigned long pin_reference; + int pin_reference_valid; + + /* The padChar and a flag telling whether it is valid. */ + char pad_char; + int pad_char_valid; + + /* The offset and length of the object. They are only valid if + HAVE_OFF is true and set to 0 if HAVE_OFF is false. */ + unsigned long off, len; + + /* The length of the path as given in the Aodf and the path itself. + path[0] is the top DF (usually 0x3f00). PATH is optional and thus + may be NULL. Malloced.*/ + size_t pathlen; + unsigned short *path; + + /* Info used for AUTH_TYPE_AUTHKEY: */ + +}; +typedef struct aodf_object_s *aodf_object_t; + + +/* Context local to this application. */ +struct app_local_s +{ + /* The home DF. Note, that we don't yet support a multilevel + hierarchy. Thus we assume this is directly below the MF. */ + unsigned short home_df; + + /* The type of the card's OS. */ + card_type_t card_type; + + /* The vendor's product. */ + card_product_t card_product; + + /* Flag indicating that extended_mode is not supported. */ + unsigned int no_extended_mode : 1; + + /* Flag indicating whether we may use direct path selection. */ + unsigned int direct_path_selection : 1; + + /* Flag indicating whether the card has any key with a gpgusage set. */ + unsigned int any_gpgusage : 1; + + /* Structure with the EFIDs of the objects described in the ODF + file. */ + struct + { + unsigned short private_keys; + unsigned short public_keys; + unsigned short trusted_public_keys; + unsigned short secret_keys; + unsigned short certificates; + unsigned short trusted_certificates; + unsigned short useful_certificates; + unsigned short data_objects; + unsigned short auth_objects; + } odf; + + /* The PKCS#15 serialnumber from EF(TokenInfo) or NULL. Malloced. */ + unsigned char *serialno; + size_t serialnolen; + + /* The manufacturerID from the TokenInfo EF. Malloced or NULL. */ + char *manufacturer_id; + + /* The label from the TokenInfo EF. Malloced or NULL. */ + char *token_label; + + /* The tokenflags from the TokenInfo EF. Malloced or NULL. */ + unsigned char *tokenflags; + unsigned int tokenflagslen; + + /* Information on all certificates. */ + cdf_object_t certificate_info; + /* Information on all trusted certificates. */ + cdf_object_t trusted_certificate_info; + /* Information on all useful certificates. */ + cdf_object_t useful_certificate_info; + + /* Information on all public keys. */ + prkdf_object_t public_key_info; + + /* Information on all private keys. */ + pukdf_object_t private_key_info; + + /* Information on all authentication objects. */ + aodf_object_t auth_object_info; + +}; + + +/*** Local prototypes. ***/ +static gpg_error_t select_ef_by_path (app_t app, const unsigned short *path, + size_t pathlen); +static gpg_error_t keygrip_from_prkdf (app_t app, prkdf_object_t prkdf); +static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf, + unsigned char **r_cert, size_t *r_certlen); +static char *get_dispserialno (app_t app, prkdf_object_t prkdf); +static gpg_error_t do_getattr (app_t app, ctrl_t ctrl, const char *name); + + + +static const char * +cardtype2str (card_type_t cardtype) +{ + switch (cardtype) + { + case CARD_TYPE_UNKNOWN: return ""; + case CARD_TYPE_TCOS: return "TCOS"; + case CARD_TYPE_MICARDO: return "Micardo"; + case CARD_TYPE_CARDOS_50: return "CardOS 5.0"; + case CARD_TYPE_CARDOS_53: return "CardOS 5.3"; + case CARD_TYPE_BELPIC: return "Belgian eID"; + case CARD_TYPE_AET: return "AET"; + } + return ""; +} + +static const char * +cardproduct2str (card_product_t cardproduct) +{ + switch (cardproduct) + { + case CARD_PRODUCT_UNKNOWN: return ""; + case CARD_PRODUCT_RSCS: return "R&S"; + case CARD_PRODUCT_DTRUST: return "D-Trust"; + case CARD_PRODUCT_GENUA: return "GeNUA"; + } + return ""; +} + +/* Release the CDF object A */ +static void +release_cdflist (cdf_object_t a) +{ + while (a) + { + cdf_object_t tmp = a->next; + ksba_free (a->cert); + xfree (a->objid); + xfree (a->authid); + xfree (a->label); + xfree (a); + a = tmp; + } +} + +/* Release the PrKDF object A. */ +static void +release_prkdflist (prkdf_object_t a) +{ + while (a) + { + prkdf_object_t tmp = a->next; + xfree (a->keyalgostr); + xfree (a->common_name); + xfree (a->serial_number); + xfree (a->objid); + xfree (a->authid); + xfree (a->label); + xfree (a); + a = tmp; + } +} + +static void +release_pukdflist (pukdf_object_t a) +{ + release_prkdflist (a); +} + +/* Release just one aodf object. */ +void +release_aodf_object (aodf_object_t a) +{ + if (a) + { + xfree (a->objid); + xfree (a->authid); + xfree (a->label); + xfree (a->path); + xfree (a); + } +} + +/* Release the AODF list A. */ +static void +release_aodflist (aodf_object_t a) +{ + while (a) + { + aodf_object_t tmp = a->next; + release_aodf_object (a); + a = tmp; + } +} + + +static void +release_lists (app_t app) +{ + release_cdflist (app->app_local->certificate_info); + app->app_local->certificate_info = NULL; + release_cdflist (app->app_local->trusted_certificate_info); + app->app_local->trusted_certificate_info = NULL; + release_cdflist (app->app_local->useful_certificate_info); + app->app_local->useful_certificate_info = NULL; + release_pukdflist (app->app_local->public_key_info); + app->app_local->public_key_info = NULL; + release_prkdflist (app->app_local->private_key_info); + app->app_local->private_key_info = NULL; + release_aodflist (app->app_local->auth_object_info); + app->app_local->auth_object_info = NULL; +} + + +static void +release_tokeninfo (app_t app) +{ + xfree (app->app_local->manufacturer_id); + app->app_local->manufacturer_id = NULL; + xfree (app->app_local->token_label); + app->app_local->token_label = NULL; + xfree (app->app_local->tokenflags); + app->app_local->tokenflags = NULL; + xfree (app->app_local->serialno); + app->app_local->serialno = NULL; +} + + +/* Release all local resources. */ +static void +do_deinit (app_t app) +{ + if (app && app->app_local) + { + release_lists (app); + release_tokeninfo (app); + xfree (app->app_local); + app->app_local = NULL; + } +} + + +/* Do a select and a read for the file with EFID. EFID_DESC is a + desctription of the EF to be used with error messages. On success + BUFFER and BUFLEN contain the entire content of the EF. The caller + must free BUFFER only on success. If EFID is 0 no seelct is done. */ +static gpg_error_t +select_and_read_binary (app_t app, unsigned short efid, const char *efid_desc, + unsigned char **buffer, size_t *buflen) +{ + gpg_error_t err; + int sw; + + if (efid) + { + err = select_ef_by_path (app, &efid, 1); + if (err) + { + log_error ("p15: error selecting %s (0x%04X): %s\n", + efid_desc, efid, gpg_strerror (err)); + return err; + } + } + + err = iso7816_read_binary_ext (app_get_slot (app), + 0, 0, 0, buffer, buflen, &sw); + if (err) + log_error ("p15: error reading %s (0x%04X): %s (sw=%04X)\n", + efid_desc, efid, gpg_strerror (err), sw); + return err; +} + + +/* If EFID is not 0 do a select and then read the record RECNO. + * EFID_DESC is a description of the EF to be used with error + * messages. On success BUFFER and BUFLEN contain the entire content + * of the EF. The caller must free BUFFER only on success. */ +static gpg_error_t +select_and_read_record (app_t app, unsigned short efid, int recno, + const char *efid_desc, + unsigned char **buffer, size_t *buflen, int *r_sw) +{ + gpg_error_t err; + int sw; + + if (r_sw) + *r_sw = 0x9000; + + if (efid) + { + err = select_ef_by_path (app, &efid, 1); + if (err) + { + log_error ("p15: error selecting %s (0x%04X): %s\n", + efid_desc, efid, gpg_strerror (err)); + if (r_sw) + *r_sw = sw; + return err; + } + } + + err = iso7816_read_record_ext (app_get_slot (app), + recno, 1, 0, buffer, buflen, &sw); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + ; + else if (err && sw == SW_FILE_STRUCT) + ; + else + log_error ("p15: error reading %s (0x%04X) record %d: %s (sw=%04X)\n", + efid_desc, efid, recno, gpg_strerror (err), sw); + if (r_sw) + *r_sw = sw; + return err; + } + /* On CardOS with a Linear TLV file structure the records starts + * with some tag (often the record number) followed by the length + * byte for this record. Detect and remove this prefix. */ + if (*buflen > 2 && (*buffer)[0] != 0x30 && (*buffer)[1] == *buflen - 2) + { + memmove (*buffer, *buffer + 2, *buflen - 2); + *buflen = *buflen - 2; + } + + return 0; +} + + +/* This function calls select file to read a file using a complete + path which may or may not start at the master file (MF). */ +static gpg_error_t +select_ef_by_path (app_t app, const unsigned short *path, size_t pathlen) +{ + gpg_error_t err; + int i, j; + + if (!pathlen) + return gpg_error (GPG_ERR_INV_VALUE); + + /* log_debug ("%s: path=", __func__); */ + /* for (j=0; j < pathlen; j++) */ + /* log_printf ("%s%04hX", j? "/":"", path[j]); */ + /* log_printf ("%s\n",app->app_local->direct_path_selection?" (direct)":"");*/ + + if (app->app_local->direct_path_selection) + { + if (pathlen && *path == 0x3f00 ) + { + if (pathlen == 1) + err = iso7816_select_mf (app_get_slot (app)); + else + err = iso7816_select_path (app_get_slot (app), path+1, pathlen-1, + 0); + } + else + err = iso7816_select_path (app_get_slot (app), path, pathlen, + app->app_local->home_df); + if (err) + { + log_error ("p15: error selecting path "); + goto err_print_path; + } + } + else if (pathlen > 1 && path[0] == 0x3fff) + { + err = iso7816_select_file (app_get_slot (app), 0x3f00, 0); + if (err) + { + log_error ("p15: error selecting part %d from path ", 0); + goto err_print_path; + } + path++; + pathlen--; + for (i=0; i < pathlen; i++) + { + err = iso7816_select_file (app_get_slot (app), + path[i], (i+1 == pathlen)? 2 : 1); + if (err) + { + log_error ("p15: error selecting part %d from path ", i); + goto err_print_path; + } + } + } + else + { + if (pathlen && *path != 0x3f00 ) + log_error ("p15: warning: relative path select not yet implemented\n"); + + /* FIXME: Use home_df. */ + for (i=0; i < pathlen; i++) + { + err = iso7816_select_file (app_get_slot (app), + path[i], !(i+1 == pathlen)); + if (err) + { + log_error ("p15: error selecting part %d from path ", i); + goto err_print_path; + } + } + } + return 0; + + err_print_path: + if (pathlen && *path != 0x3f00 ) + log_printf ("3F00/"); + else + log_printf ("%04hX/", app->app_local->home_df); + for (j=0; j < pathlen; j++) + log_printf ("%s%04hX", j? "/":"", path[j]); + log_printf (": %s\n", gpg_strerror (err)); + return err; +} + + +/* Parse a cert Id string (or a key Id string) and return the binary + object Id string in a newly allocated buffer stored at R_OBJID and + R_OBJIDLEN. On Error NULL will be stored there and an error code + returned. On success caller needs to free the buffer at R_OBJID. */ +static gpg_error_t +parse_certid (app_t app, const char *certid, + unsigned char **r_objid, size_t *r_objidlen) +{ + char tmpbuf[10]; + const char *s; + size_t objidlen; + unsigned char *objid; + int i; + + *r_objid = NULL; + *r_objidlen = 0; + + if (certid[0] != 'P' && strlen (certid) == 40) /* This is a keygrip. */ + { + prkdf_object_t prkdf; + + for (prkdf = app->app_local->private_key_info; + prkdf; prkdf = prkdf->next) + if (!keygrip_from_prkdf (app, prkdf) + && !strcmp (certid, prkdf->keygrip)) + break; + if (!prkdf || !prkdf->objidlen || !prkdf->objid) + return gpg_error (GPG_ERR_NOT_FOUND); + objidlen = prkdf->objidlen; + objid = xtrymalloc (objidlen); + if (!objid) + return gpg_error_from_syserror (); + memcpy (objid, prkdf->objid, prkdf->objidlen); + } + else /* This is a usual keyref. */ + { + if (app->app_local->home_df != DEFAULT_HOME_DF) + snprintf (tmpbuf, sizeof tmpbuf, "P15-%04X.", + (unsigned int)(app->app_local->home_df & 0xffff)); + else + strcpy (tmpbuf, "P15."); + if (strncmp (certid, tmpbuf, strlen (tmpbuf)) ) + { + if (!strncmp (certid, "P15.", 4) + || (!strncmp (certid, "P15-", 4) + && hexdigitp (certid+4) + && hexdigitp (certid+5) + && hexdigitp (certid+6) + && hexdigitp (certid+7) + && certid[8] == '.')) + return gpg_error (GPG_ERR_NOT_FOUND); + return gpg_error (GPG_ERR_INV_ID); + } + certid += strlen (tmpbuf); + for (s=certid, objidlen=0; hexdigitp (s); s++, objidlen++) + ; + if (*s || !objidlen || (objidlen%2)) + return gpg_error (GPG_ERR_INV_ID); + objidlen /= 2; + objid = xtrymalloc (objidlen); + if (!objid) + return gpg_error_from_syserror (); + for (s=certid, i=0; i < objidlen; i++, s+=2) + objid[i] = xtoi_2 (s); + } + + *r_objid = objid; + *r_objidlen = objidlen; + return 0; +} + + +/* Find a certificate object by its object ID and store a pointer to + * it at R_CDF. */ +static gpg_error_t +cdf_object_from_objid (app_t app, size_t objidlen, const unsigned char *objid, + cdf_object_t *r_cdf) +{ + cdf_object_t cdf; + + for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next) + if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) + break; + if (!cdf) + for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next) + if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) + break; + if (!cdf) + for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next) + if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) + break; + if (!cdf) + return gpg_error (GPG_ERR_NOT_FOUND); + *r_cdf = cdf; + return 0; +} + + +/* Find a certificate object by its label and store a pointer to it at + * R_CDF. */ +static gpg_error_t +cdf_object_from_label (app_t app, const char *label, cdf_object_t *r_cdf) +{ + cdf_object_t cdf; + + if (!label) + return gpg_error (GPG_ERR_NOT_FOUND); + + for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next) + if (cdf->label && !strcmp (cdf->label, label)) + break; + if (!cdf) + for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next) + if (cdf->label && !strcmp (cdf->label, label)) + break; + if (!cdf) + for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next) + if (cdf->label && !strcmp (cdf->label, label)) + break; + if (!cdf) + return gpg_error (GPG_ERR_NOT_FOUND); + *r_cdf = cdf; + return 0; +} + + +/* Find a certificate object by the certificate ID CERTID and store a + * pointer to it at R_CDF. */ +static gpg_error_t +cdf_object_from_certid (app_t app, const char *certid, cdf_object_t *r_cdf) +{ + gpg_error_t err; + size_t objidlen; + unsigned char *objid; + cdf_object_t cdf; + prkdf_object_t prkdf; + + err = parse_certid (app, certid, &objid, &objidlen); + if (err) + return err; + + err = cdf_object_from_objid (app, objidlen, objid, &cdf); + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + { + /* Try again by finding the certid in the prkdf and matching by + * label. */ + for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) + if (prkdf->objidlen == objidlen + && !memcmp (prkdf->objid, objid, objidlen)) + break; + if (prkdf) + err = cdf_object_from_label (app, prkdf->label, &cdf); + } + xfree (objid); + if (err) + return err; + *r_cdf = cdf; + return 0; +} + + +/* Find a private key object by the key Id string KEYIDSTR and store a + pointer to it at R_PRKDF. */ +static gpg_error_t +prkdf_object_from_keyidstr (app_t app, const char *keyidstr, + prkdf_object_t *r_prkdf) +{ + gpg_error_t err; + size_t objidlen; + unsigned char *objid; + prkdf_object_t prkdf; + + err = parse_certid (app, keyidstr, &objid, &objidlen); + if (err) + return err; + + for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) + if (prkdf->objidlen == objidlen && !memcmp (prkdf->objid, objid, objidlen)) + break; + xfree (objid); + if (!prkdf) + return gpg_error (GPG_ERR_NOT_FOUND); + *r_prkdf = prkdf; + return 0; +} + + + + +/* Read and parse the Object Directory File and store away the + pointers. ODF_FID shall contain the FID of the ODF. + + Example of such a file: + + A0 06 30 04 04 02 60 34 = Private Keys + A4 06 30 04 04 02 60 35 = Certificates + A5 06 30 04 04 02 60 36 = Trusted Certificates + A7 06 30 04 04 02 60 37 = Data Objects + A8 06 30 04 04 02 60 38 = Auth Objects + + These are all PathOrObjects using the path CHOICE element. The + paths are octet strings of length 2. Using this Path CHOICE + element is recommended, so we only implement that for now. +*/ +static gpg_error_t +read_ef_odf (app_t app, unsigned short odf_fid) +{ + gpg_error_t err; + unsigned char *buffer, *p; + size_t buflen, n; + unsigned short value; + size_t offset; + unsigned short home_df = 0; + + + app->app_local->odf.private_keys = 0; + app->app_local->odf.public_keys = 0; + app->app_local->odf.trusted_public_keys = 0; + app->app_local->odf.secret_keys = 0; + app->app_local->odf.certificates = 0; + app->app_local->odf.trusted_certificates = 0; + app->app_local->odf.useful_certificates = 0; + app->app_local->odf.data_objects = 0; + app->app_local->odf.auth_objects = 0; + + err = select_and_read_binary (app, odf_fid, "ODF", + &buffer, &buflen); + if (err) + return err; + + if (buflen < 8) + { + log_error ("p15: error: ODF too short\n"); + xfree (buffer); + return gpg_error (GPG_ERR_INV_OBJ); + } + + home_df = app->app_local->home_df; + p = buffer; + while (buflen && *p && *p != 0xff) + { + if ( buflen >= 8 + && (p[0] & 0xf0) == 0xA0 + && !memcmp (p+1, "\x06\x30\x04\x04\x02", 5) ) + { + offset = 6; + } + else if ( buflen >= 12 + && (p[0] & 0xf0) == 0xA0 + && !memcmp (p+1, "\x0a\x30\x08\x04\x06\x3F\x00", 7) + && (!home_df || home_df == ((p[8]<<8)|p[9])) ) + { + /* FIXME: Is this hack still required? */ + /* If we do not know the home DF, we take it from the first + * ODF object. Here are sample values: + * a0 0a 30 08 0406 3f00 5015 4401 + * a1 0a 30 08 0406 3f00 5015 4411 + * a4 0a 30 08 0406 3f00 5015 4441 + * a5 0a 30 08 0406 3f00 5015 4451 + * a8 0a 30 08 0406 3f00 5015 4481 + * 00000000 */ + if (!home_df) + { + home_df = ((p[8]<<8)|p[9]); + app->app_local->home_df = home_df; + log_info ("p15: application directory detected as 0x%04hX\n", + home_df); + /* We assume that direct path selection is possible. */ + app->app_local->direct_path_selection = 1; + } + + /* We only allow a full path if all files are at the same + level and below the home directory. To extend this we + would need to make use of new data type capable of + keeping a full path. */ + offset = 10; + } + else + { + log_printhex (p, buflen, "p15: ODF format not supported:"); + xfree (buffer); + return gpg_error (GPG_ERR_INV_OBJ); + } + switch ((p[0] & 0x0f)) + { + case 0: value = app->app_local->odf.private_keys; break; + case 1: value = app->app_local->odf.public_keys; break; + case 2: value = app->app_local->odf.trusted_public_keys; break; + case 3: value = app->app_local->odf.secret_keys; break; + case 4: value = app->app_local->odf.certificates; break; + case 5: value = app->app_local->odf.trusted_certificates; break; + case 6: value = app->app_local->odf.useful_certificates; break; + case 7: value = app->app_local->odf.data_objects; break; + case 8: value = app->app_local->odf.auth_objects; break; + default: value = 0; break; + } + if (value) + { + log_error ("p15: duplicate object type %d in ODF ignored\n", + (p[0]&0x0f)); + continue; + } + value = ((p[offset] << 8) | p[offset+1]); + switch ((p[0] & 0x0f)) + { + case 0: app->app_local->odf.private_keys = value; break; + case 1: app->app_local->odf.public_keys = value; break; + case 2: app->app_local->odf.trusted_public_keys = value; break; + case 3: app->app_local->odf.secret_keys = value; break; + case 4: app->app_local->odf.certificates = value; break; + case 5: app->app_local->odf.trusted_certificates = value; break; + case 6: app->app_local->odf.useful_certificates = value; break; + case 7: app->app_local->odf.data_objects = value; break; + case 8: app->app_local->odf.auth_objects = value; break; + default: + log_error ("p15: unknown object type %d in ODF ignored\n", + (p[0]&0x0f)); + } + offset += 2; + + if (buflen < offset) + break; + p += offset; + buflen -= offset; + } + + if (buflen) + { + /* Print a warning if non-null garbage is left over. */ + for (n=0; n < buflen && !p[n]; n++) + ; + if (n < buflen) + { + log_info ("p15: warning: garbage detected at end of ODF: "); + log_printhex (p, buflen, ""); + } + } + + xfree (buffer); + return 0; +} + + +/* Helper for the read_ef_foo functions to read the first record or + * the entire data. */ +static gpg_error_t +read_first_record (app_t app, unsigned short fid, const char *fid_desc, + unsigned char **r_buffer, size_t *r_buflen, + int *r_use_read_record) +{ + gpg_error_t err; + int sw; + + *r_buffer = NULL; + *r_buflen = 0; + *r_use_read_record = 0; + + if (!fid) + return gpg_error (GPG_ERR_NO_DATA); /* No such file. */ + + if (IS_CARDOS_5 (app)) + { + *r_use_read_record = 1; + err = select_and_read_record (app, fid, 1, fid_desc, + r_buffer, r_buflen, &sw); + if (err && sw == SW_FILE_STRUCT) + { + *r_use_read_record = 0; + err = select_and_read_binary (app, 0, fid_desc, r_buffer, r_buflen); + } + } + else + err = select_and_read_binary (app, fid, fid_desc, r_buffer, r_buflen); + + /* We get a not_found state in read_record mode if the select + * succeeded but reading the record failed. Map that to no_data + * which is what the caller of the read_ef_foo functions expect. */ + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = gpg_error (GPG_ERR_NO_DATA); + + return err; +} + + +/* Parse the BIT STRING with the keyUsageFlags from the + CommonKeyAttributes. */ +static gpg_error_t +parse_keyusage_flags (const unsigned char *der, size_t derlen, + keyusage_flags_t *usageflags) +{ + unsigned int bits, mask; + int i, unused, full; + + memset (usageflags, 0, sizeof *usageflags); + if (!derlen) + return gpg_error (GPG_ERR_INV_OBJ); + + unused = *der++; derlen--; + if ((!derlen && unused) || unused/8 > derlen) + return gpg_error (GPG_ERR_ENCODING_PROBLEM); + full = derlen - (unused+7)/8; + unused %= 8; + mask = 0; + for (i=1; unused; i <<= 1, unused--) + mask |= i; + + /* First octet */ + if (derlen) + { + bits = *der++; derlen--; + if (full) + full--; + else + { + bits &= ~mask; + mask = 0; + } + } + else + bits = 0; + if ((bits & 0x80)) usageflags->encrypt = 1; + if ((bits & 0x40)) usageflags->decrypt = 1; + if ((bits & 0x20)) usageflags->sign = 1; + if ((bits & 0x10)) usageflags->sign_recover = 1; + if ((bits & 0x08)) usageflags->wrap = 1; + if ((bits & 0x04)) usageflags->unwrap = 1; + if ((bits & 0x02)) usageflags->verify = 1; + if ((bits & 0x01)) usageflags->verify_recover = 1; + + /* Second octet. */ + if (derlen) + { + bits = *der++; derlen--; + if (full) + full--; + else + { + bits &= ~mask; + } + } + else + bits = 0; + if ((bits & 0x80)) usageflags->derive = 1; + if ((bits & 0x40)) usageflags->non_repudiation = 1; + + return 0; +} + + +static void +dump_keyusage_flags (keyusage_flags_t usageflags) +{ + const char *s = ""; + + log_info ("p15: usage="); + if (usageflags.encrypt) + log_printf ("%sencrypt", s), s = ","; + if (usageflags.decrypt) + log_printf ("%sdecrypt", s), s = ","; + if (usageflags.sign ) + log_printf ("%ssign", s), s = ","; + if (usageflags.sign_recover) + log_printf ("%ssign_recover", s), s = ","; + if (usageflags.wrap ) + log_printf ("%swrap", s), s = ","; + if (usageflags.unwrap ) + log_printf ("%sunwrap", s), s = ","; + if (usageflags.verify ) + log_printf ("%sverify", s), s = ","; + if (usageflags.verify_recover) + log_printf ("%sverify_recover", s), s = ","; + if (usageflags.derive ) + log_printf ("%sderive", s), s = ","; + if (usageflags.non_repudiation) + log_printf ("%snon_repudiation", s), s = ","; +} + + +static void +dump_keyaccess_flags (keyaccess_flags_t accessflags) +{ + const char *s = ""; + + log_info ("p15: access="); + if (accessflags.sensitive) + log_printf ("%ssensitive", s), s = ","; + if (accessflags.extractable) + log_printf ("%sextractable", s), s = ","; + if (accessflags.always_sensitive) + log_printf ("%salways_sensitive", s), s = ","; + if (accessflags.never_extractable) + log_printf ("%snever_extractable", s), s = ","; + if (accessflags.local) + log_printf ("%slocal", s), s = ","; +} + + +static void +dump_gpgusage_flags (gpgusage_flags_t gpgusage) +{ + const char *s = ""; + + log_info ("p15: gpgusage="); + if (gpgusage.cert) + log_printf ("%scert", s), s = ","; + if (gpgusage.sign) + log_printf ("%ssign", s), s = ","; + if (gpgusage.encr) + log_printf ("%sencr", s), s = ","; + if (gpgusage.auth) + log_printf ("%sauth", s), s = ","; +} + + +/* Parse the BIT STRING with the keyAccessFlags from the + CommonKeyAttributes. */ +static gpg_error_t +parse_keyaccess_flags (const unsigned char *der, size_t derlen, + keyaccess_flags_t *accessflags) +{ + unsigned int bits, mask; + int i, unused, full; + + memset (accessflags, 0, sizeof *accessflags); + if (!derlen) + return gpg_error (GPG_ERR_INV_OBJ); + + unused = *der++; derlen--; + if ((!derlen && unused) || unused/8 > derlen) + return gpg_error (GPG_ERR_ENCODING_PROBLEM); + full = derlen - (unused+7)/8; + unused %= 8; + mask = 0; + for (i=1; unused; i <<= 1, unused--) + mask |= i; + + /* First octet */ + if (derlen) + { + bits = *der++; derlen--; + if (full) + full--; + else + { + bits &= ~mask; + mask = 0; + } + } + else + bits = 0; + if ((bits & 0x10)) accessflags->local = 1; + if ((bits & 0x08)) accessflags->never_extractable = 1; + if ((bits & 0x04)) accessflags->always_sensitive = 1; + if ((bits & 0x02)) accessflags->extractable = 1; + if ((bits & 0x01)) accessflags->sensitive = 1; + + accessflags->any = 1; + return 0; +} + + +/* Parse the commonObjectAttributes and store a malloced authid at + * (r_authid,r_authidlen). (NULL,0) is stored on error or if no + * authid is found. IF R_LABEL is not NULL the label is stored there + * as a malloced string (spaces are replaced by underscores). + * + * Example data: + * 2 30 17: SEQUENCE { -- commonObjectAttributes + * 4 0C 8: UTF8String 'SK.CH.DS' -- label + * 14 03 2: BIT STRING 6 unused bits + * : '01'B (bit 0) + * 18 04 1: OCTET STRING --authid + * : 07 + * : } + */ +static gpg_error_t +parse_common_obj_attr (unsigned char const **buffer, size_t *size, + unsigned char **r_authid, size_t *r_authidlen, + char **r_label) +{ + gpg_error_t err; + int where; + int class, tag, constructed, ndef; + size_t objlen, hdrlen, nnn; + const unsigned char *ppp; + int ignore_eof = 0; + char *p; + + *r_authid = NULL; + *r_authidlen = 0; + if (r_label) + *r_label = NULL; + + where = __LINE__; + err = parse_ber_header (buffer, size, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > *size || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + + ppp = *buffer; + nnn = objlen; + *buffer += objlen; + *size -= objlen; + + /* Search the optional AuthId. */ + ignore_eof = 1; + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + + if (tag == TAG_UTF8_STRING) + { + if (r_label) + { + *r_label = xtrymalloc (objlen + 1); + if (!*r_label) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (*r_label, ppp, objlen); + (*r_label)[objlen] = 0; + /* We don't want spaces in the labels due to the properties + * of CHV-LABEL. */ + for (p = *r_label; *p; p++) + if (ascii_isspace (*p)) + *p = '_'; + } + + ppp += objlen; + nnn -= objlen; + + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + } + if (tag == TAG_BIT_STRING) + { + ppp += objlen; /* Skip the CommonObjectFlags. */ + nnn -= objlen; + + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + } + if (tag == TAG_OCTET_STRING && objlen) + { + *r_authid = xtrymalloc (objlen); + if (!*r_authid) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (*r_authid, ppp, objlen); + *r_authidlen = objlen; + } + + leave: + if (ignore_eof && gpg_err_code (err) == GPG_ERR_EOF) + err = 0; + else if (err) + log_error ("p15: error parsing commonObjectAttributes at %d: %s\n", + where, gpg_strerror (err)); + + if (err && r_label) + { + xfree (*r_label); + *r_label = NULL; + } + + return err; +} + + +/* Parse the commonKeyAttributes. On success store the objid at + * (R_OBJID/R_OBJIDLEN), sets the key usage flags at USAGEFLAGS and + * the optiona key refrence at R_KEY_REFERENCE. The latter is only + * valid if true is also stored at R_KEY_REFERENCE_VALID. + * + * Example data: + * + * 21 30 12: SEQUENCE { -- commonKeyAttributes + * 23 04 1: OCTET STRING + * : 01 + * 26 03 3: BIT STRING 6 unused bits + * : '1000000000'B (bit 9) + * 31 02 2: INTEGER 80 -- keyReference (optional) + * : } + */ +static gpg_error_t +parse_common_key_attr (unsigned char const **buffer, size_t *size, + unsigned char **r_objid, size_t *r_objidlen, + keyusage_flags_t *usageflags, + keyaccess_flags_t *accessflags, + unsigned long *r_key_reference, + int *r_key_reference_valid) +{ + gpg_error_t err; + int where; + int class, tag, constructed, ndef; + size_t objlen, hdrlen, nnn; + const unsigned char *ppp; + int ignore_eof = 0; + unsigned long ul; + const unsigned char *objid = NULL; + size_t objidlen; + unsigned long key_reference = 0; + int key_reference_valid = 0; + + *r_objid = NULL; + *r_objidlen = 0; + memset (usageflags, 0, sizeof *usageflags); + memset (accessflags, 0, sizeof *accessflags); + *r_key_reference_valid = 0; + + where = __LINE__; + err = parse_ber_header (buffer, size, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > *size || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + + ppp = *buffer; + nnn = objlen; + *buffer += objlen; + *size -= objlen; + + /* Get the Id. */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn + || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + + objid = ppp; + objidlen = objlen; + ppp += objlen; + nnn -= objlen; + + /* Get the KeyUsageFlags. */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn + || class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + + err = parse_keyusage_flags (ppp, objlen, usageflags); + if (err) + goto leave; + ppp += objlen; + nnn -= objlen; + + ignore_eof = 1; /* Remaining items are optional. */ + + /* Find the keyReference */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nnn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + + if (class == CLASS_UNIVERSAL && tag == TAG_BOOLEAN) + { + /* Skip the native element. */ + ppp += objlen; + nnn -= objlen; + + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nnn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + } + if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING) + { + /* These are the keyAccessFlags. */ + err = parse_keyaccess_flags (ppp, objlen, accessflags); + if (err) + goto leave; + ppp += objlen; + nnn -= objlen; + + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nnn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + } + if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER) + { + /* This is the keyReference. */ + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*ppp++) & 0xff; + nnn--; + } + key_reference = ul; + key_reference_valid = 1; + } + + leave: + if (ignore_eof && gpg_err_code (err) == GPG_ERR_EOF) + err = 0; + + if (!err) + { + if (!objid || !objidlen) + err = gpg_error (GPG_ERR_INV_OBJ); + else + { + *r_objid = xtrymalloc (objidlen); + if (!*r_objid) + err = gpg_error_from_syserror (); + else + { + memcpy (*r_objid, objid, objidlen); + *r_objidlen = objidlen; + } + } + } + if (!err && key_reference_valid) + { + *r_key_reference = key_reference; + *r_key_reference_valid = 1; + } + + if (err) + log_error ("p15: error parsing commonKeyAttributes at %d: %s\n", + where, gpg_strerror (err)); + return err; + +} + + +/* Read and parse the Private Key Directory Files. + * + * Sample object: + * SEQUENCE { + * SEQUENCE { -- commonObjectAttributes + * UTF8String 'SK.CH.DS' + * BIT STRING 6 unused bits + * '01'B (bit 0) -- flags: non-modifiable,private + * OCTET STRING --authid + * 07 + * } + * SEQUENCE { -- commonKeyAttributes + * OCTET STRING + * 01 + * BIT STRING 6 unused bits + * '1000000000'B (bit 9) -- keyusage: non-repudiation + * INTEGER 80 -- keyReference (optional) + * } + * [1] { -- keyAttributes + * SEQUENCE { -- privateRSAKeyAttributes + * SEQUENCE { -- objectValue + * OCTET STRING --path + * 3F 00 40 16 00 50 + * } + * INTEGER 1024 -- modulus + * } + * } + * } + * + * Sample part for EC objects: + * [1] { -- keyAttributes + * [1] { -- privateECkeyAttributes + * SEQUENCE { -- objectValue + * SEQUENCE { --path + * OCTET STRING 50 72 4B 03 + * } + * INTEGER 33 -- Not in PKCS#15v1.1, need to buy 7816-15? + * } + * } + */ +static gpg_error_t +read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result) +{ + gpg_error_t err; + unsigned char *buffer; + size_t buflen; + const unsigned char *p; + size_t n, objlen, hdrlen; + int class, tag, constructed, ndef; + prkdf_object_t prkdflist = NULL; + int i; + int recno = 1; + unsigned char *authid = NULL; + size_t authidlen = 0; + unsigned char *objid = NULL; + size_t objidlen = 0; + char *label = NULL; + int record_mode; + + err = read_first_record (app, fid, "PrKDF", &buffer, &buflen, &record_mode); + if (err) + return err; + + p = buffer; + n = buflen; + + /* Loop over the records. We stop as soon as we detect a new record + starting with 0x00 or 0xff as these values are commonly used to + pad data blocks and are no valid ASN.1 encoding. Note the + special handling for record mode at the end of the loop. */ + while (n && *p && *p != 0xff) + { + const unsigned char *pp; + size_t nn; + int where; + const char *errstr = NULL; + prkdf_object_t prkdf = NULL; + unsigned long ul; + keyusage_flags_t usageflags; + keyaccess_flags_t accessflags; + unsigned long key_reference = 0; + int key_reference_valid = 0; + int is_ecc = 0; + + where = __LINE__; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + ; + else if (objlen > n) + err = gpg_error (GPG_ERR_INV_OBJ); + else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) + ; /* PrivateRSAKeyAttributes */ + else if (class == CLASS_CONTEXT) + { + switch (tag) + { + case 0: is_ecc = 1; break; /* PrivateECKeyAttributes */ + case 1: errstr = "DH key objects are not supported"; break; + case 2: errstr = "DSA key objects are not supported"; break; + case 3: errstr = "KEA key objects are not supported"; break; + default: errstr = "unknown privateKeyObject"; break; + } + if (errstr) + goto parse_error; + } + else + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto parse_error; + } + + if (err) + { + log_error ("p15: error parsing PrKDF record: %s\n", + gpg_strerror (err)); + goto leave; + } + + pp = p; + nn = objlen; + p += objlen; + n -= objlen; + + /* Parse the commonObjectAttributes. */ + where = __LINE__; + xfree (authid); + xfree (label); + err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen, &label); + if (err) + goto parse_error; + + /* Parse the commonKeyAttributes. */ + where = __LINE__; + xfree (objid); + err = parse_common_key_attr (&pp, &nn, + &objid, &objidlen, + &usageflags, &accessflags, + &key_reference, &key_reference_valid); + if (err) + goto parse_error; + log_assert (objid); + + /* Skip commonPrivateKeyAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + if (class == CLASS_CONTEXT && tag == 0) + { + pp += objlen; + nn -= objlen; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + } + /* Parse the keyAttributes. */ + if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + nn = objlen; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + ; + else if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) + ; /* A typeAttribute always starts with a sequence. */ + else + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + nn = objlen; + + /* Check that the reference is a Path object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) + { + errstr = "unsupported reference type"; + goto parse_error; + } + nn = objlen; + + /* Parse the Path object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + /* Make sure that the next element is a non zero path and of + even length (FID are two bytes each). */ + if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING + || !objlen || (objlen & 1) ) + { + errstr = "invalid path reference"; + goto parse_error; + } + + /* Create a new PrKDF list item. */ + prkdf = xtrycalloc (1, (sizeof *prkdf + - sizeof(unsigned short) + + objlen/2 * sizeof(unsigned short))); + if (!prkdf) + { + err = gpg_error_from_syserror (); + goto leave; + } + prkdf->is_ecc = is_ecc; + + prkdf->objidlen = objidlen; + prkdf->objid = objid; + objid = NULL; + if (authid) + { + prkdf->authidlen = authidlen; + prkdf->authid = authid; + authid = NULL; + } + if (label) + { + prkdf->label = label; + label = NULL; + } + + prkdf->pathlen = objlen/2; + for (i=0; i < prkdf->pathlen; i++, pp += 2, nn -= 2) + prkdf->path[i] = ((pp[0] << 8) | pp[1]); + + prkdf->usageflags = usageflags; + prkdf->accessflags = accessflags; + prkdf->key_reference = key_reference; + prkdf->key_reference_valid = key_reference_valid; + + if (nn) + { + /* An index and length follows. */ + prkdf->have_off = 1; + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn + || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*pp++) & 0xff; + nn--; + } + prkdf->off = ul; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn + || class != CLASS_CONTEXT || tag != 0)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*pp++) & 0xff; + nn--; + } + prkdf->len = ul; + } + + /* The info is printed later in read_p15_info because we also + * want to look at the certificates. */ + + /* Put it into the list. */ + prkdf->next = prkdflist; + prkdflist = prkdf; + prkdf = NULL; + goto next_record; /* Ready with this record. */ + + parse_error: + log_error ("p15: error parsing PrKDF record at %d: %s - skipped\n", + where, errstr? errstr : gpg_strerror (err)); + if (prkdf) + { + xfree (prkdf->objid); + xfree (prkdf->authid); + xfree (prkdf->label); + xfree (prkdf); + } + err = 0; + + next_record: + /* If the card uses a record oriented file structure, read the + * next record. Otherwise we keep on parsing the current buffer. */ + recno++; + if (record_mode) + { + xfree (buffer); buffer = NULL; + err = select_and_read_record (app, 0, recno, "PrKDF", + &buffer, &buflen, NULL); + if (err) { + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = 0; + goto leave; + } + p = buffer; + n = buflen; + } + } /* End looping over all records. */ + + leave: + xfree (authid); + xfree (label); + xfree (objid); + xfree (buffer); + if (err) + release_prkdflist (prkdflist); + else + *result = prkdflist; + return err; +} + + +/* Read and parse the Public Keys Directory File. */ +static gpg_error_t +read_ef_pukdf (app_t app, unsigned short fid, pukdf_object_t *result) +{ + gpg_error_t err; + unsigned char *buffer; + size_t buflen; + const unsigned char *p; + size_t n, objlen, hdrlen; + int class, tag, constructed, ndef; + pukdf_object_t pukdflist = NULL; + int i; + int recno = 1; + unsigned char *authid = NULL; + size_t authidlen = 0; + unsigned char *objid = NULL; + size_t objidlen = 0; + char *label = NULL; + int record_mode; + + err = read_first_record (app, fid, "PuKDF", &buffer, &buflen, &record_mode); + if (err) + return err; + + p = buffer; + n = buflen; + + /* Loop over the records. We stop as soon as we detect a new record + * starting with 0x00 or 0xff as these values are commonly used to + * pad data blocks and are no valid ASN.1 encoding. Note the + * special handling for record mode at the end of the loop. */ + while (n && *p && *p != 0xff) + { + const unsigned char *pp; + size_t nn; + int where; + const char *errstr = NULL; + pukdf_object_t pukdf = NULL; + unsigned long ul; + keyusage_flags_t usageflags; + keyaccess_flags_t accessflags; + unsigned long key_reference = 0; + int key_reference_valid = 0; + + where = __LINE__; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + ; + else if (objlen > n) + err = gpg_error (GPG_ERR_INV_OBJ); + else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) + ; /* PublicRSAKeyAttributes */ + else if (class == CLASS_CONTEXT) + { + switch (tag) + { + case 0: break; /* EC key object */ + case 1: errstr = "DH key objects are not supported"; break; + case 2: errstr = "DSA key objects are not supported"; break; + case 3: errstr = "KEA key objects are not supported"; break; + default: errstr = "unknown publicKeyObject"; break; + } + if (errstr) + goto parse_error; + } + else + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto parse_error; + } + + if (err) + { + log_error ("p15: error parsing PuKDF record: %s\n", + gpg_strerror (err)); + goto leave; + } + + pp = p; + nn = objlen; + p += objlen; + n -= objlen; + + /* Parse the commonObjectAttributes. */ + where = __LINE__; + xfree (authid); + xfree (label); + err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen, &label); + if (err) + goto parse_error; + + /* Parse the commonKeyAttributes. */ + where = __LINE__; + xfree (objid); + err = parse_common_key_attr (&pp, &nn, + &objid, &objidlen, + &usageflags, &accessflags, + &key_reference, &key_reference_valid); + if (err) + goto parse_error; + log_assert (objid); + + /* Parse the subClassAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + if (class == CLASS_CONTEXT && tag == 0) + { + /* Skip this CommonPublicKeyAttribute. */ + pp += objlen; + nn -= objlen; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + } + /* We expect a typeAttribute. */ + if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; /* No typeAttribute. */ + nn = objlen; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + ; + else if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) + ; /* A typeAttribute always starts with a sequence. */ + else + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + nn = objlen; + + /* Check that the reference is a Path object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) + { + errstr = "unsupported reference type"; + goto parse_error; + } + nn = objlen; + + /* Parse the Path object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + /* Make sure that the next element is a non zero path and of + even length (FID are two bytes each). */ + if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING + || !objlen || (objlen & 1) ) + { + errstr = "invalid path reference"; + goto parse_error; + } + + /* Create a new PuKDF list item. */ + pukdf = xtrycalloc (1, (sizeof *pukdf + - sizeof(unsigned short) + + objlen/2 * sizeof(unsigned short))); + if (!pukdf) + { + err = gpg_error_from_syserror (); + goto leave; + } + pukdf->objidlen = objidlen; + pukdf->objid = objid; + objid = NULL; + if (authid) + { + pukdf->authidlen = authidlen; + pukdf->authid = authid; + authid = NULL; + } + if (label) + { + pukdf->label = label; + label = NULL; + } + + pukdf->pathlen = objlen/2; + for (i=0; i < pukdf->pathlen; i++, pp += 2, nn -= 2) + pukdf->path[i] = ((pp[0] << 8) | pp[1]); + + pukdf->usageflags = usageflags; + pukdf->accessflags = accessflags; + pukdf->key_reference = key_reference; + pukdf->key_reference_valid = key_reference_valid; + + if (nn) + { + /* An index and length follows. */ + pukdf->have_off = 1; + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn + || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*pp++) & 0xff; + nn--; + } + pukdf->off = ul; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn + || class != CLASS_CONTEXT || tag != 0)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*pp++) & 0xff; + nn--; + } + pukdf->len = ul; + } + + + if (opt.verbose) + { + log_info ("p15: PuKDF %04hX: id=", fid); + for (i=0; i < pukdf->objidlen; i++) + log_printf ("%02X", pukdf->objid[i]); + if (pukdf->label) + log_printf (" (%s)", pukdf->label); + log_info ("p15: path="); + for (i=0; i < pukdf->pathlen; i++) + log_printf ("%s%04hX", i?"/":"",pukdf->path[i]); + if (pukdf->have_off) + log_printf ("[%lu/%lu]", pukdf->off, pukdf->len); + if (pukdf->authid) + { + log_printf (" authid="); + for (i=0; i < pukdf->authidlen; i++) + log_printf ("%02X", pukdf->authid[i]); + } + if (pukdf->key_reference_valid) + log_printf (" keyref=0x%02lX", pukdf->key_reference); + if (pukdf->accessflags.any) + dump_keyaccess_flags (pukdf->accessflags); + dump_keyusage_flags (pukdf->usageflags); + log_printf ("\n"); + } + + /* Put it into the list. */ + pukdf->next = pukdflist; + pukdflist = pukdf; + pukdf = NULL; + goto next_record; /* Ready with this record. */ + + parse_error: + log_error ("p15: error parsing PuKDF record at %d: %s - skipped\n", + where, errstr? errstr : gpg_strerror (err)); + if (pukdf) + { + xfree (pukdf->objid); + xfree (pukdf->authid); + xfree (pukdf->label); + xfree (pukdf); + } + err = 0; + + next_record: + /* If the card uses a record oriented file structure, read the + * next record. Otherwise we keep on parsing the current buffer. */ + recno++; + if (record_mode) + { + xfree (buffer); buffer = NULL; + err = select_and_read_record (app, 0, recno, "PuKDF", + &buffer, &buflen, NULL); + if (err) { + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = 0; + goto leave; + } + p = buffer; + n = buflen; + } + } /* End looping over all records. */ + + leave: + xfree (authid); + xfree (label); + xfree (objid); + xfree (buffer); + if (err) + release_pukdflist (pukdflist); + else + *result = pukdflist; + return err; +} + + +/* Read and parse the Certificate Directory Files identified by FID. + On success a newlist of CDF object gets stored at RESULT and the + caller is then responsible of releasing this list. On error a + error code is returned and RESULT won't get changed. */ +static gpg_error_t +read_ef_cdf (app_t app, unsigned short fid, int cdftype, cdf_object_t *result) +{ + gpg_error_t err; + unsigned char *buffer; + size_t buflen; + const unsigned char *p; + size_t n, objlen, hdrlen; + int class, tag, constructed, ndef; + cdf_object_t cdflist = NULL; + int i; + int recno = 1; + unsigned char *authid = NULL; + size_t authidlen = 0; + char *label = NULL; + int record_mode; + + err = read_first_record (app, fid, "CDF", &buffer, &buflen, &record_mode); + if (err) + return err; + + p = buffer; + n = buflen; + + /* Loop over the records. We stop as soon as we detect a new record + starting with 0x00 or 0xff as these values are commonly used to + pad data blocks and are no valid ASN.1 encoding. Note the + special handling for record mode at the end of the loop. */ + while (n && *p && *p != 0xff) + { + const unsigned char *pp; + size_t nn; + int where; + const char *errstr = NULL; + cdf_object_t cdf = NULL; + unsigned long ul; + const unsigned char *objid; + size_t objidlen; + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + { + log_error ("p15: error parsing CDF record: %s\n", gpg_strerror (err)); + goto leave; + } + pp = p; + nn = objlen; + p += objlen; + n -= objlen; + + /* Parse the commonObjectAttributes. */ + where = __LINE__; + xfree (authid); + xfree (label); + err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen, &label); + if (err) + goto parse_error; + + /* Parse the commonCertificateAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + { + const unsigned char *ppp = pp; + size_t nnn = objlen; + + pp += objlen; + nn -= objlen; + + /* Get the Id. */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn + || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + objid = ppp; + objidlen = objlen; + } + + /* Parse the certAttribute. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + nn = objlen; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn + || class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + nn = objlen; + + /* Check that the reference is a Path object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) + { + errstr = "unsupported reference type"; + goto parse_error; + } + nn = objlen; + + /* Parse the Path object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + /* Make sure that the next element is a non zero path and of + even length (FID are two bytes each). */ + if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING + || !objlen || (objlen & 1) ) + { + errstr = "invalid path reference"; + goto parse_error; + } + /* Create a new CDF list item. */ + cdf = xtrycalloc (1, (sizeof *cdf + - sizeof(unsigned short) + + objlen/2 * sizeof(unsigned short))); + if (!cdf) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (authid) + { + cdf->authidlen = authidlen; + cdf->authid = authid; + authid = NULL; + } + if (label) + { + cdf->label = label; + label = NULL; + } + + cdf->objidlen = objidlen; + cdf->objid = xtrymalloc (objidlen); + if (!cdf->objid) + { + err = gpg_error_from_syserror (); + xfree (cdf); + goto leave; + } + memcpy (cdf->objid, objid, objidlen); + + cdf->pathlen = objlen/2; + for (i=0; i < cdf->pathlen; i++, pp += 2, nn -= 2) + cdf->path[i] = ((pp[0] << 8) | pp[1]); + + if (nn) + { + /* An index and length follows. */ + cdf->have_off = 1; + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn + || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*pp++) & 0xff; + nn--; + } + cdf->off = ul; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn + || class != CLASS_CONTEXT || tag != 0)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*pp++) & 0xff; + nn--; + } + cdf->len = ul; + } + + if (opt.verbose) + { + log_info ("p15: CDF-%c %04hX: id=", cdftype, fid); + for (i=0; i < cdf->objidlen; i++) + log_printf ("%02X", cdf->objid[i]); + if (cdf->label) + log_printf (" (%s)", cdf->label); + log_info ("p15: path="); + for (i=0; i < cdf->pathlen; i++) + log_printf ("%s%04hX", i?"/":"", cdf->path[i]); + if (cdf->have_off) + log_printf ("[%lu/%lu]", cdf->off, cdf->len); + if (cdf->authid) + { + log_printf (" authid="); + for (i=0; i < cdf->authidlen; i++) + log_printf ("%02X", cdf->authid[i]); + } + log_printf ("\n"); + } + + /* Put it into the list. */ + cdf->next = cdflist; + cdflist = cdf; + cdf = NULL; + goto next_record; /* Ready with this record. */ + + parse_error: + log_error ("p15: error parsing CDF record at %d: %s - skipped\n", + where, errstr? errstr : gpg_strerror (err)); + xfree (cdf); + err = 0; + + next_record: + xfree (authid); + xfree (label); + /* If the card uses a record oriented file structure, read the + * next record. Otherwise we keep on parsing the current buffer. */ + recno++; + if (record_mode) + { + xfree (buffer); buffer = NULL; + err = select_and_read_record (app, 0, recno, "CDF", + &buffer, &buflen, NULL); + if (err) { + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = 0; + goto leave; + } + p = buffer; + n = buflen; + } + } /* End loop over all records. */ + + leave: + xfree (authid); + xfree (label); + xfree (buffer); + if (err) + release_cdflist (cdflist); + else + *result = cdflist; + return err; +} + + +/* + * SEQUENCE { + * SEQUENCE { -- CommonObjectAttributes + * UTF8String 'specific PIN for DS' + * BIT STRING 0 unused bits + * '00000011'B + * } + * SEQUENCE { -- CommonAuthenticationObjectAttributes + * OCTET STRING + * 07 -- iD + * } + * + * [1] { -- typeAttributes + * SEQUENCE { -- PinAttributes + * BIT STRING 0 unused bits + * '0000100000110010'B -- local,initialized,needs-padding + * -- exchangeRefData + * ENUMERATED 1 -- ascii-numeric + * INTEGER 6 -- minLength + * INTEGER 6 -- storedLength + * INTEGER 8 -- maxLength + * [0] + * 02 -- pinReference + * GeneralizedTime 19/04/2002 12:12 GMT -- lastPinChange + * SEQUENCE { + * OCTET STRING + * 3F 00 40 16 -- path to DF of PIN + * } + * } + * } + * } + * + * Or for an authKey: + * + * [1] { -- typeAttributes + * SEQUENCE { -- AuthKeyAttributes + * BOOLEAN TRUE -- derivedKey + * OCTET STRING 02 -- authKeyId + * } + * } + * } +*/ +/* Read and parse an Authentication Object Directory File identified + by FID. On success a newlist of AODF objects gets stored at RESULT + and the caller is responsible of releasing this list. On error a + error code is returned and RESULT won't get changed. */ +static gpg_error_t +read_ef_aodf (app_t app, unsigned short fid, aodf_object_t *result) +{ + gpg_error_t err; + unsigned char *buffer; + size_t buflen; + const unsigned char *p; + size_t n, objlen, hdrlen; + int class, tag, constructed, ndef; + aodf_object_t aodflist = NULL; + int i; + int recno = 1; + int record_mode; + + err = read_first_record (app, fid, "AODF", &buffer, &buflen, &record_mode); + if (err) + return err; + + p = buffer; + n = buflen; + + /* Loop over the records. We stop as soon as we detect a new record + starting with 0x00 or 0xff as these values are commonly used to + pad data blocks and are no valid ASN.1 encoding. Note the + special handling for record mode at the end of the loop. */ + while (n && *p && *p != 0xff) + { + const unsigned char *pp; + size_t nn; + int where; + const char *errstr = NULL; + auth_type_t auth_type; + aodf_object_t aodf = NULL; + unsigned long ul; + const char *s; + + where = __LINE__; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + ; + else if (objlen > n) + err = gpg_error (GPG_ERR_INV_OBJ); + else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) + auth_type = AUTH_TYPE_PIN; /* PinAttributes */ + else if (class == CLASS_CONTEXT && tag == 1 ) + auth_type = AUTH_TYPE_AUTHKEY; /* AuthKeyAttributes */ + else if (class == CLASS_CONTEXT) + { + switch (tag) + { + case 0: errstr = "biometric auth types are not supported"; break; + case 2: errstr = "external auth type are not supported"; break; + default: errstr = "unknown privateKeyObject"; break; + } + goto parse_error; + } + else + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto parse_error; + } + + if (err) + { + log_error ("p15: error parsing AODF record: %s\n", + gpg_strerror (err)); + goto leave; + } + pp = p; + nn = objlen; + p += objlen; + n -= objlen; + + /* Allocate memory for a new AODF list item. */ + aodf = xtrycalloc (1, sizeof *aodf); + if (!aodf) + goto no_core; + aodf->fid = fid; + aodf->auth_type = auth_type; + + /* Parse the commonObjectAttributes. */ + where = __LINE__; + err = parse_common_obj_attr (&pp, &nn, &aodf->authid, &aodf->authidlen, + &aodf->label); + if (err) + goto parse_error; + + /* Parse the CommonAuthenticationObjectAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + { + const unsigned char *ppp = pp; + size_t nnn = objlen; + + pp += objlen; + nn -= objlen; + + /* Get the Id. */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn + || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + aodf->objidlen = objlen; + aodf->objid = xtrymalloc (objlen); + if (!aodf->objid) + goto no_core; + memcpy (aodf->objid, ppp, objlen); + } + + /* Parse the typeAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + nn = objlen; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + ; + else if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) + ; /* Okay */ + else + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + nn = objlen; + + if (auth_type == AUTH_TYPE_PIN) + { + /* PinFlags */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || !objlen + || class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + { + unsigned int bits, mask; + int unused, full; + + unused = *pp++; nn--; objlen--; + if ((!objlen && unused) || unused/8 > objlen) + { + err = gpg_error (GPG_ERR_ENCODING_PROBLEM); + goto parse_error; + } + full = objlen - (unused+7)/8; + unused %= 8; + mask = 0; + for (i=1; unused; i <<= 1, unused--) + mask |= i; + + /* The first octet */ + bits = 0; + if (objlen) + { + bits = *pp++; nn--; objlen--; + if (full) + full--; + else + { + bits &= ~mask; + mask = 0; + } + } + if ((bits & 0x80)) /* ASN.1 bit 0. */ + aodf->pinflags.case_sensitive = 1; + if ((bits & 0x40)) /* ASN.1 bit 1. */ + aodf->pinflags.local = 1; + if ((bits & 0x20)) + aodf->pinflags.change_disabled = 1; + if ((bits & 0x10)) + aodf->pinflags.unblock_disabled = 1; + if ((bits & 0x08)) + aodf->pinflags.initialized = 1; + if ((bits & 0x04)) + aodf->pinflags.needs_padding = 1; + if ((bits & 0x02)) + aodf->pinflags.unblocking_pin = 1; + if ((bits & 0x01)) + aodf->pinflags.so_pin = 1; + /* The second octet. */ + bits = 0; + if (objlen) + { + bits = *pp++; nn--; objlen--; + if (full) + full--; + else + { + bits &= ~mask; + } + } + if ((bits & 0x80)) + aodf->pinflags.disable_allowed = 1; + if ((bits & 0x40)) + aodf->pinflags.integrity_protected = 1; + if ((bits & 0x20)) + aodf->pinflags.confidentiality_protected = 1; + if ((bits & 0x10)) + aodf->pinflags.exchange_ref_data = 1; + /* Skip remaining bits. */ + pp += objlen; + nn -= objlen; + } + + /* PinType */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn + || class != CLASS_UNIVERSAL || tag != TAG_ENUMERATED)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (!err && objlen > sizeof (ul)) + err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); + if (err) + goto parse_error; + + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*pp++) & 0xff; + nn--; + } + aodf->pintype = ul; + + /* minLength */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn + || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (!err && objlen > sizeof (ul)) + err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); + if (err) + goto parse_error; + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*pp++) & 0xff; + nn--; + } + aodf->min_length = ul; + + /* storedLength */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn + || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (!err && objlen > sizeof (ul)) + err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); + if (err) + goto parse_error; + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*pp++) & 0xff; + nn--; + } + aodf->stored_length = ul; + + /* optional maxLength */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto ready; + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER) + { + if (objlen > sizeof (ul)) + { + err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); + goto parse_error; + } + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*pp++) & 0xff; + nn--; + } + aodf->max_length = ul; + aodf->max_length_valid = 1; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto ready; + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + } + + /* Optional pinReference. */ + if (class == CLASS_CONTEXT && tag == 0) + { + if (objlen > sizeof (ul)) + { + err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); + goto parse_error; + } + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*pp++) & 0xff; + nn--; + } + aodf->pin_reference = ul; + aodf->pin_reference_valid = 1; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto ready; + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + } + + /* Optional padChar. */ + if (class == CLASS_UNIVERSAL && tag == TAG_OCTET_STRING) + { + if (objlen != 1) + { + errstr = "padChar is not of size(1)"; + goto parse_error; + } + aodf->pad_char = *pp++; nn--; + aodf->pad_char_valid = 1; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto ready; + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + } + + /* Skip optional lastPinChange. */ + if (class == CLASS_UNIVERSAL && tag == TAG_GENERALIZED_TIME) + { + pp += objlen; + nn -= objlen; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto ready; + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + } + + /* Optional Path object. */ + if (class == CLASS_UNIVERSAL || tag == TAG_SEQUENCE) + { + const unsigned char *ppp = pp; + size_t nnn = objlen; + + pp += objlen; + nn -= objlen; + + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nnn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + /* Make sure that the next element has a path of even + * length (FIDs are two bytes each). */ + if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING + || (objlen & 1) ) + { + errstr = "invalid path reference"; + goto parse_error; + } + + aodf->pathlen = objlen/2; + aodf->path = xtrycalloc (aodf->pathlen, sizeof *aodf->path); + if (!aodf->path) + goto no_core; + for (i=0; i < aodf->pathlen; i++, ppp += 2, nnn -= 2) + aodf->path[i] = ((ppp[0] << 8) | ppp[1]); + + if (nnn) + { + /* An index and length follows. */ + aodf->have_off = 1; + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, + &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn + || class != CLASS_UNIVERSAL + || tag != TAG_INTEGER)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*ppp++) & 0xff; + nnn--; + } + aodf->off = ul; + + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, + &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn + || class != CLASS_CONTEXT || tag != 0)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*ppp++) & 0xff; + nnn--; + } + aodf->len = ul; + } + } + } + else if (auth_type == AUTH_TYPE_AUTHKEY) + { + + } + + /* Ignore further objects which might be there due to future + extensions of pkcs#15. */ + + ready: + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; + if (opt.verbose) + { + log_info ("p15: AODF %04hX: id=", fid); + for (i=0; i < aodf->objidlen; i++) + log_printf ("%02X", aodf->objid[i]); + if (aodf->label) + log_printf (" (%s)", aodf->label); + log_info ("p15: "); + log_printf (" %s", + aodf->auth_type == AUTH_TYPE_PIN? "pin" : + aodf->auth_type == AUTH_TYPE_AUTHKEY? "authkey" : "?"); + if (aodf->pathlen) + { + log_printf (" path="); + for (i=0; i < aodf->pathlen; i++) + log_printf ("%s%04hX", i?"/":"",aodf->path[i]); + if (aodf->have_off) + log_printf ("[%lu/%lu]", aodf->off, aodf->len); + } + if (aodf->authid) + { + log_printf (" authid="); + for (i=0; i < aodf->authidlen; i++) + log_printf ("%02X", aodf->authid[i]); + } + if (aodf->auth_type == AUTH_TYPE_PIN) + { + if (aodf->pin_reference_valid) + log_printf (" pinref=0x%02lX", aodf->pin_reference); + log_printf (" min=%lu", aodf->min_length); + log_printf (" stored=%lu", aodf->stored_length); + if (aodf->max_length_valid) + log_printf (" max=%lu", aodf->max_length); + if (aodf->pad_char_valid) + log_printf (" pad=0x%02x", aodf->pad_char); + + log_info ("p15: flags="); + s = ""; + if (aodf->pinflags.case_sensitive) + log_printf ("%scase_sensitive", s), s = ","; + if (aodf->pinflags.local) + log_printf ("%slocal", s), s = ","; + if (aodf->pinflags.change_disabled) + log_printf ("%schange_disabled", s), s = ","; + if (aodf->pinflags.unblock_disabled) + log_printf ("%sunblock_disabled", s), s = ","; + if (aodf->pinflags.initialized) + log_printf ("%sinitialized", s), s = ","; + if (aodf->pinflags.needs_padding) + log_printf ("%sneeds_padding", s), s = ","; + if (aodf->pinflags.unblocking_pin) + log_printf ("%sunblocking_pin", s), s = ","; + if (aodf->pinflags.so_pin) + log_printf ("%sso_pin", s), s = ","; + if (aodf->pinflags.disable_allowed) + log_printf ("%sdisable_allowed", s), s = ","; + if (aodf->pinflags.integrity_protected) + log_printf ("%sintegrity_protected", s), s = ","; + if (aodf->pinflags.confidentiality_protected) + log_printf ("%sconfidentiality_protected", s), s = ","; + if (aodf->pinflags.exchange_ref_data) + log_printf ("%sexchange_ref_data", s), s = ","; + { + char numbuf[50]; + const char *s2; + + switch (aodf->pintype) + { + case PIN_TYPE_BCD: s2 = "bcd"; break; + case PIN_TYPE_ASCII_NUMERIC: s2 = "ascii-numeric"; break; + case PIN_TYPE_UTF8: s2 = "utf8"; break; + case PIN_TYPE_HALF_NIBBLE_BCD: s2 = "half-nibble-bcd"; break; + case PIN_TYPE_ISO9564_1: s2 = "iso9564-1"; break; + default: + sprintf (numbuf, "%lu", (unsigned long)aodf->pintype); + s2 = numbuf; + } + log_printf ("%stype=%s", s, s2); s = ","; + } + } + else if (aodf->auth_type == AUTH_TYPE_AUTHKEY) + { + } + log_printf ("\n"); + } + + /* Put it into the list. */ + aodf->next = aodflist; + aodflist = aodf; + aodf = NULL; + goto next_record; /* Ready with this record. */ + + no_core: + err = gpg_error_from_syserror (); + release_aodf_object (aodf); + goto leave; + + parse_error: + log_error ("p15: error parsing AODF record at %d: %s - skipped\n", + where, errstr? errstr : gpg_strerror (err)); + err = 0; + release_aodf_object (aodf); + + next_record: + /* If the card uses a record oriented file structure, read the + * next record. Otherwise we keep on parsing the current buffer. */ + recno++; + if (record_mode) + { + xfree (buffer); buffer = NULL; + err = select_and_read_record (app, 0, recno, "AODF", + &buffer, &buflen, NULL); + if (err) { + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = 0; + goto leave; + } + p = buffer; + n = buflen; + } + } /* End looping over all records. */ + + leave: + xfree (buffer); + if (err) + release_aodflist (aodflist); + else + *result = aodflist; + return err; +} + + +/* Print the BIT STRING with the tokenflags from the TokenInfo. */ +static void +print_tokeninfo_tokenflags (const unsigned char *der, size_t derlen) +{ + unsigned int bits, mask; + int i, unused, full; + int other = 0; + + if (!derlen) + { + log_printf (" [invalid object]"); + return; + } + + unused = *der++; derlen--; + if ((!derlen && unused) || unused/8 > derlen) + { + log_printf (" [wrong encoding]"); + return; + } + full = derlen - (unused+7)/8; + unused %= 8; + mask = 0; + for (i=1; unused; i <<= 1, unused--) + mask |= i; + + /* First octet */ + if (derlen) + { + bits = *der++; derlen--; + if (full) + full--; + else + { + bits &= ~mask; + mask = 0; + } + } + else + bits = 0; + if ((bits & 0x80)) log_printf (" readonly"); + if ((bits & 0x40)) log_printf (" loginRequired"); + if ((bits & 0x20)) log_printf (" prnGeneration"); + if ((bits & 0x10)) log_printf (" eidCompliant"); + if ((bits & 0x08)) other = 1; + if ((bits & 0x04)) other = 1; + if ((bits & 0x02)) other = 1; + if ((bits & 0x01)) other = 1; + + /* Next octet. */ + if (derlen) + other = 1; + + if (other) + log_printf (" [unknown]"); +} + + + +/* Read and parse the EF(TokenInfo). + * + * TokenInfo ::= SEQUENCE { + * version INTEGER {v1(0)} (v1,...), + * serialNumber OCTET STRING, + * manufacturerID Label OPTIONAL, + * label [0] Label OPTIONAL, + * tokenflags TokenFlags, + * seInfo SEQUENCE OF SecurityEnvironmentInfo OPTIONAL, + * recordInfo [1] RecordInfo OPTIONAL, + * supportedAlgorithms [2] SEQUENCE OF AlgorithmInfo OPTIONAL, + * ..., + * issuerId [3] Label OPTIONAL, + * holderId [4] Label OPTIONAL, + * lastUpdate [5] LastUpdate OPTIONAL, + * preferredLanguage PrintableString OPTIONAL -- In accordance with + * -- IETF RFC 1766 + * } (CONSTRAINED BY { -- Each AlgorithmInfo.reference value must be unique --}) + * + * TokenFlags ::= BIT STRING { + * readOnly (0), + * loginRequired (1), + * prnGeneration (2), + * eidCompliant (3) + * } + * + * + * Sample EF 5032: + * 30 31 02 01 00 04 04 05 45 36 9F 0C 0C 44 2D 54 01......E6...D-T + * 72 75 73 74 20 47 6D 62 48 80 14 4F 66 66 69 63 rust GmbH..Offic + * 65 20 69 64 65 6E 74 69 74 79 20 63 61 72 64 03 e identity card. + * 02 00 40 20 63 61 72 64 03 02 00 40 00 00 00 00 ..@ card...@.... + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + * + * 0 49: SEQUENCE { + * 2 1: INTEGER 0 + * 5 4: OCTET STRING 05 45 36 9F + * 11 12: UTF8String 'D-Trust GmbH' + * 25 20: [0] 'Office identity card' + * 47 2: BIT STRING + * : '00000010'B (bit 1) + * : Error: Spurious zero bits in bitstring. + * : } + */ +static gpg_error_t +read_ef_tokeninfo (app_t app) +{ + gpg_error_t err; + unsigned char *buffer = NULL; + size_t buflen; + const unsigned char *p; + size_t n, objlen, hdrlen; + int class, tag, constructed, ndef; + unsigned long ul; + + release_tokeninfo (app); + app->app_local->card_product = CARD_PRODUCT_UNKNOWN; + + err = select_and_read_binary (app, 0x5032, "TokenInfo", &buffer, &buflen); + if (err) + return err; + + p = buffer; + n = buflen; + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + { + log_error ("p15: error parsing TokenInfo: %s\n", gpg_strerror (err)); + goto leave; + } + + n = objlen; + + /* Version. */ + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || tag != TAG_INTEGER)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*p++) & 0xff; + n--; + } + if (ul) + { + log_error ("p15: invalid version %lu in TokenInfo\n", ul); + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + + /* serialNumber. */ + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || tag != TAG_OCTET_STRING || !objlen)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + + xfree (app->app_local->serialno); + app->app_local->serialno = xtrymalloc (objlen); + if (!app->app_local->serialno) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (app->app_local->serialno, p, objlen); + app->app_local->serialnolen = objlen; + p += objlen; + n -= objlen; + + /* Is there an optional manufacturerID? */ + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || !objlen)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + if (class == CLASS_UNIVERSAL && tag == TAG_UTF8_STRING) + { + app->app_local->manufacturer_id = percent_data_escape (0, NULL, + p, objlen); + p += objlen; + n -= objlen; + /* Get next TLV. */ + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || !objlen)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + } + if (class == CLASS_CONTEXT && tag == 0) + { + app->app_local->token_label = percent_data_escape (0, NULL, p, objlen); + + p += objlen; + n -= objlen; + /* Get next TLV. */ + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || !objlen)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + } + /* The next is the mandatory tokenflags object. */ + if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING) + { + app->app_local->tokenflagslen = objlen; + app->app_local->tokenflags = xtrymalloc (objlen); + if (!app->app_local->tokenflags) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (app->app_local->tokenflags, p, objlen); + p += objlen; + n -= objlen; + } + + + leave: + xfree (buffer); + return err; +} + + +/* Get all the basic information from the pkcs#15 card, check the + structure and initialize our local context. This is used once at + application initialization. */ +static gpg_error_t +read_p15_info (app_t app) +{ + gpg_error_t err; + prkdf_object_t prkdf; + unsigned int flag; + + err = read_ef_tokeninfo (app); + if (err) + return err; + /* If we don't have a serial number yet but the TokenInfo provides + * one, use that. */ + if (!APP_CARD(app)->serialno && app->app_local->serialno) + { + APP_CARD(app)->serialno = app->app_local->serialno; + APP_CARD(app)->serialnolen = app->app_local->serialnolen; + app->app_local->serialno = NULL; + app->app_local->serialnolen = 0; + err = app_munge_serialno (APP_CARD(app)); + if (err) + return err; + } + + release_lists (app); + + if (IS_CARDOS_5 (app) + && app->app_local->manufacturer_id + && !ascii_strcasecmp (app->app_local->manufacturer_id, "GeNUA mbH")) + { + if (!app->app_local->card_product) + app->app_local->card_product = CARD_PRODUCT_GENUA; + } + + /* Read the ODF so that we know the location of all directory + files. */ + /* Fixme: We might need to get a non-standard ODF FID from TokenInfo. */ + err = read_ef_odf (app, 0x5031); + if (err) + return err; + + /* Read certificate information. */ + log_assert (!app->app_local->certificate_info); + log_assert (!app->app_local->trusted_certificate_info); + log_assert (!app->app_local->useful_certificate_info); + err = read_ef_cdf (app, app->app_local->odf.certificates, 'c', + &app->app_local->certificate_info); + if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) + err = read_ef_cdf (app, app->app_local->odf.trusted_certificates, 't', + &app->app_local->trusted_certificate_info); + if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) + err = read_ef_cdf (app, app->app_local->odf.useful_certificates, 'u', + &app->app_local->useful_certificate_info); + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = 0; + if (err) + return err; + + /* Read information about public keys. */ + log_assert (!app->app_local->public_key_info); + err = read_ef_pukdf (app, app->app_local->odf.public_keys, + &app->app_local->public_key_info); + if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) + err = read_ef_pukdf (app, app->app_local->odf.trusted_public_keys, + &app->app_local->public_key_info); + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = 0; + if (err) + return err; + + /* Read information about private keys. */ + log_assert (!app->app_local->private_key_info); + err = read_ef_prkdf (app, app->app_local->odf.private_keys, + &app->app_local->private_key_info); + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = 0; + if (err) + return err; + + /* Read information about authentication objects. */ + log_assert (!app->app_local->auth_object_info); + err = read_ef_aodf (app, app->app_local->odf.auth_objects, + &app->app_local->auth_object_info); + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = 0; + + + /* See whether we can extend the private key information using + * information from certificates. We use only the first matching + * certificate; if we want to change this strategy we should walk + * over the certificates and then find the corresponsing private key + * objects. */ + app->app_local->any_gpgusage = 0; + for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) + { + cdf_object_t cdf; + char *extusage; + char *p, *pend; + int seen, i; + + if (opt.debug) + log_printhex (prkdf->objid, prkdf->objidlen, "p15: prkdf id="); + if (cdf_object_from_objid (app, prkdf->objidlen, prkdf->objid, &cdf) + && cdf_object_from_label (app, prkdf->label, &cdf)) + continue; /* No matching certificate. */ + if (!cdf->cert) /* Read and parse the certificate. */ + readcert_by_cdf (app, cdf, NULL, NULL); + if (!cdf->cert) + continue; /* Unsupported or broken certificate. */ + + if (prkdf->is_ecc) + { + const char *oid; + const unsigned char *der; + size_t off, derlen, objlen, hdrlen; + int class, tag, constructed, ndef; + + for (i=0; !(err = ksba_cert_get_extension + (cdf->cert, i, &oid, NULL, &off, &derlen)); i++) + if (!strcmp (oid, "1.3.6.1.4.1.11591.2.2.10") ) + break; + if (!err && (der = ksba_cert_get_image (cdf->cert, NULL))) + { + der += off; + err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > derlen || tag != TAG_OCTET_STRING || ndef)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (!err) + { + derlen = objlen; + if (opt.debug) + log_printhex (der, derlen, "p15: OpenPGP KDF parms:"); + /* Store them if they match the known OpenPGP format. */ + if (derlen == 4 && der[0] == 3 && der[1] == 1) + memcpy (prkdf->ecdh_kdf, der, 4); + } + } + err = 0; + } + + if (ksba_cert_get_ext_key_usages (cdf->cert, &extusage)) + continue; /* No extended key usage attribute. */ + + if (opt.debug) + log_debug ("p15: ExtKeyUsages: %s\n", extusage); + p = extusage; + while (p && (pend=strchr (p, ':'))) + { + *pend++ = 0; + if ( *pend == 'C' ) /* Look only at critical usages. */ + { + prkdf->extusage.valid = 1; + seen = 1; + if (!strcmp (p, oid_kp_codeSigning) + || !strcmp (p, oid_kp_timeStamping) + || !strcmp (p, oid_kp_ocspSigning) + || !strcmp (p, oid_kp_ms_documentSigning) + || !strcmp (p, oid_kp_ms_old_documentSigning)) + prkdf->extusage.sign = 1; + else if (!strcmp (p, oid_kp_emailProtection)) + prkdf->extusage.encr = 1; + else if (!strcmp (p, oid_kp_serverAuth) + || !strcmp (p, oid_kp_clientAuth) + || !strcmp (p, oid_kp_ms_smartcardLogon)) + prkdf->extusage.auth = 1; + else if (!strcmp (p, oid_kp_anyExtendedKeyUsage)) + { + prkdf->extusage.sign = 1; + prkdf->extusage.encr = 1; + prkdf->extusage.auth = 1; + } + else + seen = 0; + } + else + seen = 0; + + /* Now check the gpg Usage. Here we don't care about + * critical or non-critical here. */ + if (seen) + ; /* No more need to look for other caps. */ + else if (!strcmp (p, oid_kp_gpgUsageCert)) + { + prkdf->gpgusage.cert = 1; + prkdf->gpgusage.any = 1; + app->app_local->any_gpgusage = 1; + } + else if (!strcmp (p, oid_kp_gpgUsageSign)) + { + prkdf->gpgusage.sign = 1; + prkdf->gpgusage.any = 1; + app->app_local->any_gpgusage = 1; + } + else if (!strcmp (p, oid_kp_gpgUsageEncr)) + { + prkdf->gpgusage.encr = 1; + prkdf->gpgusage.any = 1; + app->app_local->any_gpgusage = 1; + } + else if (!strcmp (p, oid_kp_gpgUsageAuth)) + { + prkdf->gpgusage.auth = 1; + prkdf->gpgusage.any = 1; + app->app_local->any_gpgusage = 1; + } + + /* Skip to next item. */ + if ((p = strchr (pend, '\n'))) + p++; + } + xfree (extusage); + } + + /* See whether we can figure out something about the card. */ + if (!app->app_local->card_product + && app->app_local->manufacturer_id + && !strcmp (app->app_local->manufacturer_id, "www.atos.net/cardos") + && IS_CARDOS_5 (app)) + { + /* This is a modern CARDOS card. */ + flag = 0; + for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) + { + if (prkdf->label && !strcmp (prkdf->label, "IdentityKey") + && prkdf->key_reference_valid && prkdf->key_reference == 1 + && !prkdf->authid) + flag |= 1; + else if (prkdf->label && !strcmp (prkdf->label, "TransportKey") + && prkdf->key_reference_valid && prkdf->key_reference==2 + && prkdf->authid) + flag |= 2; + } + if (flag == 3) + app->app_local->card_product = CARD_PRODUCT_RSCS; + + } + if (!app->app_local->card_product + && app->app_local->token_label + && !strncmp (app->app_local->token_label, "D-TRUST Card V3", 15) + && app->app_local->card_type == CARD_TYPE_CARDOS_50) + { + app->app_local->card_product = CARD_PRODUCT_DTRUST; + } + + + /* Now print the info about the PrKDF. */ + if (opt.verbose) + { + int i; + unsigned char *atr; + size_t atrlen; + const char *cardstr; + + for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) + { + log_info ("p15: PrKDF %04hX: id=", app->app_local->odf.private_keys); + for (i=0; i < prkdf->objidlen; i++) + log_printf ("%02X", prkdf->objid[i]); + if (prkdf->label) + log_printf (" (%s)", prkdf->label); + log_info ("p15: path="); + for (i=0; i < prkdf->pathlen; i++) + log_printf ("%s%04hX", i?"/":"",prkdf->path[i]); + if (prkdf->have_off) + log_printf ("[%lu/%lu]", prkdf->off, prkdf->len); + if (prkdf->authid) + { + log_printf (" authid="); + for (i=0; i < prkdf->authidlen; i++) + log_printf ("%02X", prkdf->authid[i]); + } + if (prkdf->key_reference_valid) + log_printf (" keyref=0x%02lX", prkdf->key_reference); + log_printf (" type=%s", prkdf->is_ecc? "ecc":"rsa"); + if (prkdf->accessflags.any) + dump_keyaccess_flags (prkdf->accessflags); + dump_keyusage_flags (prkdf->usageflags); + if (prkdf->extusage.valid) + log_info ("p15: extusage=%s%s%s%s%s", + prkdf->extusage.sign? "sign":"", + (prkdf->extusage.sign + && prkdf->extusage.encr)?",":"", + prkdf->extusage.encr? "encr":"", + ((prkdf->extusage.sign || prkdf->extusage.encr) + && prkdf->extusage.auth)?",":"", + prkdf->extusage.auth? "auth":""); + if (prkdf->gpgusage.any) + dump_gpgusage_flags (prkdf->gpgusage); + + log_printf ("\n"); + } + + log_info ("p15: TokenInfo:\n"); + if (app->app_local->serialno) + { + log_info ("p15: serialNumber .: "); + log_printhex (app->app_local->serialno, app->app_local->serialnolen, + ""); + } + else if (APP_CARD(app)->serialno) + { + log_info ("p15: serialNumber .: "); + log_printhex (APP_CARD(app)->serialno, APP_CARD(app)->serialnolen, + ""); + } + + if (app->app_local->manufacturer_id) + log_info ("p15: manufacturerID: %s\n", + app->app_local->manufacturer_id); + if (app->app_local->card_product) + { + cardstr = cardproduct2str (app->app_local->card_product); + log_info ("p15: product ......: %d%s%s%s\n", + app->app_local->card_product, + *cardstr? " (":"", cardstr, *cardstr? ")":""); + } + if (app->app_local->token_label) + log_info ("p15: label ........: %s\n", app->app_local->token_label); + if (app->app_local->tokenflags) + { + log_info ("p15: tokenflags ...:"); + print_tokeninfo_tokenflags (app->app_local->tokenflags, + app->app_local->tokenflagslen); + log_printf ("\n"); + } + + log_info ("p15: atr ..........: "); + atr = apdu_get_atr (app_get_slot (app), &atrlen); + if (!atr) + log_printf ("[error]\n"); + else + { + log_printhex (atr, atrlen, ""); + xfree (atr); + } + + cardstr = cardtype2str (app->app_local->card_type); + log_info ("p15: cardtype .....: %d%s%s%s\n", + app->app_local->card_type, + *cardstr? " (":"", cardstr, *cardstr? ")":""); + } + + return err; +} + + +/* Helper to do_learn_status: Send information about all certificates + listed in CERTINFO back. Use CERTTYPE as type of the + certificate. */ +static gpg_error_t +send_certinfo (app_t app, ctrl_t ctrl, const char *certtype, + cdf_object_t certinfo) +{ + for (; certinfo; certinfo = certinfo->next) + { + char *buf, *p; + const char *label; + char *labelbuf; + + buf = xtrymalloc (9 + certinfo->objidlen*2 + 1); + if (!buf) + return gpg_error_from_syserror (); + p = stpcpy (buf, "P15"); + if (app->app_local->home_df != DEFAULT_HOME_DF) + { + snprintf (p, 6, "-%04X", + (unsigned int)(app->app_local->home_df & 0xffff)); + p += 5; + } + p = stpcpy (p, "."); + bin2hex (certinfo->objid, certinfo->objidlen, p); + + label = (certinfo->label && *certinfo->label)? certinfo->label : "-"; + labelbuf = percent_data_escape (0, NULL, label, strlen (label)); + if (!labelbuf) + { + xfree (buf); + return gpg_error_from_syserror (); + } + + send_status_info (ctrl, "CERTINFO", + certtype, strlen (certtype), + buf, strlen (buf), + labelbuf, strlen (labelbuf), + NULL, (size_t)0); + xfree (buf); + xfree (labelbuf); + } + return 0; +} + + +/* Get the keygrip of the private key object PRKDF. On success the + * keygrip, the algo and the length are stored in the KEYGRIP, + * KEYALGO, and KEYNBITS fields of the PRKDF object. */ +static gpg_error_t +keygrip_from_prkdf (app_t app, prkdf_object_t prkdf) +{ + gpg_error_t err; + cdf_object_t cdf; + unsigned char *der; + size_t derlen; + ksba_cert_t cert; + gcry_sexp_t s_pkey = NULL; + + /* Easy if we got a cached version. */ + if (prkdf->keygrip_valid) + return 0; + + xfree (prkdf->common_name); + prkdf->common_name = NULL; + xfree (prkdf->serial_number); + prkdf->serial_number = NULL; + + /* We could have also checked whether a public key directory file + * and a matching public key for PRKDF is available. This would + * make extraction of the key faster. However, this way we don't + * have a way to look at extended key attributes to check gpgusage. + * FIXME: Add public key lookup if no certificate was found. */ + + /* Look for a matching certificate. A certificate matches if the id + * matches the one of the private key info. If none was found we + * also try to match on the label. */ + err = cdf_object_from_objid (app, prkdf->objidlen, prkdf->objid, &cdf); + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + err = cdf_object_from_label (app, prkdf->label, &cdf); + if (!err && !cdf) + err = gpg_error (GPG_ERR_NOT_FOUND); + if (err) + goto leave; + + err = readcert_by_cdf (app, cdf, &der, &derlen); + if (err) + goto leave; + + err = ksba_cert_new (&cert); + if (!err) + err = ksba_cert_init_from_mem (cert, der, derlen); + xfree (der); + if (!err) + err = app_help_get_keygrip_string (cert, prkdf->keygrip, &s_pkey, NULL); + if (!err && !prkdf->gpgusage.any) + { + /* Try to get the CN and the SerialNumber from the certificate; + * we use a very simple approach here which should work in many + * cases. Eventually we should add a rfc-2253 parser into + * libksba to make it easier to parse such a string. + * We don't do this if this is marked as gpg key and thus + * has only a dummy certificate. + * + * First example string: + * "CN=Otto Schily,O=Miniluv,C=DE" + * Second example string: + * "2.5.4.5=#445452323030303236333531,2.5.4.4=#4B6F6368," + * "2.5.4.42=#5765726E6572,CN=Werner Koch,OU=For testing" + * " purposes only!,O=Testorganisation,C=DE" + */ + char *dn = ksba_cert_get_subject (cert, 0); + if (dn) + { + char *p, *pend, *buf; + + p = strstr (dn, "CN="); + if (p && (p==dn || p[-1] == ',')) + { + p += 3; + if (!(pend = strchr (p, ','))) + pend = p + strlen (p); + if (pend && pend > p + && (prkdf->common_name = xtrymalloc ((pend - p) + 1))) + { + memcpy (prkdf->common_name, p, pend-p); + prkdf->common_name[pend-p] = 0; + } + } + p = strstr (dn, "2.5.4.5=#"); /* OID of the SerialNumber */ + if (p && (p==dn || p[-1] == ',')) + { + p += 9; + if (!(pend = strchr (p, ','))) + pend = p + strlen (p); + if (pend && pend > p + && (buf = xtrymalloc ((pend - p) + 1))) + { + memcpy (buf, p, pend-p); + buf[pend-p] = 0; + if (!hex2str (buf, buf, strlen (buf)+1, NULL)) + xfree (buf); /* Invalid hex encoding. */ + else + prkdf->serial_number = buf; + } + } + ksba_free (dn); + } + } + + if (!err && !prkdf->keytime) + { + ksba_isotime_t isot; + time_t t; + + ksba_cert_get_validity (cert, 0, isot); + t = isotime2epoch (isot); + prkdf->keytime = (t == (time_t)(-1))? 0 : (u32)t; + prkdf->have_keytime = 1; + } + + if (!err && !prkdf->keyalgostr) + prkdf->keyalgostr = pubkey_algo_string (s_pkey, NULL); + + ksba_cert_release (cert); + if (err) + goto leave; + + prkdf->keyalgo = get_pk_algo_from_key (s_pkey); + if (!prkdf->keyalgo) + { + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + goto leave; + } + + prkdf->keynbits = gcry_pk_get_nbits (s_pkey); + if (!prkdf->keynbits) + { + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + goto leave; + } + + prkdf->keygrip_valid = 1; /* Yeah, got everything. */ + + leave: + gcry_sexp_release (s_pkey); + return err; +} + + +/* Return a malloced keyref string for PRKDF. Returns NULL on + * malloc failure. */ +static char * +keyref_from_prkdf (app_t app, prkdf_object_t prkdf) +{ + char *buf, *p; + + buf = xtrymalloc (4 + 5 + prkdf->objidlen*2 + 1); + if (!buf) + return NULL; + p = stpcpy (buf, "P15"); + if (app->app_local->home_df != DEFAULT_HOME_DF) + { + snprintf (p, 6, "-%04X", + (unsigned int)(app->app_local->home_df & 0xffff)); + p += 5; + } + p = stpcpy (p, "."); + bin2hex (prkdf->objid, prkdf->objidlen, p); + return buf; +} + + +/* Helper to do_learn_status: Send information about all known + keypairs back. FIXME: much code duplication from + send_certinfo(). */ +static gpg_error_t +send_keypairinfo (app_t app, ctrl_t ctrl, prkdf_object_t prkdf) +{ + gpg_error_t err; + + for (; prkdf; prkdf = prkdf->next) + { + char *buf; + int j; + + buf = keyref_from_prkdf (app, prkdf); + if (!buf) + return gpg_error_from_syserror (); + + err = keygrip_from_prkdf (app, prkdf); + if (err) + { + log_error ("p15: error getting keygrip from "); + for (j=0; j < prkdf->pathlen; j++) + log_printf ("%s%04hX", j?"/":"", prkdf->path[j]); + log_printf (": %s\n", gpg_strerror (err)); + } + else + { + char usage[5]; + char keytime[20]; + const char *algostr; + size_t usagelen = 0; + + if (prkdf->gpgusage.any) + { + if (prkdf->gpgusage.sign) + usage[usagelen++] = 's'; + if (prkdf->gpgusage.cert) + usage[usagelen++] = 'c'; + if (prkdf->gpgusage.encr) + usage[usagelen++] = 'e'; + if (prkdf->gpgusage.auth) + usage[usagelen++] = 'a'; + } + else + { + if ((prkdf->usageflags.sign + || prkdf->usageflags.sign_recover + || prkdf->usageflags.non_repudiation) + && (!prkdf->extusage.valid + || prkdf->extusage.sign)) + usage[usagelen++] = 's'; + if ((prkdf->usageflags.sign + || prkdf->usageflags.sign_recover) + && (!prkdf->extusage.valid || prkdf->extusage.sign)) + usage[usagelen++] = 'c'; + if ((prkdf->usageflags.decrypt + || prkdf->usageflags.unwrap) + && (!prkdf->extusage.valid || prkdf->extusage.encr)) + usage[usagelen++] = 'e'; + if ((prkdf->usageflags.sign + || prkdf->usageflags.sign_recover) + && (!prkdf->extusage.valid || prkdf->extusage.auth)) + usage[usagelen++] = 'a'; + } + + log_assert (strlen (prkdf->keygrip) == 40); + if (prkdf->keytime && prkdf->have_keytime) + snprintf (keytime, sizeof keytime, "%lu", + (unsigned long)prkdf->keytime); + else + strcpy (keytime, "-"); + + algostr = prkdf->keyalgostr; + + send_status_info (ctrl, "KEYPAIRINFO", + prkdf->keygrip, 2*KEYGRIP_LEN, + buf, strlen (buf), + usage, usagelen, + keytime, strlen (keytime), + algostr, strlen (algostr?algostr:""), + NULL, (size_t)0); + } + xfree (buf); + } + return 0; +} + + + +/* This is the handler for the LEARN command. Note that if + * APP_LEARN_FLAG_REREAD is set and this function returns an error, + * the caller must deinitialize this application. */ +static gpg_error_t +do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) +{ + gpg_error_t err; + + if (flags & APP_LEARN_FLAG_REREAD) + { + err = read_p15_info (app); + if (err) + return err; + } + + if ((flags & APP_LEARN_FLAG_KEYPAIRINFO)) + err = 0; + else + { + err = do_getattr (app, ctrl, "MANUFACTURER"); + if (!err) + err = send_certinfo (app, ctrl, "100", + app->app_local->certificate_info); + if (!err) + err = send_certinfo (app, ctrl, "101", + app->app_local->trusted_certificate_info); + if (!err) + err = send_certinfo (app, ctrl, "102", + app->app_local->useful_certificate_info); + } + + if (!err) + err = send_keypairinfo (app, ctrl, app->app_local->private_key_info); + + if (!err) + err = do_getattr (app, ctrl, "CHV-STATUS"); + if (!err) + err = do_getattr (app, ctrl, "CHV-LABEL"); + + + return err; +} + + +/* Read a certificate using the information in CDF and return the + * certificate in a newly malloced buffer R_CERT and its length + * R_CERTLEN. Also parses the certificate. R_CERT and R_CERTLEN may + * be NULL to do just the caching. */ +static gpg_error_t +readcert_by_cdf (app_t app, cdf_object_t cdf, + unsigned char **r_cert, size_t *r_certlen) +{ + gpg_error_t err; + unsigned char *buffer = NULL; + const unsigned char *p, *save_p; + size_t buflen, n; + int class, tag, constructed, ndef; + size_t totobjlen, objlen, hdrlen; + int rootca; + int i; + + if (r_cert) + *r_cert = NULL; + if (r_certlen) + *r_certlen = 0; + + /* First check whether it has been cached. */ + if (cdf->cert) + { + const unsigned char *image; + size_t imagelen; + + if (!r_cert || !r_certlen) + return 0; /* Caller does not actually want the result. */ + + image = ksba_cert_get_image (cdf->cert, &imagelen); + if (!image) + { + log_error ("p15: ksba_cert_get_image failed\n"); + return gpg_error (GPG_ERR_INTERNAL); + } + *r_cert = xtrymalloc (imagelen); + if (!*r_cert) + return gpg_error_from_syserror (); + memcpy (*r_cert, image, imagelen); + *r_certlen = imagelen; + return 0; + } + + if (DBG_CARD) + { + log_info ("p15: Reading CDF: id="); + for (i=0; i < cdf->objidlen; i++) + log_printf ("%02X", cdf->objid[i]); + if (cdf->label) + log_printf (" (%s)", cdf->label); + log_info ("p15: path="); + for (i=0; i < cdf->pathlen; i++) + log_printf ("%s%04hX", i?"/":"", cdf->path[i]); + if (cdf->have_off) + log_printf ("[%lu/%lu]", cdf->off, cdf->len); + if (cdf->authid) + { + log_printf (" authid="); + for (i=0; i < cdf->authidlen; i++) + log_printf ("%02X", cdf->authid[i]); + } + log_printf ("\n"); + } + + /* Read the entire file. fixme: This could be optimized by first + reading the header to figure out how long the certificate + actually is. */ + err = select_ef_by_path (app, cdf->path, cdf->pathlen); + if (err) + goto leave; + + if (app->app_local->no_extended_mode || !cdf->len) + err = iso7816_read_binary_ext (app_get_slot (app), 0, cdf->off, 0, + &buffer, &buflen, NULL); + else + err = iso7816_read_binary_ext (app_get_slot (app), 1, cdf->off, cdf->len, + &buffer, &buflen, NULL); + if (!err && (!buflen || *buffer == 0xff)) + err = gpg_error (GPG_ERR_NOT_FOUND); + if (err) + { + log_error ("p15: error reading certificate id="); + for (i=0; i < cdf->objidlen; i++) + log_printf ("%02X", cdf->objid[i]); + log_printf (" at "); + for (i=0; i < cdf->pathlen; i++) + log_printf ("%s%04hX", i? "/":"", cdf->path[i]); + log_printf (": %s\n", gpg_strerror (err)); + goto leave; + } + + /* Check whether this is really a certificate. */ + p = buffer; + n = buflen; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + + if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) + rootca = 0; + else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed ) + rootca = 1; + else + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + totobjlen = objlen + hdrlen; + log_assert (totobjlen <= buflen); + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + + if (!rootca + && class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed) + { + /* The certificate seems to be contained in a userCertificate + container. Skip this and assume the following sequence is + the certificate. */ + if (n < objlen) + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + p += objlen; + n -= objlen; + save_p = p; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) ) + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + totobjlen = objlen + hdrlen; + log_assert (save_p + totobjlen <= buffer + buflen); + memmove (buffer, save_p, totobjlen); + } + + + /* Try to parse and cache the certificate. */ + err = ksba_cert_new (&cdf->cert); + if (!err) + { + err = ksba_cert_init_from_mem (cdf->cert, buffer, totobjlen); + if (!err) /* Call us to use the just cached cert object. */ + err = readcert_by_cdf (app, cdf, r_cert, r_certlen); + if (err) + { + ksba_cert_release (cdf->cert); + cdf->cert = NULL; + } + + } + if (err) + { + log_error ("p15: caching certificate failed: %s\n", + gpg_strerror (err)); + /* We return the certificate anyway so that the caller has a + * chance to get an even unsupported or broken certificate. */ + if (r_cert && r_certlen) + { + *r_cert = buffer; + buffer = NULL; + *r_certlen = totobjlen; + } + } + + leave: + xfree (buffer); + return err; +} + + +/* Handler for the READCERT command. + + Read the certificate with id CERTID (as returned by learn_status in + the CERTINFO status lines) and return it in the freshly allocated + buffer to be stored at R_CERT and its length at R_CERTLEN. A error + code will be returned on failure and R_CERT and R_CERTLEN will be + set to (NULL,0). */ +static gpg_error_t +do_readcert (app_t app, const char *certid, + unsigned char **r_cert, size_t *r_certlen) +{ + gpg_error_t err; + cdf_object_t cdf; + + *r_cert = NULL; + *r_certlen = 0; + err = cdf_object_from_certid (app, certid, &cdf); + if (!err) + err = readcert_by_cdf (app, cdf, r_cert, r_certlen); + return err; +} + + +/* Sort helper for an array of authentication objects. */ +static int +compare_aodf_objid (const void *arg_a, const void *arg_b) +{ + const aodf_object_t a = *(const aodf_object_t *)arg_a; + const aodf_object_t b = *(const aodf_object_t *)arg_b; + int rc; + + rc = memcmp (a->objid, b->objid, + a->objidlen < b->objidlen? a->objidlen : b->objidlen); + if (!rc) + { + if (a->objidlen < b->objidlen) + rc = -1; + else if (a->objidlen > b->objidlen) + rc = 1; + } + return rc; +} + + +static void +send_key_fpr_line (ctrl_t ctrl, int number, const unsigned char *fpr) +{ + char buf[41]; + char numbuf[25]; + + bin2hex (fpr, 20, buf); + if (number == -1) + *numbuf = 0; /* Don't print the key number */ + else + snprintf (numbuf, sizeof numbuf, "%d", number); + send_status_info (ctrl, "KEY-FPR", + numbuf, (size_t)strlen(numbuf), + buf, (size_t)strlen (buf), + NULL, 0); +} + + +/* If possible Emit a FPR-KEY status line for the private key object + * PRKDF using NUMBER as index. */ +static void +send_key_fpr (app_t app, ctrl_t ctrl, prkdf_object_t prkdf, int number) +{ + gpg_error_t err; + cdf_object_t cdf; + unsigned char *pk, *fixed_pk; + size_t pklen, fixed_pklen; + const unsigned char *m, *e, *q; + size_t mlen, elen, qlen; + unsigned char fpr20[20]; + + if (cdf_object_from_objid (app, prkdf->objidlen, prkdf->objid, &cdf) + && cdf_object_from_label (app, prkdf->label, &cdf)) + return; + if (!cdf->cert) + readcert_by_cdf (app, cdf, NULL, NULL); + if (!cdf->cert) + return; + if (!prkdf->have_keytime) + return; + pk = ksba_cert_get_public_key (cdf->cert); + if (!pk) + return; + pklen = gcry_sexp_canon_len (pk, 0, NULL, &err); + + if (uncompress_ecc_q_in_canon_sexp (pk, pklen, &fixed_pk, &fixed_pklen)) + { + xfree (pk); + return; + } + if (fixed_pk) + { + xfree (pk); pk = NULL; + pk = fixed_pk; + pklen = fixed_pklen; + } + + switch (prkdf->keyalgo) + { + case GCRY_PK_RSA: + if (!get_rsa_pk_from_canon_sexp (pk, pklen, + &m, &mlen, &e, &elen) + && !compute_openpgp_fpr_rsa (4, + prkdf->keytime, + m, mlen, e, elen, + fpr20, NULL)) + send_key_fpr_line (ctrl, number, fpr20); + break; + + case GCRY_PK_ECC: + case GCRY_PK_ECDSA: + case GCRY_PK_ECDH: + case GCRY_PK_EDDSA: + /* Note that NUMBER 2 indicates the encryption key. */ + if (!get_ecc_q_from_canon_sexp (pk, pklen, &q, &qlen) + && !compute_openpgp_fpr_ecc (4, + prkdf->keytime, + prkdf->keyalgostr, + number == 2, + q, qlen, + prkdf->ecdh_kdf, 4, + fpr20, NULL)) + send_key_fpr_line (ctrl, number, fpr20); + break; + + default: /* No Fingerprint for an unknown algo. */ + break; + + } + xfree (pk); +} + + +/* Implement the GETATTR command. This is similar to the LEARN + command but returns just one value via the status interface. */ +static gpg_error_t +do_getattr (app_t app, ctrl_t ctrl, const char *name) +{ + gpg_error_t err; + prkdf_object_t prkdf; + + if (!strcmp (name, "$AUTHKEYID") + || !strcmp (name, "$ENCRKEYID") + || !strcmp (name, "$SIGNKEYID")) + { + char *buf; + + /* We return the ID of the first private key capable of the + * requested action. If any gpgusage flag has been set for the + * card we consult the gpgusage flags and not the regualr usage + * flags. + */ + /* FIXME: This changed: Note that we do not yet return + * non_repudiation keys for $SIGNKEYID because our D-Trust + * testcard uses rsaPSS, which is not supported by gpgsm and not + * covered by the VS-NfD approval. */ + for (prkdf = app->app_local->private_key_info; prkdf; + prkdf = prkdf->next) + { + if (app->app_local->any_gpgusage) + { + if ((name[1] == 'A' && prkdf->gpgusage.auth) + || (name[1] == 'E' && prkdf->gpgusage.encr) + || (name[1] == 'S' && prkdf->gpgusage.sign)) + break; + } + else + { + if ((name[1] == 'A' && (prkdf->usageflags.sign + || prkdf->usageflags.sign_recover)) + || (name[1] == 'E' && (prkdf->usageflags.decrypt + || prkdf->usageflags.unwrap)) + || (name[1] == 'S' && (prkdf->usageflags.sign + || prkdf->usageflags.sign_recover))) + break; + } + } + if (prkdf) + { + buf = keyref_from_prkdf (app, prkdf); + if (!buf) + return gpg_error_from_syserror (); + + send_status_info (ctrl, name, buf, strlen (buf), NULL, 0); + xfree (buf); + } + return 0; + } + else if (!strcmp (name, "$DISPSERIALNO")) + { + /* For certain cards we return special IDs. There is no + general rule for it so we need to decide case by case. */ + if (app->app_local->card_type == CARD_TYPE_BELPIC) + { + /* The eID card has a card number printed on the front matter + which seems to be a good indication. */ + unsigned char *buffer; + const unsigned char *p; + size_t buflen, n; + unsigned short path[] = { 0x3F00, 0xDF01, 0x4031 }; + + err = select_ef_by_path (app, path, DIM(path) ); + if (!err) + err = iso7816_read_binary (app_get_slot (app), 0, 0, + &buffer, &buflen); + if (err) + { + log_error ("p15: error accessing EF(ID): %s\n", + gpg_strerror (err)); + return err; + } + + p = find_tlv (buffer, buflen, 1, &n); + if (p && n == 12) + { + char tmp[12+2+1]; + memcpy (tmp, p, 3); + tmp[3] = '-'; + memcpy (tmp+4, p+3, 7); + tmp[11] = '-'; + memcpy (tmp+12, p+10, 2); + tmp[14] = 0; + send_status_info (ctrl, name, tmp, strlen (tmp), NULL, 0); + xfree (buffer); + return 0; + } + xfree (buffer); + } + else + { + char *sn; + + /* We use the first private key object which has a serial + * number set. If none was found, we parse the first + * object and see whether this has then a serial number. */ + for (prkdf = app->app_local->private_key_info; prkdf; + prkdf = prkdf->next) + if (prkdf->serial_number) + break; + if (!prkdf && app->app_local->private_key_info) + { + prkdf = app->app_local->private_key_info; + keygrip_from_prkdf (app, prkdf); + if (!prkdf->serial_number) + prkdf = NULL; + } + sn = get_dispserialno (app, prkdf); + /* Unless there is a bogus S/N in the cert, or the product + * has a different strategy for the display-s/n, we should + * have a suitable one from the cert now. */ + if (sn) + { + err = send_status_printf (ctrl, name, "%s", sn); + xfree (sn); + return err; + } + } + /* No abbreviated serial number. */ + } + else if (!strcmp (name, "MANUFACTURER")) + { + if (app->app_local->manufacturer_id + && !strchr (app->app_local->manufacturer_id, '[') + && app->app_local->card_product) + return send_status_printf (ctrl, "MANUFACTURER", "0 %s [%s]", + app->app_local->manufacturer_id, + cardproduct2str (app->app_local->card_product)); + else if (app->app_local->manufacturer_id) + return send_status_printf (ctrl, "MANUFACTURER", "0 %s", + app->app_local->manufacturer_id); + else + return 0; + } + else if (!strcmp (name, "CHV-STATUS") || !strcmp (name, "CHV-LABEL")) + { + int is_label = (name[4] == 'L'); + aodf_object_t aodf; + aodf_object_t aodfarray[16]; + int naodf = 0; + membuf_t mb; + char *p; + int i; + + /* Put the AODFs into an array for easier sorting. Note that we + * handle onl the first 16 encountrer which should be more than + * enough. */ + for (aodf = app->app_local->auth_object_info; + aodf && naodf < DIM(aodfarray); aodf = aodf->next) + if (aodf->objidlen && aodf->pin_reference_valid) + aodfarray[naodf++] = aodf; + qsort (aodfarray, naodf, sizeof *aodfarray, compare_aodf_objid); + + init_membuf (&mb, 256); + for (i = 0; i < naodf; i++) + { + /* int j; */ + /* log_debug ("p15: AODF[%d] pinref=%lu id=", */ + /* i, aodfarray[i]->pin_reference); */ + /* for (j=0; j < aodfarray[i]->objidlen; j++) */ + /* log_printf ("%02X", aodfarray[i]->objid[j]); */ + /* Note that there is no need to percent escape the label + * because all white space have been replaced by '_'. */ + if (is_label) + put_membuf_printf (&mb, "%s%s", i? " ":"", + (aodfarray[i]->label + && *aodfarray[i]->label)? + aodfarray[i]->label:"X"); + else + put_membuf_printf + (&mb, "%s%d", i? " ":"", + iso7816_verify_status (app_get_slot (app), + aodfarray[i]->pin_reference)); + } + put_membuf( &mb, "", 1); + p = get_membuf (&mb, NULL); + if (!p) + return gpg_error_from_syserror (); + err = send_status_direct (ctrl, is_label? "CHV-LABEL":"CHV-STATUS", p); + xfree (p); + return err; + } + else if (!strcmp (name, "KEY-LABEL")) + { + /* Send KEY-LABEL lines for all private key objects. */ + const char *label; + char *idbuf, *labelbuf; + + for (prkdf = app->app_local->private_key_info; prkdf; + prkdf = prkdf->next) + { + idbuf = keyref_from_prkdf (app, prkdf); + if (!idbuf) + return gpg_error_from_syserror (); + + label = (prkdf->label && *prkdf->label)? prkdf->label : "-"; + labelbuf = percent_data_escape (0, NULL, label, strlen (label)); + if (!labelbuf) + { + xfree (idbuf); + return gpg_error_from_syserror (); + } + + send_status_info (ctrl, name, + idbuf, strlen (idbuf), + labelbuf, strlen(labelbuf), + NULL, 0); + xfree (idbuf); + xfree (labelbuf); + } + return 0; + } + else if (!strcmp (name, "KEY-FPR")) + { + /* Send KEY-FPR for the two openpgp keys. */ + for (prkdf = app->app_local->private_key_info; prkdf; + prkdf = prkdf->next) + { + if (app->app_local->any_gpgusage) + { + if (prkdf->gpgusage.sign) + break; + } + else + { + if (prkdf->usageflags.sign || prkdf->usageflags.sign_recover) + break; + } + } + if (prkdf) + send_key_fpr (app, ctrl, prkdf, 1); + for (prkdf = app->app_local->private_key_info; prkdf; + prkdf = prkdf->next) + { + if (app->app_local->any_gpgusage) + { + if (prkdf->gpgusage.encr) + break; + } + else + { + if (prkdf->usageflags.decrypt || prkdf->usageflags.unwrap) + break; + } + } + if (prkdf) + send_key_fpr (app, ctrl, prkdf, 2); + return 0; + } + + return gpg_error (GPG_ERR_INV_NAME); +} + + + + +/* Micardo cards require special treatment. This is a helper for the + crypto functions to manage the security environment. We expect that + the key file has already been selected. FID is the one of the + selected key. */ +static gpg_error_t +micardo_mse (app_t app, unsigned short fid) +{ + gpg_error_t err; + int recno; + unsigned short refdata = 0; + int se_num; + unsigned char msebuf[10]; + + /* Read the KeyD file containing extra information on keys. */ + err = iso7816_select_file (app_get_slot (app), 0x0013, 0); + if (err) + { + log_error ("p15: error reading EF_keyD: %s\n", gpg_strerror (err)); + return err; + } + + for (recno = 1, se_num = -1; ; recno++) + { + unsigned char *buffer; + size_t buflen; + size_t n, nn; + const unsigned char *p, *pp; + + err = iso7816_read_record (app_get_slot (app), recno, 1, 0, + &buffer, &buflen); + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + break; /* ready */ + if (err) + { + log_error ("p15: error reading EF_keyD record: %s\n", + gpg_strerror (err)); + return err; + } + if (opt.verbose) + { + log_info (buffer, buflen, "p15: keyD record: "); + log_printhex (buffer, buflen, ""); + } + p = find_tlv (buffer, buflen, 0x83, &n); + if (p && n == 4 && ((p[2]<<8)|p[3]) == fid) + { + refdata = ((p[0]<<8)|p[1]); + /* Locate the SE DO and the there included sec env number. */ + p = find_tlv (buffer, buflen, 0x7b, &n); + if (p && n) + { + pp = find_tlv (p, n, 0x80, &nn); + if (pp && nn == 1) + { + se_num = *pp; + xfree (buffer); + break; /* found. */ + } + } + } + xfree (buffer); + } + if (se_num == -1) + { + log_error ("p15: CRT for keyfile %04hX not found\n", fid); + return gpg_error (GPG_ERR_NOT_FOUND); + } + + + /* Restore the security environment to SE_NUM if needed */ + if (se_num) + { + err = iso7816_manage_security_env (app_get_slot (app), + 0xf3, se_num, NULL, 0); + if (err) + { + log_error ("p15: restoring SE to %d failed: %s\n", + se_num, gpg_strerror (err)); + return err; + } + } + + /* Set the DST reference data. */ + msebuf[0] = 0x83; + msebuf[1] = 0x03; + msebuf[2] = 0x80; + msebuf[3] = (refdata >> 8); + msebuf[4] = refdata; + err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xb6, msebuf, 5); + if (err) + { + log_error ("p15: setting SE to reference file %04hX failed: %s\n", + refdata, gpg_strerror (err)); + return err; + } + return 0; +} + + + +/* Prepare the verification of the PIN for the key PRKDF by checking + * the AODF and selecting the key file. KEYREF is used for error + * messages. AODF may be NULL if no verification needs to be done. */ +static gpg_error_t +prepare_verify_pin (app_t app, const char *keyref, + prkdf_object_t prkdf, aodf_object_t aodf) +{ + gpg_error_t err; + int i; + + if (aodf) + { + if (opt.verbose) + { + log_info ("p15: using AODF %04hX id=", aodf->fid); + for (i=0; i < aodf->objidlen; i++) + log_printf ("%02X", aodf->objid[i]); + log_printf ("\n"); + } + + if (aodf->authid && opt.verbose) + log_info ("p15: PIN is controlled by another authentication token\n"); + + if (aodf->pinflags.integrity_protected + || aodf->pinflags.confidentiality_protected) + { + log_error ("p15: PIN verification requires" + " unsupported protection method\n"); + return gpg_error (GPG_ERR_BAD_PIN_METHOD); + } + if (!aodf->stored_length && aodf->pinflags.needs_padding) + { + log_error ("p15: PIN verification requires" + " padding but no length known\n"); + return gpg_error (GPG_ERR_INV_CARD); + } + } + + + if (app->app_local->card_product == CARD_PRODUCT_DTRUST) + { + /* According to our protocol analysis we need to select a + * special AID here. Before that the master file needs to be + * selected. (RID A000000167 is assigned to IBM) */ + static char const dtrust_aid[] = + { 0xA0, 0x00, 0x00, 0x01, 0x67, 0x45, 0x53, 0x49, 0x47, 0x4E }; + + err = iso7816_select_mf (app_get_slot (app)); + if (!err) + err = iso7816_select_application (app_get_slot (app), + dtrust_aid, sizeof dtrust_aid, 0); + if (err) + log_error ("p15: error selecting D-TRUST's AID for key %s: %s\n", + keyref, gpg_strerror (err)); + } + else if (prkdf) + { + /* Standard case: Select the key file. Note that this may + * change the security environment thus we need to do it before + * PIN verification. */ + err = select_ef_by_path (app, prkdf->path, prkdf->pathlen); + if (err) + log_error ("p15: error selecting file for key %s: %s\n", + keyref, gpg_strerror (err)); + } + else + { + log_info ("p15: skipping EF selection for auth object '%s'\n", keyref); + err = 0; + } + + return err; +} + + +static int +any_control_or_space (const char *string) +{ + const unsigned char *s; + + for (s = string; *s; s++) + if (*s <= 0x20 || *s >= 0x7f) + return 1; + return 0; +} + +static int +any_control_or_space_mem (const void *buffer, size_t buflen) +{ + const unsigned char *s; + + for (s = buffer; buflen; s++, buflen--) + if (*s <= 0x20 || *s >= 0x7f) + return 1; + return 0; +} + + +/* Return a malloced serial number to be shown to the user. PRKDF is + * used to get it from a certificate; PRKDF may be NULL. */ +static char * +get_dispserialno (app_t app, prkdf_object_t prkdf) +{ + char *serial; + const unsigned char *s; + int i; + size_t n; + + /* We prefer the SerialNumber RDN from the Subject-DN but we don't + * use it if it features a percent sign (special character in pin + * prompts) or has any control character. For some cards we use a + * different strategy. */ + if (app->app_local->card_product == CARD_PRODUCT_RSCS) + { + /* We use only the right 8 hex digits. */ + serial = app_get_serialno (app); + if (serial && (n=strlen (serial)) > 8) + memmove (serial, serial + n - 8, 9); + } + else if (IS_CARDOS_5 (app) && app->app_local->manufacturer_id + && !ascii_strcasecmp (app->app_local->manufacturer_id, + "Technology Nexus") + && app->serialno && app->serialnolen == 4+9 + && !memcmp (app->serialno, "\xff\x00\x00\xff", 4) + && !any_control_or_space_mem (app->serialno + 4, 9)) + { + /* Sample: ff0000ff354830313232363537 -> "5H01 2265 7" */ + serial = xtrymalloc (9+2+1); + if (serial) + { + s = app->serialno + 4; + for (i=0; i < 4; i++) + serial[i] = *s++; + serial[i++] = ' '; + for (; i < 9; i++) + serial[i] = *s++; + serial[i++] = ' '; + serial[i++] = *s; + serial[i] = 0; + } + } + else if (prkdf && prkdf->serial_number && *prkdf->serial_number + && !strchr (prkdf->serial_number, '%') + && !any_control_or_space (prkdf->serial_number)) + { + serial = xtrystrdup (prkdf->serial_number); + } + else + { + serial = app_get_serialno (app); + } + + return serial; +} + + +/* Return an allocated string to be used as prompt. PRKDF may be + * NULL. Returns NULL on malloc error. */ +static char * +make_pin_prompt (app_t app, int remaining, const char *firstline, + prkdf_object_t prkdf) +{ + char *serial, *tmpbuf, *result; + const char *holder = NULL; + + serial = get_dispserialno (app, prkdf); + + if (app->app_local->card_product == CARD_PRODUCT_GENUA) + { + /* The label of the first non SO-PIN is used for the holder. */ + aodf_object_t aodf; + + for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next) + if (aodf->auth_type == AUTH_TYPE_PIN + && !aodf->pinflags.so_pin + && aodf->label) + { + holder = aodf->label; + break; + } + } + + if (holder) + ; + else if (prkdf && prkdf->common_name) + holder = prkdf->common_name; + else if (app->app_local->token_label) + holder = app->app_local->token_label; + else + holder = ""; + + /* TRANSLATORS: Put a \x1f right before a colon. This can be + * used by pinentry to nicely align the names and values. Keep + * the %s at the start and end of the string. */ + result = xtryasprintf (_("%s" + "Number\x1f: %s%%0A" + "Holder\x1f: %s" + "%s"), + "\x1e", + serial, + holder, + ""); + xfree (serial); + if (!result) + return NULL; /* Out of core. */ + + /* Append a "remaining attempts" info if needed. */ + if (remaining != -1 && remaining < 3) + { + char *rembuf; + + /* TRANSLATORS: This is the number of remaining attempts to + * enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */ + rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining); + if (rembuf) + { + tmpbuf = strconcat (firstline, "%0A%0A", result, + "%0A%0A", rembuf, NULL); + xfree (rembuf); + } + else + tmpbuf = NULL; + xfree (result); + result = tmpbuf; + } + else + { + tmpbuf = strconcat (firstline, "%0A%0A", result, NULL); + xfree (result); + result = tmpbuf; + } + + return result; +} + + +/* Given the private key object PRKDF and its authentication object + * AODF ask for the PIN and verify that PIN. If AODF is NULL, no + * authentication is done. */ +static gpg_error_t +verify_pin (app_t app, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, + prkdf_object_t prkdf, aodf_object_t aodf) +{ + gpg_error_t err; + char *pinvalue; + size_t pinvaluelen; + const char *label; + const char *errstr; + const char *s; + int remaining; + int pin_reference; + int verified = 0; + int i; + + if (!aodf) + return 0; + + pin_reference = aodf->pin_reference_valid? aodf->pin_reference : 0; + + if (IS_CARDOS_5 (app)) + { + /* We know that this card supports a verify status check. Note + * that in contrast to PIV cards ISO7816_VERIFY_NOT_NEEDED is + * not supported. We also don't use the pin_verified cache + * status because that is not as reliable as to ask the card + * about its state. */ + if (prkdf) /* Clear the cache which we don't use. */ + prkdf->pin_verified = 0; + + remaining = iso7816_verify_status (app_get_slot (app), pin_reference); + if (remaining == ISO7816_VERIFY_NOT_NEEDED) + { + verified = 1; + remaining = -1; + } + else if (remaining < 0) + remaining = -1; /* We don't care about the concrete error. */ + else if (remaining < 3) + log_info ("p15: PIN has %d attempts left\n", remaining); + } + else + remaining = -1; /* Unknown. */ + + /* Check whether we already verified it. */ + if (prkdf && (prkdf->pin_verified || verified)) + return 0; /* Already done. */ + + if (prkdf + && prkdf->usageflags.non_repudiation + && (app->app_local->card_type == CARD_TYPE_BELPIC + || app->app_local->card_product == CARD_PRODUCT_DTRUST)) + label = _("||Please enter the PIN for the key to create " + "qualified signatures."); + else if (aodf->pinflags.so_pin) + label = _("|A|Please enter the Admin PIN"); + else if (aodf->pinflags.unblocking_pin) + label = _("|P|Please enter the PIN Unblocking Code (PUK) " + "for the standard keys."); + else + label = _("||Please enter the PIN for the standard keys."); + + { + char *prompt = make_pin_prompt (app, remaining, label, prkdf); + if (!prompt) + err = gpg_error_from_syserror (); + else + err = pincb (pincb_arg, prompt, &pinvalue); + xfree (prompt); + } + if (err) + { + log_info ("p15: PIN callback returned error: %s\n", gpg_strerror (err)); + return err; + } + + /* We might need to cope with UTF8 things here. Not sure how + min_length etc. are exactly defined, for now we take them as + a plain octet count. */ + if (strlen (pinvalue) < aodf->min_length) + { + log_error ("p15: PIN is too short; minimum length is %lu\n", + aodf->min_length); + err = gpg_error (GPG_ERR_BAD_PIN); + } + else if (aodf->stored_length && strlen (pinvalue) > aodf->stored_length) + { + /* This would otherwise truncate the PIN silently. */ + log_error ("p15: PIN is too large; maximum length is %lu\n", + aodf->stored_length); + err = gpg_error (GPG_ERR_BAD_PIN); + } + else if (aodf->max_length_valid && strlen (pinvalue) > aodf->max_length) + { + log_error ("p15: PIN is too large; maximum length is %lu\n", + aodf->max_length); + err = gpg_error (GPG_ERR_BAD_PIN); + } + + if (err) + { + xfree (pinvalue); + return err; + } + + errstr = NULL; + err = 0; + switch (aodf->pintype) + { + case PIN_TYPE_BCD: + case PIN_TYPE_ASCII_NUMERIC: + for (s=pinvalue; digitp (s); s++) + ; + if (*s) + { + errstr = "Non-numeric digits found in PIN"; + err = gpg_error (GPG_ERR_BAD_PIN); + } + break; + case PIN_TYPE_UTF8: + break; + case PIN_TYPE_HALF_NIBBLE_BCD: + errstr = "PIN type Half-Nibble-BCD is not supported"; + break; + case PIN_TYPE_ISO9564_1: + errstr = "PIN type ISO9564-1 is not supported"; + break; + default: + errstr = "Unknown PIN type"; + break; + } + if (errstr) + { + log_error ("p15: can't verify PIN: %s\n", errstr); + xfree (pinvalue); + return err? err : gpg_error (GPG_ERR_BAD_PIN_METHOD); + } + + + if (aodf->pintype == PIN_TYPE_BCD ) + { + char *paddedpin; + int ndigits; + + for (ndigits=0, s=pinvalue; *s; ndigits++, s++) + ; + paddedpin = xtrymalloc (aodf->stored_length+1); + if (!paddedpin) + { + err = gpg_error_from_syserror (); + xfree (pinvalue); + return err; + } + + i = 0; + paddedpin[i++] = 0x20 | (ndigits & 0x0f); + for (s=pinvalue; i < aodf->stored_length && *s && s[1]; s = s+2 ) + paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f)); + if (i < aodf->stored_length && *s) + paddedpin[i++] = (((*s - '0') << 4) + |((aodf->pad_char_valid?aodf->pad_char:0)&0x0f)); + + if (aodf->pinflags.needs_padding) + { + while (i < aodf->stored_length) + paddedpin[i++] = aodf->pad_char_valid? aodf->pad_char : 0; + } + + xfree (pinvalue); + pinvalue = paddedpin; + pinvaluelen = i; + } + else if (aodf->pinflags.needs_padding) + { + char *paddedpin; + + paddedpin = xtrymalloc (aodf->stored_length+1); + if (!paddedpin) + { + err = gpg_error_from_syserror (); + xfree (pinvalue); + return err; + } + for (i=0, s=pinvalue; i < aodf->stored_length && *s; i++, s++) + paddedpin[i] = *s; + /* Not sure what padding char to use if none has been set. + For now we use 0x00; maybe a space would be better. */ + for (; i < aodf->stored_length; i++) + paddedpin[i] = aodf->pad_char_valid? aodf->pad_char : 0; + paddedpin[i] = 0; + pinvaluelen = i; + xfree (pinvalue); + pinvalue = paddedpin; + } + else + pinvaluelen = strlen (pinvalue); + + /* log_printhex (pinvalue, pinvaluelen, */ + /* "about to verify with ref %lu pin:", pin_reference); */ + err = iso7816_verify (app_get_slot (app), pin_reference, + pinvalue, pinvaluelen); + xfree (pinvalue); + if (err) + { + log_error ("p15: PIN verification failed: %s\n", gpg_strerror (err)); + return err; + } + if (opt.verbose) + log_info ("p15: PIN verification succeeded\n"); + if (prkdf) + prkdf->pin_verified = 1; + + return 0; +} + + + + +/* Handler for the PKSIGN command. + + 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; + that callback should return the PIN in an allocated buffer and + store that as the 3rd argument. */ +static gpg_error_t +do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + gpg_error_t err; + prkdf_object_t prkdf; /* The private key object. */ + aodf_object_t aodf; /* The associated authentication object. */ + int mse_done = 0; /* Set to true if the MSE has been done. */ + unsigned int digestlen; /* Length of the hash. */ + int exmode, le_value; + unsigned char oidbuf[64]; + size_t oidbuflen; + size_t n; + unsigned char *indata_buffer = NULL; /* Malloced helper. */ + + (void)ctrl; + + if (!keyidstr || !*keyidstr || !indatalen) + return gpg_error (GPG_ERR_INV_VALUE); + + err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); + if (err) + return err; + if (!(prkdf->usageflags.sign + || prkdf->usageflags.sign_recover + || prkdf->usageflags.non_repudiation + || prkdf->gpgusage.cert + || prkdf->gpgusage.sign + || prkdf->gpgusage.auth )) + { + log_error ("p15: key %s may not be used for signing\n", keyidstr); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + if (!prkdf->authid) + { + log_error ("p15: no authentication object defined for %s\n", keyidstr); + /* fixme: we might want to go ahead and do without PIN + verification. */ + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + } + + /* Find the authentication object to this private key object. */ + for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next) + if (aodf->objidlen == prkdf->authidlen + && !memcmp (aodf->objid, prkdf->authid, prkdf->authidlen)) + break; + if (!aodf) + log_info ("p15: no authentication for %s needed\n", keyidstr); + + /* We need some more info about the key - get the keygrip to + * populate these fields. */ + err = keygrip_from_prkdf (app, prkdf); + if (err) + { + log_error ("p15: keygrip_from_prkdf failed: %s\n", gpg_strerror (err)); + return err; + } + + + digestlen = gcry_md_get_algo_dlen (hashalgo); + + /* We handle ECC separately from RSA so that we do not need to touch + * working code. In particular we prepare the input data before the + * verify and a possible MSE. */ + if (prkdf->is_ecc) + { + if (digestlen != 32 && digestlen != 48 && digestlen != 64) + { + log_error ("p15: ECC signing not possible: dlen=%u\n", digestlen); + err = gpg_error (GPG_ERR_DIGEST_ALGO); + goto leave; + } + + if (indatalen == digestlen) + ; /* Already prepared. */ + else if (indatalen > digestlen) + { + /* Assume a PKCS#1 prefix and remove it. */ + oidbuflen = sizeof oidbuf; + err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen); + if (err) + { + log_error ("p15: no OID for hash algo %d\n", hashalgo); + err = gpg_error (GPG_ERR_INTERNAL); + goto leave; + } + if (indatalen != oidbuflen + digestlen + || memcmp (indata, oidbuf, oidbuflen)) + { + log_error ("p15: input data too long for ECC: len=%zu\n", + indatalen); + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + indata = (const char*)indata + oidbuflen; + indatalen -= oidbuflen; + } + else + { + log_error ("p15: input data too short for ECC: len=%zu\n", + indatalen); + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + } + else /* Prepare RSA input. */ + { + unsigned int framelen; + unsigned char *frame; + int i; + + framelen = (prkdf->keynbits+7) / 8; + if (!framelen) + { + log_error ("p15: key length unknown" + " - can't prepare PKCS#v1.5 frame\n"); + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + oidbuflen = sizeof oidbuf; + if (!hashalgo) + { + /* We assume that indata already has the required + * digestinfo; thus merely prepend the padding below. */ + } + else if ((err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen))) + { + log_debug ("p15: no OID for hash algo %d\n", hashalgo); + goto leave; + } + else + { + if (indatalen == digestlen) + { + /* Plain hash in INDATA; prepend the digestinfo. */ + indata_buffer = xtrymalloc (oidbuflen + indatalen); + if (!indata_buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (indata_buffer, oidbuf, oidbuflen); + memcpy (indata_buffer+oidbuflen, indata, indatalen); + indata = indata_buffer; + indatalen = oidbuflen + indatalen; + } + else if (indatalen == oidbuflen + digestlen + && !memcmp (indata, oidbuf, oidbuflen)) + ; /* We already got the correct prefix. */ + else + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("p15: bad input for signing with RSA and hash %d\n", + hashalgo); + goto leave; + } + } + /* Now prepend the pkcs#v1.5 padding. We require at least 8 + * byte of padding and 3 extra bytes for the prefix and the + * delimiting nul. */ + if (!indatalen || indatalen + 8 + 4 > framelen) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("p15: input does not fit into a %u bit PKCS#v1.5 frame\n", + 8*framelen); + goto leave; + } + frame = xtrymalloc (framelen); + if (!frame) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (app->app_local->card_type == CARD_TYPE_BELPIC) + { + /* This card wants only the plain hash w/o any prefix. */ + /* FIXME: We may want to remove this code because it is unlikely + * that such cards are still in use. */ + memcpy (frame, indata, indatalen); + framelen = indatalen; + } + else + { + n = 0; + frame[n++] = 0; + frame[n++] = 1; /* Block type. */ + i = framelen - indatalen - 3 ; + memset (frame+n, 0xff, i); + n += i; + frame[n++] = 0; /* Delimiter. */ + memcpy (frame+n, indata, indatalen); + n += indatalen; + log_assert (n == framelen); + } + /* And now put it into the indata_buffer. */ + xfree (indata_buffer); + indata_buffer = frame; + indata = indata_buffer; + indatalen = framelen; + } + + /* Prepare PIN verification. This is split so that we can do + * MSE operation for some task after having selected the key file but + * before sending the verify APDU. */ + err = prepare_verify_pin (app, keyidstr, prkdf, aodf); + if (err) + return err; + + /* Due to the fact that the non-repudiation signature on a BELPIC + card requires a verify immediately before the DSO we set the + MSE before we do the verification. Other cards might also allow + this but I don't want to break anything, thus we do it only + for the BELPIC card here. + FIXME: see comment above about these cards. */ + if (app->app_local->card_type == CARD_TYPE_BELPIC) + { + unsigned char mse[5]; + + mse[0] = 4; /* Length of the template. */ + mse[1] = 0x80; /* Algorithm reference tag. */ + if (hashalgo == MD_USER_TLS_MD5SHA1) + mse[2] = 0x01; /* Let card do pkcs#1 0xFF padding. */ + else + mse[2] = 0x02; /* RSASSA-PKCS1-v1.5 using SHA1. */ + mse[3] = 0x84; /* Private key reference tag. */ + mse[4] = prkdf->key_reference_valid? prkdf->key_reference : 0x82; + + err = iso7816_manage_security_env (app_get_slot (app), + 0x41, 0xB6, + mse, sizeof mse); + mse_done = 1; + } + if (err) + { + log_error ("p15: MSE failed: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Now that we have all the information available run the actual PIN + * verification.*/ + err = verify_pin (app, pincb, pincb_arg, prkdf, aodf); + if (err) + return err; + + /* Manage security environment needs to be tweaked for certain cards. */ + if (mse_done) + err = 0; + else if (app->app_local->card_type == CARD_TYPE_TCOS) + { + /* TCOS creates signatures always using the local key 0. MSE + may not be used. */ + } + else if (app->app_local->card_type == CARD_TYPE_MICARDO) + { + if (!prkdf->pathlen) + err = gpg_error (GPG_ERR_BUG); + else + err = micardo_mse (app, prkdf->path[prkdf->pathlen-1]); + } + else if (prkdf->key_reference_valid) + { + unsigned char mse[3]; + + mse[0] = 0x84; /* Select asym. key. */ + mse[1] = 1; + mse[2] = prkdf->key_reference; + + err = iso7816_manage_security_env (app_get_slot (app), + 0x41, 0xB6, + mse, sizeof mse); + } + if (err) + { + log_error ("p15: MSE failed: %s\n", gpg_strerror (err)); + goto leave; + } + + if (prkdf->keyalgo == GCRY_PK_RSA && prkdf->keynbits >= 2048) + { + exmode = 1; + le_value = prkdf->keynbits / 8; + } + else + { + exmode = 0; + le_value = 0; + } + + err = iso7816_compute_ds (app_get_slot (app), + exmode, indata, indatalen, + le_value, outdata, outdatalen); + + leave: + xfree (indata_buffer); + return err; +} + + +/* Handler for the PKAUTH command. + + This is basically the same as the PKSIGN command but we first check + that the requested key is suitable for authentication; that is, it + must match the criteria used for the attribute $AUTHKEYID. See + do_sign for calling conventions; there is no HASHALGO, though. */ +static gpg_error_t +do_auth (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + gpg_error_t err; + prkdf_object_t prkdf; + int algo; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + + err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); + if (err) + return err; + if (!(prkdf->usageflags.sign || prkdf->gpgusage.auth)) + { + log_error ("p15: key %s may not be used for authentication\n", keyidstr); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + algo = indatalen == 36? MD_USER_TLS_MD5SHA1 : GCRY_MD_SHA1; + return do_sign (app, ctrl, keyidstr, algo, pincb, pincb_arg, + indata, indatalen, outdata, outdatalen); +} + + +/* Handler for the PKDECRYPT command. 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. */ +static gpg_error_t +do_decipher (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen, + unsigned int *r_info) +{ + gpg_error_t err; + prkdf_object_t prkdf; /* The private key object. */ + aodf_object_t aodf; /* The associated authentication object. */ + int exmode, le_value, padind; + + (void)ctrl; + (void)r_info; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + if (!indatalen || !indata || !outdatalen || !outdata) + return gpg_error (GPG_ERR_INV_ARG); + + err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); + if (err) + return err; + if (!(prkdf->usageflags.decrypt + || prkdf->usageflags.unwrap + || prkdf->gpgusage.encr )) + { + log_error ("p15: key %s may not be used for decryption\n", keyidstr); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + /* Find the authentication object to this private key object. */ + if (!prkdf->authid) + { + log_error ("p15: no authentication object defined for %s\n", keyidstr); + /* fixme: we might want to go ahead and do without PIN + verification. */ + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + } + for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next) + if (aodf->objidlen == prkdf->authidlen + && !memcmp (aodf->objid, prkdf->authid, prkdf->authidlen)) + break; + if (!aodf) + log_info ("p15: no authentication for %s needed\n", keyidstr); + + /* We need some more info about the key - get the keygrip to + * populate these fields. */ + err = keygrip_from_prkdf (app, prkdf); + if (err) + { + log_error ("p15: keygrip_from_prkdf failed: %s\n", gpg_strerror (err)); + return err; + } + + /* Verify the PIN. */ + err = prepare_verify_pin (app, keyidstr, prkdf, aodf); + if (!err) + err = verify_pin (app, pincb, pincb_arg, prkdf, aodf); + if (err) + return err; + + if (prkdf->is_ecc && IS_CARDOS_5(app)) + { + + err = iso7816_manage_security_env (app_get_slot (app), 0xF3, 0x01, + NULL, 0); + if (err) + { + log_error ("p15: MSE failed: %s\n", gpg_strerror (err)); + return err; + } + } + + + /* The next is guess work for CardOS. */ + if (app->app_local->card_product == CARD_PRODUCT_DTRUST) + { + /* From analyzing an USB trace of a Windows signing application + * we see that the SE is simply reset to 0x14. It seems to be + * sufficient to do this for decryption; signing still works + * with the standard code despite that our trace showed that + * there the SE is restored to 0x09. Note that the special + * D-Trust AID is in any case select by prepare_verify_pin. + * + * Hey, D-Trust please hand over the specs so that you can + * actually sell your cards and we can properly implement it; + * other vendors understand this and do not demand ridiculous + * paper work or complicated procedures to get samples. */ + err = iso7816_manage_security_env (app_get_slot (app), + 0xF3, 0x14, NULL, 0); + + } + else if (prkdf->key_reference_valid) + { + unsigned char mse[9]; + int i; + + /* Note: This works with CardOS but the D-Trust card has the + * problem that the next created signature would be broken. */ + + i = 0; + if (!prkdf->is_ecc) + { + mse[i++] = 0x80; /* Algorithm reference. */ + mse[i++] = 1; + mse[i++] = 0x0a; /* RSA, no padding. */ + } + mse[i++] = 0x84; /* Key reference. */ + mse[i++] = 1; + mse[i++] = prkdf->key_reference; + if (prkdf->is_ecc && IS_CARDOS_5(app)) + { + mse[i++] = 0x95; /* ???. */ + mse[i++] = 1; + mse[i++] = 0x40; + } + log_assert (i <= DIM(mse)); + err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB8, + mse, i); + } + /* Check for MSE error. */ + if (err) + { + log_error ("p15: MSE failed: %s\n", gpg_strerror (err)); + return err; + } + + exmode = le_value = 0; + padind = 0; + if (prkdf->keyalgo == GCRY_PK_RSA && prkdf->keynbits >= 2048) + { + exmode = 1; /* Extended length w/o a limit. */ + le_value = prkdf->keynbits / 8; + } + + if (app->app_local->card_product == CARD_PRODUCT_DTRUST) + padind = 0x81; + + if (prkdf->is_ecc && IS_CARDOS_5(app)) + { + if ((indatalen & 1) && *(const char *)indata == 0x04) + { + /* Strip indicator byte. */ + indatalen--; + indata = (const char *)indata + 1; + } + err = iso7816_pso_csv (app_get_slot (app), exmode, + indata, indatalen, + le_value, + outdata, outdatalen); + } + else + { + err = iso7816_decipher (app_get_slot (app), exmode, + indata, indatalen, + le_value, padind, + outdata, outdatalen); + } + + return err; +} + + +/* Perform a simple verify operation for the PIN specified by + * KEYIDSTR. Note that we require a key reference which is then used + * to select the authentication object. Return GPG_ERR_NO_PIN if a + * PIN is not required for using the private key KEYIDSTR. */ +static gpg_error_t +do_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + prkdf_object_t prkdf; /* The private key object. */ + aodf_object_t aodf; /* The associated authentication object. */ + + (void)ctrl; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + + err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); + if (err + && gpg_err_code (err) != GPG_ERR_INV_ID + && gpg_err_code (err) != GPG_ERR_NOT_FOUND) + return err; + + if (err) /* Not found or invalid - assume it is the label. */ + { + prkdf = NULL; + for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next) + if (aodf->label && !ascii_strcasecmp (aodf->label, keyidstr)) + break; + if (!aodf) + return err; /* Re-use the original error code. */ + } + else /* Find the authentication object to this private key object. */ + { + if (!prkdf->authid) + { + log_error ("p15: no authentication object defined for %s\n", + keyidstr); + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + } + for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next) + if (aodf->objidlen == prkdf->authidlen + && !memcmp (aodf->objid, prkdf->authid, prkdf->authidlen)) + break; + if (!aodf) /* None found. */ + return gpg_error (GPG_ERR_NO_PIN); + } + + err = prepare_verify_pin (app, keyidstr, prkdf, aodf); + if (!err) + err = verify_pin (app, pincb, pincb_arg, prkdf, aodf); + + return err; +} + + +/* Process the various keygrip based info requests. */ +static gpg_error_t +do_with_keygrip (app_t app, ctrl_t ctrl, int action, + const char *want_keygripstr, int capability) +{ + gpg_error_t err; + char *serialno = NULL; + int as_data = 0; + prkdf_object_t prkdf; + + /* First a quick check for valid parameters. */ + switch (action) + { + case KEYGRIP_ACTION_LOOKUP: + if (!want_keygripstr) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + break; + case KEYGRIP_ACTION_SEND_DATA: + as_data = 1; + break; + case KEYGRIP_ACTION_WRITE_STATUS: + break; + default: + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + /* Allocate the s/n string if needed. */ + if (action != KEYGRIP_ACTION_LOOKUP) + { + serialno = app_get_serialno (app); + if (!serialno) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + for (prkdf = app->app_local->private_key_info; + prkdf; prkdf = prkdf->next) + { + if (keygrip_from_prkdf (app, prkdf)) + continue; + + if (action == KEYGRIP_ACTION_LOOKUP) + { + if (!strcmp (prkdf->keygrip, want_keygripstr)) + { + err = 0; /* Found */ + goto leave; + } + } + else if (!want_keygripstr || !strcmp (prkdf->keygrip, want_keygripstr)) + { + char *keyref; + + if (capability == GCRY_PK_USAGE_SIGN) + { + if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover + || prkdf->usageflags.non_repudiation)) + continue; + } + else if (capability == GCRY_PK_USAGE_ENCR) + { + if (!(prkdf->usageflags.decrypt || prkdf->usageflags.unwrap)) + continue; + } + else if (capability == GCRY_PK_USAGE_AUTH) + { + if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover)) + continue; + } + + keyref = keyref_from_prkdf (app, prkdf); + if (!keyref) + { + err = gpg_error_from_syserror (); + goto leave; + } + + send_keyinfo (ctrl, as_data, prkdf->keygrip, serialno, keyref); + xfree (keyref); + if (want_keygripstr) + { + err = 0; /* Found */ + goto leave; + } + } + } + + /* Return an error so that the dispatcher keeps on looping over the + * other applications. For clarity we use a different error code + * when listing all keys. Note that in lookup mode WANT_KEYGRIPSTR + * is not NULL. */ + if (!want_keygripstr) + err = gpg_error (GPG_ERR_TRUE); + else + err = gpg_error (GPG_ERR_NOT_FOUND); + + leave: + xfree (serialno); + return err; +} + + + +/* Assume that EF(DIR) has been selected. Read its content and figure + out the home EF of pkcs#15. Return that home DF or 0 if not found + and the value at the address of BELPIC indicates whether it was + found by the belpic aid. */ +static unsigned short +read_home_df (int slot, int *r_belpic) +{ + gpg_error_t err; + unsigned char *buffer; + const unsigned char *p, *pp; + size_t buflen, n, nn; + unsigned short result = 0; + + *r_belpic = 0; + + err = iso7816_read_binary (slot, 0, 0, &buffer, &buflen); + if (err) + { + log_error ("p15: error reading EF(DIR): %s\n", gpg_strerror (err)); + return 0; + } + + /* FIXME: We need to scan all records. */ + p = find_tlv (buffer, buflen, 0x61, &n); + if (p && n) + { + pp = find_tlv (p, n, 0x4f, &nn); + if (pp && ((nn == sizeof pkcs15_aid && !memcmp (pp, pkcs15_aid, nn)) + || (*r_belpic = (nn == sizeof pkcs15be_aid + && !memcmp (pp, pkcs15be_aid, nn))))) + { + pp = find_tlv (p, n, 0x50, &nn); + if (pp && opt.verbose) + log_info ("p15: application label from EF(DIR) is '%.*s'\n", + (int)nn, pp); + pp = find_tlv (p, n, 0x51, &nn); + if (pp && nn == 4 && *pp == 0x3f && !pp[1]) + { + result = ((pp[2] << 8) | pp[3]); + if (opt.verbose) + log_info ("p15: application directory is 0x%04hX\n", result); + } + } + } + xfree (buffer); + return result; +} + + +/* + Select the PKCS#15 application on the card in SLOT. + */ +gpg_error_t +app_select_p15 (app_t app) +{ + int slot = app_get_slot (app); + int rc; + unsigned short def_home_df = 0; + card_type_t card_type = CARD_TYPE_UNKNOWN; + int direct = 0; + int is_belpic = 0; + unsigned char *fci = NULL; + size_t fcilen; + + rc = iso7816_select_application_ext (slot, pkcs15_aid, sizeof pkcs15_aid, 1, + &fci, &fcilen); + if (rc) + { /* Not found: Try to locate it from 2F00. We use direct path + selection here because it seems that the Belgian eID card + does only allow for that. Many other cards supports this + selection method too. Note, that we don't use + select_application above for the Belgian card - the call + works but it seems that it does not switch to the correct DF. + Using the 2f02 just works. */ + unsigned short path[1] = { 0x2f00 }; + + rc = iso7816_select_path (slot, path, 1, 0); + if (!rc) + { + direct = 1; + def_home_df = read_home_df (slot, &is_belpic); + if (def_home_df) + { + path[0] = def_home_df; + rc = iso7816_select_path (slot, path, 1, 0); + } + } + } + if (rc) + { /* Still not found: Try the default DF. */ + def_home_df = DEFAULT_HOME_DF; + rc = iso7816_select_file (slot, def_home_df, 1); + } + if (!rc) + { + /* Determine the type of the card. The general case is to look + it up from the ATR table. For the Belgian eID card we know + it instantly from the AID. */ + if (is_belpic) + { + card_type = CARD_TYPE_BELPIC; + } + else + { + unsigned char *atr; + size_t atrlen; + int i; + + atr = apdu_get_atr (app_get_slot (app), &atrlen); + if (!atr) + rc = gpg_error (GPG_ERR_INV_CARD); + else + { + for (i=0; card_atr_list[i].atrlen; i++) + if (card_atr_list[i].atrlen == atrlen + && !memcmp (card_atr_list[i].atr, atr, atrlen)) + { + card_type = card_atr_list[i].type; + break; + } + xfree (atr); + } + } + } + if (!rc) + { + app->apptype = APPTYPE_P15; + + app->app_local = xtrycalloc (1, sizeof *app->app_local); + if (!app->app_local) + { + rc = gpg_error_from_syserror (); + goto leave; + } + + /* Set the home DF from the FCI returned by the select. */ + if (!def_home_df && fci) + { + const unsigned char *s; + size_t n; + + s = find_tlv (fci, fcilen, 0x83, &n); + if (s && n == 2) + def_home_df = buf16_to_ushort (s); + else + { + if (fcilen) + log_printhex (fci, fcilen, "fci:"); + log_info ("p15: select did not return the DF - using default\n"); + def_home_df = DEFAULT_HOME_DF; + } + } + app->app_local->home_df = def_home_df; + + /* Store the card type. FIXME: We might want to put this into + the common APP structure. */ + app->app_local->card_type = card_type; + + app->app_local->card_product = CARD_PRODUCT_UNKNOWN; + + /* Store whether we may and should use direct path selection. */ + switch (card_type) + { + case CARD_TYPE_CARDOS_50: + case CARD_TYPE_CARDOS_53: + direct = 1; + break; + case CARD_TYPE_AET: + app->app_local->no_extended_mode = 1; + break; + default: + /* Use whatever has been determined above. */ + break; + } + app->app_local->direct_path_selection = direct; + + /* Read basic information and thus check whether this is a real + card. */ + rc = read_p15_info (app); + if (rc) + goto leave; + + /* Special serial number munging. We need to check for a German + prototype card right here because we need to access to + EF(TokenInfo). We mark such a serial number by the using a + prefix of FF0100. */ + if (APP_CARD(app)->serialnolen == 12 + && !memcmp (APP_CARD(app)->serialno, + "\xD2\x76\0\0\0\0\0\0\0\0\0\0", 12)) + { + /* This is a German card with a silly serial number. Try to get + the serial number from the EF(TokenInfo). . */ + unsigned char *p; + + /* FIXME: actually get it from EF(TokenInfo). */ + + p = xtrymalloc (3 + APP_CARD(app)->serialnolen); + if (!p) + rc = gpg_error (gpg_err_code_from_errno (errno)); + else + { + memcpy (p, "\xff\x01", 3); + memcpy (p+3, APP_CARD(app)->serialno, APP_CARD(app)->serialnolen); + APP_CARD(app)->serialnolen += 3; + xfree (APP_CARD(app)->serialno); + APP_CARD(app)->serialno = p; + } + } + + app->fnc.deinit = do_deinit; + app->fnc.prep_reselect = NULL; + app->fnc.reselect = NULL; + app->fnc.learn_status = do_learn_status; + app->fnc.readcert = do_readcert; + app->fnc.getattr = do_getattr; + app->fnc.setattr = NULL; + app->fnc.genkey = NULL; + app->fnc.sign = do_sign; + app->fnc.auth = do_auth; + app->fnc.decipher = do_decipher; + app->fnc.change_pin = NULL; + app->fnc.check_pin = do_check_pin; + app->fnc.with_keygrip = do_with_keygrip; + + leave: + if (rc) + do_deinit (app); + } + + xfree (fci); + return rc; +} diff --git a/scd/app-sc-hsm.c b/scd/app-sc-hsm.c new file mode 100644 index 0000000..1425b43 --- /dev/null +++ b/scd/app-sc-hsm.c @@ -0,0 +1,2087 @@ +/* app-sc-hsm.c - The SmartCard-HSM card application (www.smartcard-hsm.com). + * Copyright (C) 2005 Free Software Foundation, Inc. + * Copyright (C) 2014 Andreas Schwier <andreas.schwier@cardcontact.de> + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +/* + Code in this driver is based on app-p15.c with modifications. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <time.h> + +#include "scdaemon.h" + +#include "iso7816.h" +#include "../common/tlv.h" +#include "apdu.h" + + +/* The AID of the SmartCard-HSM applet. */ +static char const sc_hsm_aid[] = { 0xE8, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, + 0xC3, 0x1F, 0x02, 0x01 }; + + +/* Special file identifier for SmartCard-HSM */ +typedef enum +{ + SC_HSM_PRKD_PREFIX = 0xC4, + SC_HSM_CD_PREFIX = 0xC8, + SC_HSM_DCOD_PREFIX = 0xC9, + SC_HSM_CA_PREFIX = 0xCA, + SC_HSM_KEY_PREFIX = 0xCC, + SC_HSM_EE_PREFIX = 0xCE +} fid_prefix_type_t; + + +/* The key types supported by the SmartCard-HSM */ +typedef enum + { + KEY_TYPE_RSA, + KEY_TYPE_ECC + } key_type_t; + + +/* A bit array with for the key usage flags from the + commonKeyAttributes. */ +struct keyusage_flags_s +{ + unsigned int encrypt: 1; + unsigned int decrypt: 1; + unsigned int sign: 1; + unsigned int sign_recover: 1; + unsigned int wrap: 1; + unsigned int unwrap: 1; + unsigned int verify: 1; + unsigned int verify_recover: 1; + unsigned int derive: 1; + unsigned int non_repudiation: 1; +}; +typedef struct keyusage_flags_s keyusage_flags_t; + + + +/* This is an object to store information about a Certificate + Directory File (CDF) in a format suitable for further processing by + us. To keep memory management, simple we use a linked list of + items; i.e. one such object represents one certificate and the list + the entire CDF. */ +struct cdf_object_s +{ + /* Link to next item when used in a linked list. */ + struct cdf_object_s *next; + + /* Length and allocated buffer with the Id of this object. */ + size_t objidlen; + unsigned char *objid; + + /* To avoid reading a certificate more than once, we cache it in an + allocated memory IMAGE of IMAGELEN. */ + size_t imagelen; + unsigned char *image; + + /* EF containing certificate */ + unsigned short fid; +}; +typedef struct cdf_object_s *cdf_object_t; + + + +/* This is an object to store information about a Private Key + Directory File (PrKDF) in a format suitable for further processing + by us. To keep memory management, simple we use a linked list of + items; i.e. one such object represents one certificate and the list + the entire PrKDF. */ +struct prkdf_object_s +{ + /* Link to next item when used in a linked list. */ + struct prkdf_object_s *next; + + /* Key type */ + key_type_t keytype; + + /* Key size in bits or 0 if unknown */ + size_t keysize; + + /* Length and allocated buffer with the Id of this object. */ + size_t objidlen; + unsigned char *objid; + + /* The key's usage flags. */ + keyusage_flags_t usageflags; + + /* The keyReference */ + unsigned char key_reference; +}; +typedef struct prkdf_object_s *prkdf_object_t; + + + +/* Context local to this application. */ +struct app_local_s +{ + /* Information on all certificates. */ + cdf_object_t certificate_info; + /* Information on all trusted certificates. */ + cdf_object_t trusted_certificate_info; + /* Information on all private keys. */ + prkdf_object_t private_key_info; +}; + + + +/*** Local prototypes. ***/ +static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf, + unsigned char **r_cert, size_t *r_certlen); + + + +/* Release the CDF object A */ +static void +release_cdflist (cdf_object_t a) +{ + while (a) + { + cdf_object_t tmp = a->next; + xfree (a->image); + xfree (a->objid); + xfree (a); + a = tmp; + } +} + + + +/* Release the PrKDF object A. */ +static void +release_prkdflist (prkdf_object_t a) +{ + while (a) + { + prkdf_object_t tmp = a->next; + xfree (a->objid); + xfree (a); + a = tmp; + } +} + + + +/* Release all local resources. */ +static void +do_deinit (app_t app) +{ + if (app && app->app_local) + { + release_cdflist (app->app_local->certificate_info); + release_cdflist (app->app_local->trusted_certificate_info); + release_prkdflist (app->app_local->private_key_info); + xfree (app->app_local); + app->app_local = NULL; + } +} + + + +/* Get the list of EFs from the SmartCard-HSM. + * On success a dynamically buffer containing the EF list is returned. + * The caller is responsible for freeing the buffer. + */ +static gpg_error_t +list_ef (int slot, 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_le (slot, 1, 0x80, 0x58, 0x00, 0x00, -1, NULL, 65536, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + } + return iso7816_map_sw (sw); +} + + + +/* Do a select and a read for the file with EFID. EFID_DESC is a + description of the EF to be used with error messages. On success + BUFFER and BUFLEN contain the entire content of the EF. The caller + must free BUFFER only on success. */ +static gpg_error_t +select_and_read_binary (int slot, unsigned short efid, const char *efid_desc, + unsigned char **buffer, size_t *buflen, int maxread) +{ + gpg_error_t err; + unsigned char cdata[4]; + int sw; + + cdata[0] = 0x54; /* Create ISO 7861-4 odd ins READ BINARY */ + cdata[1] = 0x02; + cdata[2] = 0x00; + cdata[3] = 0x00; + + sw = apdu_send_le(slot, 1, 0x00, 0xB1, efid >> 8, efid & 0xFF, + 4, cdata, maxread, buffer, buflen); + + if (sw == SW_EOF_REACHED) + sw = SW_SUCCESS; + + err = iso7816_map_sw (sw); + if (err) + { + log_error ("error reading %s (0x%04X): %s\n", + efid_desc, efid, gpg_strerror (err)); + return err; + } + return 0; +} + + + +/* Parse a cert Id string (or a key Id string) and return the binary + object Id string in a newly allocated buffer stored at R_OBJID and + R_OBJIDLEN. On Error NULL will be stored there and an error code + returned. On success caller needs to free the buffer at R_OBJID. */ +static gpg_error_t +parse_certid (const char *certid, unsigned char **r_objid, size_t *r_objidlen) +{ + const char *s; + size_t objidlen; + unsigned char *objid; + int i; + + *r_objid = NULL; + *r_objidlen = 0; + + if (strncmp (certid, "HSM.", 4)) + return gpg_error (GPG_ERR_INV_ID); + certid += 4; + + for (s=certid, objidlen=0; hexdigitp (s); s++, objidlen++) + ; + if (*s || !objidlen || (objidlen%2)) + return gpg_error (GPG_ERR_INV_ID); + objidlen /= 2; + objid = xtrymalloc (objidlen); + if (!objid) + return gpg_error_from_syserror (); + for (s=certid, i=0; i < objidlen; i++, s+=2) + objid[i] = xtoi_2 (s); + *r_objid = objid; + *r_objidlen = objidlen; + return 0; +} + + + +/* Find a certificate object by the certificate ID CERTID and store a + pointer to it at R_CDF. */ +static gpg_error_t +cdf_object_from_certid (app_t app, const char *certid, cdf_object_t *r_cdf) +{ + gpg_error_t err; + size_t objidlen; + unsigned char *objid; + cdf_object_t cdf; + + err = parse_certid (certid, &objid, &objidlen); + if (err) + return err; + + for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next) + if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) + break; + if (!cdf) + for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next) + if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) + break; + xfree (objid); + if (!cdf) + return gpg_error (GPG_ERR_NOT_FOUND); + *r_cdf = cdf; + return 0; +} + + + +/* Find a private key object by the key Id string KEYIDSTR and store a + pointer to it at R_PRKDF. */ +static gpg_error_t +prkdf_object_from_keyidstr (app_t app, const char *keyidstr, + prkdf_object_t *r_prkdf) +{ + gpg_error_t err; + size_t objidlen; + unsigned char *objid; + prkdf_object_t prkdf; + + err = parse_certid (keyidstr, &objid, &objidlen); + if (err) + return err; + + for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) + if (prkdf->objidlen == objidlen && !memcmp (prkdf->objid, objid, objidlen)) + break; + xfree (objid); + if (!prkdf) + return gpg_error (GPG_ERR_NOT_FOUND); + *r_prkdf = prkdf; + return 0; +} + + + +/* Parse the BIT STRING with the keyUsageFlags from the + CommonKeyAttributes. */ +static gpg_error_t +parse_keyusage_flags (const unsigned char *der, size_t derlen, + keyusage_flags_t *usageflags) +{ + unsigned int bits, mask; + int i, unused, full; + + memset (usageflags, 0, sizeof *usageflags); + if (!derlen) + return gpg_error (GPG_ERR_INV_OBJ); + + unused = *der++; derlen--; + if ((!derlen && unused) || unused/8 > derlen) + return gpg_error (GPG_ERR_ENCODING_PROBLEM); + full = derlen - (unused+7)/8; + unused %= 8; + mask = 0; + for (i=1; unused; i <<= 1, unused--) + mask |= i; + + /* First octet */ + if (derlen) + { + bits = *der++; derlen--; + if (full) + full--; + else + { + bits &= ~mask; + mask = 0; + } + } + else + bits = 0; + if ((bits & 0x80)) usageflags->encrypt = 1; + if ((bits & 0x40)) usageflags->decrypt = 1; + if ((bits & 0x20)) usageflags->sign = 1; + if ((bits & 0x10)) usageflags->sign_recover = 1; + if ((bits & 0x08)) usageflags->wrap = 1; + if ((bits & 0x04)) usageflags->unwrap = 1; + if ((bits & 0x02)) usageflags->verify = 1; + if ((bits & 0x01)) usageflags->verify_recover = 1; + + /* Second octet. */ + if (derlen) + { + bits = *der++; derlen--; + if (full) + full--; + else + { + bits &= ~mask; + } + } + else + bits = 0; + if ((bits & 0x80)) usageflags->derive = 1; + if ((bits & 0x40)) usageflags->non_repudiation = 1; + + return 0; +} + + + +/* Read and parse a Private Key Directory File containing a single key + description in PKCS#15 format. For each private key a matching + certificate description is created, if the certificate EF exists + and contains a X.509 certificate. + + Example data: + +0000 30 2A 30 13 0C 11 4A 6F 65 20 44 6F 65 20 28 52 0*0...Joe Doe (R +0010 53 41 32 30 34 38 29 30 07 04 01 01 03 02 02 74 SA2048)0.......t +0020 A1 0A 30 08 30 02 04 00 02 02 08 00 ..0.0....... + + Decoded example: + +SEQUENCE SIZE( 42 ) + SEQUENCE SIZE( 19 ) + UTF8-STRING SIZE( 17 ) -- label + 0000 4A 6F 65 20 44 6F 65 20 28 52 53 41 32 30 34 38 Joe Doe (RSA2048 + 0010 29 ) + SEQUENCE SIZE( 7 ) + OCTET-STRING SIZE( 1 ) -- id + 0000 01 + BIT-STRING SIZE( 2 ) -- key usage + 0000 02 74 + A1 [ CONTEXT 1 ] IMPLICIT SEQUENCE SIZE( 10 ) + SEQUENCE SIZE( 8 ) + SEQUENCE SIZE( 2 ) + OCTET-STRING SIZE( 0 ) -- empty path, req object in PKCS#15 + INTEGER SIZE( 2 ) -- modulus size in bits + 0000 08 00 +*/ +static gpg_error_t +read_ef_prkd (app_t app, unsigned short fid, prkdf_object_t *prkdresult, + cdf_object_t *cdresult) +{ + gpg_error_t err; + unsigned char *buffer = NULL; + size_t buflen; + const unsigned char *p; + size_t n, objlen, hdrlen; + int class, tag, constructed, ndef; + int i; + const unsigned char *pp; + size_t nn; + int where; + const char *errstr = NULL; + prkdf_object_t prkdf = NULL; + cdf_object_t cdf = NULL; + unsigned long ul; + const unsigned char *objid; + size_t objidlen; + keyusage_flags_t usageflags; + const char *s; + key_type_t keytype; + size_t keysize; + + if (!fid) + return gpg_error (GPG_ERR_NO_DATA); /* No private keys. */ + + err = select_and_read_binary (app->slot, fid, "PrKDF", &buffer, &buflen, 255); + if (err) + return err; + + p = buffer; + n = buflen; + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || (tag != TAG_SEQUENCE && tag != 0x00))) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + { + log_error ("error parsing PrKDF record: %s\n", gpg_strerror (err)); + goto leave; + } + + keytype = tag == 0x00 ? KEY_TYPE_ECC : KEY_TYPE_RSA; + + pp = p; + nn = objlen; + p += objlen; + n -= objlen; + + /* Parse the commonObjectAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + { + const unsigned char *ppp = pp; + size_t nnn = objlen; + + pp += objlen; + nn -= objlen; + + /* Search the optional AuthId. We need to skip the optional Label + (UTF8STRING) and the optional CommonObjectFlags (BITSTRING). */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto no_authid; + if (err) + goto parse_error; + + if (tag == TAG_UTF8_STRING) + { + ppp += objlen; /* Skip the Label. */ + nnn -= objlen; + + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto no_authid; + if (err) + goto parse_error; + } + if (tag == TAG_BIT_STRING) + { + ppp += objlen; /* Skip the CommonObjectFlags. */ + nnn -= objlen; + + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto no_authid; + if (err) + goto parse_error; + } + if (tag == TAG_OCTET_STRING && objlen) + { + /* AuthId ignored */ + } + no_authid: + ; + } + + /* Parse the commonKeyAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + { + const unsigned char *ppp = pp; + size_t nnn = objlen; + + pp += objlen; + nn -= objlen; + + /* Get the Id. */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn + || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + objid = ppp; + objidlen = objlen; + ppp += objlen; + nnn -= objlen; + + /* Get the KeyUsageFlags. */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn + || class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + err = parse_keyusage_flags (ppp, objlen, &usageflags); + if (err) + goto parse_error; + + ppp += objlen; + nnn -= objlen; + + /* Find the keyReference */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto leave_cki; + if (!err && objlen > nnn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + if (class == CLASS_UNIVERSAL && tag == TAG_BOOLEAN) + { + /* Skip the native element. */ + ppp += objlen; + nnn -= objlen; + + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto leave_cki; + if (!err && objlen > nnn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + } + if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING) + { + /* Skip the accessFlags. */ + ppp += objlen; + nnn -= objlen; + + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto leave_cki; + if (!err && objlen > nnn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + } + if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER) + { + /* Yep, this is the keyReference. + Note: UL is currently not used. */ + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*ppp++) & 0xff; + nnn--; + } + } + + leave_cki: + ; + } + + + /* Skip subClassAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + if (class == CLASS_CONTEXT && tag == 0) + { + pp += objlen; + nn -= objlen; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + } + + /* Parse the keyAttributes. */ + if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + nn = objlen; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + nn = objlen; + + /* Check that the reference is a Path object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) + { + errstr = "unsupported reference type"; + goto parse_error; + } + + pp += objlen; + nn -= objlen; + + /* Parse the key size object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + keysize = 0; + if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER && objlen == 2) + { + keysize = *pp++ << 8; + keysize += *pp++; + } + + /* Create a new PrKDF list item. */ + prkdf = xtrycalloc (1, sizeof *prkdf); + if (!prkdf) + { + err = gpg_error_from_syserror (); + goto leave; + } + prkdf->keytype = keytype; + prkdf->keysize = keysize; + prkdf->objidlen = objidlen; + prkdf->objid = xtrymalloc (objidlen); + if (!prkdf->objid) + { + err = gpg_error_from_syserror (); + xfree (prkdf); + prkdf = NULL; + goto leave; + } + memcpy (prkdf->objid, objid, objidlen); + + prkdf->usageflags = usageflags; + prkdf->key_reference = fid & 0xFF; + + log_debug ("PrKDF %04hX: id=", fid); + for (i=0; i < prkdf->objidlen; i++) + log_printf ("%02X", prkdf->objid[i]); + log_printf (" keyref=0x%02X", prkdf->key_reference); + log_printf (" keysize=%zu", prkdf->keysize); + log_printf (" usage="); + s = ""; + if (prkdf->usageflags.encrypt) + { + log_printf ("%sencrypt", s); + s = ","; + } + if (prkdf->usageflags.decrypt) + { + log_printf ("%sdecrypt", s); + s = ","; + } + if (prkdf->usageflags.sign) + { + log_printf ("%ssign", s); + s = ","; + } + if (prkdf->usageflags.sign_recover) + { + log_printf ("%ssign_recover", s); + s = ","; + } + if (prkdf->usageflags.wrap ) + { + log_printf ("%swrap", s); + s = ","; + } + if (prkdf->usageflags.unwrap ) + { + log_printf ("%sunwrap", s); + s = ","; + } + if (prkdf->usageflags.verify ) + { + log_printf ("%sverify", s); + s = ","; + } + if (prkdf->usageflags.verify_recover) + { + log_printf ("%sverify_recover", s); + s = ","; + } + if (prkdf->usageflags.derive ) + { + log_printf ("%sderive", s); + s = ","; + } + if (prkdf->usageflags.non_repudiation) + { + log_printf ("%snon_repudiation", s); + } + log_printf ("\n"); + + xfree (buffer); + buffer = NULL; + buflen = 0; + err = select_and_read_binary (app->slot, + ((SC_HSM_EE_PREFIX << 8) | (fid & 0xFF)), + "CertEF", &buffer, &buflen, 1); + if (!err && buffer[0] == 0x30) + { + /* Create a matching CDF list item. */ + cdf = xtrycalloc (1, sizeof *cdf); + if (!cdf) + { + err = gpg_error_from_syserror (); + goto leave; + } + cdf->objidlen = prkdf->objidlen; + cdf->objid = xtrymalloc (cdf->objidlen); + if (!cdf->objid) + { + err = gpg_error_from_syserror (); + xfree (cdf); + cdf = NULL; + goto leave; + } + memcpy (cdf->objid, prkdf->objid, objidlen); + + cdf->fid = (SC_HSM_EE_PREFIX << 8) | (fid & 0xFF); + + log_debug ("CDF %04hX: id=", fid); + for (i=0; i < cdf->objidlen; i++) + log_printf ("%02X", cdf->objid[i]); + log_printf (" fid=%04X\n", cdf->fid); + } + + goto leave; /* Ready. */ + + parse_error: + log_error ("error parsing PrKDF record (%d): %s - skipped\n", + where, errstr? errstr : gpg_strerror (err)); + err = 0; + + leave: + xfree (buffer); + if (err) + { + if (prkdf) + { + if (prkdf->objid) + xfree (prkdf->objid); + xfree (prkdf); + } + if (cdf) + { + if (cdf->objid) + xfree (cdf->objid); + xfree (cdf); + } + } + else + { + if (prkdf) + prkdf->next = *prkdresult; + *prkdresult = prkdf; + if (cdf) + { + cdf->next = *cdresult; + *cdresult = cdf; + } + } + return err; +} + + + +/* Read and parse the Certificate Description File identified by FID. + On success a the CDF list gets stored at RESULT and the caller is + then responsible of releasing the object. + + Example data: + +0000 30 35 30 11 0C 0B 43 65 72 74 69 66 69 63 61 74 050...Certificat +0010 65 03 02 06 40 30 16 04 14 C2 01 7C 2F BA A4 4A e...@0.....|/..J +0020 4A BB B8 49 11 DB 4A CA AA 7E 6A 2D 1B A1 08 30 J..I..J..~j-...0 +0030 06 30 04 04 02 CA 00 .0..... + + Decoded example: + +SEQUENCE SIZE( 53 ) + SEQUENCE SIZE( 17 ) + UTF8-STRING SIZE( 11 ) -- label + 0000 43 65 72 74 69 66 69 63 61 74 65 Certificate + BIT-STRING SIZE( 2 ) -- common object attributes + 0000 06 40 + SEQUENCE SIZE( 22 ) + OCTET-STRING SIZE( 20 ) -- id + 0000 C2 01 7C 2F BA A4 4A 4A BB B8 49 11 DB 4A CA AA + 0010 7E 6A 2D 1B + A1 [ CONTEXT 1 ] IMPLICIT SEQUENCE SIZE( 8 ) + SEQUENCE SIZE( 6 ) + SEQUENCE SIZE( 4 ) + OCTET-STRING SIZE( 2 ) -- path + 0000 CA 00 .. + */ +static gpg_error_t +read_ef_cd (app_t app, unsigned short fid, cdf_object_t *result) +{ + gpg_error_t err; + unsigned char *buffer = NULL; + size_t buflen; + const unsigned char *p; + size_t n, objlen, hdrlen; + int class, tag, constructed, ndef; + int i; + const unsigned char *pp; + size_t nn; + int where; + const char *errstr = NULL; + cdf_object_t cdf = NULL; + const unsigned char *objid; + size_t objidlen; + + if (!fid) + return gpg_error (GPG_ERR_NO_DATA); /* No certificates. */ + + err = select_and_read_binary (app->slot, fid, "CDF", &buffer, &buflen, 255); + if (err) + return err; + + p = buffer; + n = buflen; + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + { + log_error ("error parsing CDF record: %s\n", gpg_strerror (err)); + goto leave; + } + pp = p; + nn = objlen; + p += objlen; + n -= objlen; + + /* Skip the commonObjectAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + pp += objlen; + nn -= objlen; + + /* Parse the commonCertificateAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + { + const unsigned char *ppp = pp; + size_t nnn = objlen; + + pp += objlen; + nn -= objlen; + + /* Get the Id. */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn + || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + objid = ppp; + objidlen = objlen; + } + + /* Parse the certAttribute. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + nn = objlen; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn + || class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + nn = objlen; + + /* Check that the reference is a Path object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto parse_error; + } + nn = objlen; + + /* Parse the Path object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + /* Make sure that the next element is a non zero path and of + even length (FID are two bytes each). */ + if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING + || (objlen & 1) ) + { + errstr = "invalid path reference"; + goto parse_error; + } + /* Create a new CDF list item. */ + cdf = xtrycalloc (1, sizeof *cdf); + if (!cdf) + { + err = gpg_error_from_syserror (); + goto leave; + } + cdf->objidlen = objidlen; + cdf->objid = xtrymalloc (objidlen); + if (!cdf->objid) + { + err = gpg_error_from_syserror (); + xfree (cdf); + cdf = NULL; + goto leave; + } + memcpy (cdf->objid, objid, objidlen); + + cdf->fid = (SC_HSM_CA_PREFIX << 8) | (fid & 0xFF); + + log_debug ("CDF %04hX: id=", fid); + for (i=0; i < cdf->objidlen; i++) + log_printf ("%02X", cdf->objid[i]); + + goto leave; + + parse_error: + log_error ("error parsing CDF record (%d): %s - skipped\n", + where, errstr? errstr : gpg_strerror (err)); + err = 0; + + leave: + xfree (buffer); + if (err) + { + if (cdf) + { + if (cdf->objid) + xfree (cdf->objid); + xfree (cdf); + } + } + else + { + if (cdf) + cdf->next = *result; + *result = cdf; + } + return err; +} + + + +/* Read the device certificate and extract the serial number. + + EF.C_DevAut (2F02) contains two CVCs, the first is the device + certificate, the second is the issuer certificate. + + Example data: + +0000 7F 21 81 E2 7F 4E 81 9B 5F 29 01 00 42 0B 55 54 .!...N.._)..B.UT +0010 43 43 30 32 30 30 30 30 32 7F 49 4F 06 0A 04 00 CC0200002.IO.... +0020 7F 00 07 02 02 02 02 03 86 41 04 6D FF D6 85 57 .........A.m...W +0030 40 FB 10 5D 94 71 8A 94 D2 5E 50 33 E7 1E C0 6C @..].q...^P3...l +0040 63 D5 C8 FC BA F3 02 1D 70 23 F6 47 E8 35 48 EF c.......p#.G.5H. +0050 B5 94 72 3C 6F BE C0 EB 9A C7 FB 06 59 26 CF 65 ..r<o.......Y&.e +0060 EF A1 72 E0 98 F3 F0 44 1B B7 71 5F 20 10 55 54 ..r....D..q_ .UT +0070 43 43 30 32 30 30 30 31 33 30 30 30 30 30 7F 4C CC020001300000.L +0080 10 06 0B 2B 06 01 04 01 81 C3 1F 03 01 01 53 01 ...+..........S. +0090 00 5F 25 06 01 04 00 07 01 01 5F 24 06 02 01 00 ._%......._$.... +00A0 03 02 07 5F 37 40 7F 73 04 3B 06 63 79 41 BE 1A ..._7@.s.;.cyA.. +00B0 9F FC F6 77 67 2B 8A 41 D1 11 F6 9B 54 44 AD 19 ...wg+.A....TD.. +00C0 FB B8 0C C6 2F 34 71 8E 4F F6 92 59 34 61 D9 4F ..../4q.O..Y4a.O +00D0 4A 86 36 A8 D8 9A C6 3C 17 7E 71 CE A8 26 D0 C5 J.6....<.~q..&.. +00E0 25 61 78 9D 01 F8 7F 21 81 E0 7F 4E 81 99 5F 29 %ax....!...N.._) +00F0 01 00 42 0E 55 54 53 52 43 41 43 43 31 30 30 30 ..B.UTSRCACC1000 +0100 30 31 7F 49 4F 06 0A 04 00 7F 00 07 02 02 02 02 01.IO........... +0110 03 86 41 04 2F EA 33 47 7F 45 81 E2 FC CB 66 87 ..A./.3G.E....f. +0120 4B 96 21 1D 68 81 73 F2 9F 8F 6B 91 F0 DE 4B 54 K.!.h.s...k...KT +0130 8E D8 F0 82 3D CB BE 10 98 A3 1E 4F F0 72 5C E5 ....=......O.r\. +0140 7B 1E F7 3C 68 09 03 E8 A0 3F 3E 06 C1 B0 3C 18 {..<h....?>...<. +0150 6B AC 06 EA 5F 20 0B 55 54 43 43 30 32 30 30 30 k..._ .UTCC02000 +0160 30 32 7F 4C 10 06 0B 2B 06 01 04 01 81 C3 1F 03 02.L...+........ +0170 01 01 53 01 80 5F 25 06 01 03 00 03 02 08 5F 24 ..S.._%......._$ +0180 06 02 01 00 03 02 07 5F 37 40 93 C1 42 8B B3 8E ......._7@..B... +0190 42 61 6F 2C 19 E6 98 41 BD AA 60 BD E0 DD 4E F0 Bao,...A..`...N. +01A0 15 D5 4F 71 B7 BB C3 3A F2 AD 27 5E DD EE 6D 12 ..Oq...:..'^..m. +01B0 76 E6 2B A0 4C 01 CA C1 26 0C 45 6D C6 CB EC 92 v.+.L...&.Em.... +01C0 BF 38 18 AD 8F B2 29 40 A9 51 .8....)@.Q + + The certificate format is defined in BSI TR-03110: + +7F21 [ APPLICATION 33 ] IMPLICIT SEQUENCE SIZE( 226 ) + 7F4E [ APPLICATION 78 ] IMPLICIT SEQUENCE SIZE( 155 ) + 5F29 [ APPLICATION 41 ] SIZE( 1 ) -- profile id + 0000 00 + 42 [ APPLICATION 2 ] SIZE( 11 ) -- CAR + 0000 55 54 43 43 30 32 30 30 30 30 32 UTCC0200002 + 7F49 [ APPLICATION 73 ] IMPLICIT SEQUENCE SIZE( 79 ) -- public key + OBJECT IDENTIFIER = { id-TA-ECDSA-SHA-256 } + 86 [ CONTEXT 6 ] SIZE( 65 ) + 0000 04 6D FF D6 85 57 40 FB 10 5D 94 71 8A 94 D2 5E + 0010 50 33 E7 1E C0 6C 63 D5 C8 FC BA F3 02 1D 70 23 + 0020 F6 47 E8 35 48 EF B5 94 72 3C 6F BE C0 EB 9A C7 + 0030 FB 06 59 26 CF 65 EF A1 72 E0 98 F3 F0 44 1B B7 + 0040 71 + 5F20 [ APPLICATION 32 ] SIZE( 16 ) -- CHR + 0000 55 54 43 43 30 32 30 30 30 31 33 30 30 30 30 30 UTCC020001300000 + 7F4C [ APPLICATION 76 ] IMPLICIT SEQUENCE SIZE( 16 ) -- CHAT + OBJECT IDENTIFIER = { 1 3 6 1 4 1 24991 3 1 1 } + 53 [ APPLICATION 19 ] SIZE( 1 ) + 0000 00 + 5F25 [ APPLICATION 37 ] SIZE( 6 ) -- Valid from + 0000 01 04 00 07 01 01 + 5F24 [ APPLICATION 36 ] SIZE( 6 ) -- Valid to + 0000 02 01 00 03 02 07 + 5F37 [ APPLICATION 55 ] SIZE( 64 ) -- Signature + 0000 7F 73 04 3B 06 63 79 41 BE 1A 9F FC F6 77 67 2B + 0010 8A 41 D1 11 F6 9B 54 44 AD 19 FB B8 0C C6 2F 34 + 0020 71 8E 4F F6 92 59 34 61 D9 4F 4A 86 36 A8 D8 9A + 0030 C6 3C 17 7E 71 CE A8 26 D0 C5 25 61 78 9D 01 F8 + + The serial number is contained in tag 5F20, while the last 5 digits + are truncated. + */ +static gpg_error_t +read_serialno(app_t app) +{ + gpg_error_t err; + unsigned char *buffer = NULL; + size_t buflen; + const unsigned char *p,*chr; + size_t n, objlen, hdrlen, chrlen; + int class, tag, constructed, ndef; + + err = select_and_read_binary (app->slot, 0x2F02, "EF.C_DevAut", + &buffer, &buflen, 512); + if (err) + return err; + + p = buffer; + n = buflen; + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || tag != 0x21)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + { + log_error ("error parsing C_DevAut: %s\n", gpg_strerror (err)); + goto leave; + } + + chr = find_tlv (p, objlen, 0x5F20, &chrlen); + if (!chr || chrlen <= 5) + { + err = gpg_error (GPG_ERR_INV_OBJ); + log_error ("CHR not found in CVC\n"); + goto leave; + } + chrlen -= 5; + + app->serialno = xtrymalloc (chrlen); + if (!app->serialno) + { + err = gpg_error_from_syserror (); + goto leave; + } + + app->serialnolen = chrlen; + memcpy (app->serialno, chr, chrlen); + + leave: + xfree (buffer); + return err; +} + + +/* Get all the basic information from the SmartCard-HSM, check the + structure and initialize our local context. This is used once at + application initialization. */ +static gpg_error_t +read_meta (app_t app) +{ + gpg_error_t err; + unsigned char *eflist = NULL; + size_t eflistlen = 0; + int i; + + err = read_serialno(app); + if (err) + return err; + + err = list_ef (app->slot, &eflist, &eflistlen); + if (err) + return err; + + for (i = 0; i < eflistlen; i += 2) + { + switch(eflist[i]) + { + case SC_HSM_KEY_PREFIX: + if (eflist[i + 1] == 0) /* No key with ID=0 */ + break; + err = read_ef_prkd (app, ((SC_HSM_PRKD_PREFIX << 8) | eflist[i + 1]), + &app->app_local->private_key_info, + &app->app_local->certificate_info); + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = 0; + if (err) + return err; + break; + case SC_HSM_CD_PREFIX: + err = read_ef_cd (app, ((eflist[i] << 8) | eflist[i + 1]), + &app->app_local->trusted_certificate_info); + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = 0; + if (err) + return err; + break; + } + } + + xfree (eflist); + + return err; +} + + + +/* Helper to do_learn_status: Send information about all certificates + listed in CERTINFO back. Use CERTTYPE as type of the + certificate. */ +static gpg_error_t +send_certinfo (ctrl_t ctrl, const char *certtype, cdf_object_t certinfo) +{ + for (; certinfo; certinfo = certinfo->next) + { + char *buf, *p; + + buf = xtrymalloc (4 + certinfo->objidlen*2 + 1); + if (!buf) + return gpg_error_from_syserror (); + p = stpcpy (buf, "HSM."); + bin2hex (certinfo->objid, certinfo->objidlen, p); + + send_status_info (ctrl, "CERTINFO", + certtype, strlen (certtype), + buf, strlen (buf), + NULL, (size_t)0); + xfree (buf); + } + return 0; +} + + + +/* Get the keygrip of the private key object PRKDF. On success the + keygrip gets returned in the caller provided 41 byte buffer + R_GRIPSTR. */ +static gpg_error_t +keygripstr_from_prkdf (app_t app, prkdf_object_t prkdf, char *r_gripstr) +{ + gpg_error_t err; + cdf_object_t cdf; + unsigned char *der; + size_t derlen; + ksba_cert_t cert; + + /* Look for a matching certificate. A certificate matches if the Id + matches the one of the private key info. */ + for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next) + if (cdf->objidlen == prkdf->objidlen + && !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen)) + break; + if (!cdf) + return gpg_error (GPG_ERR_NOT_FOUND); + + err = readcert_by_cdf (app, cdf, &der, &derlen); + if (err) + return err; + + err = ksba_cert_new (&cert); + if (!err) + err = ksba_cert_init_from_mem (cert, der, derlen); + xfree (der); + if (!err) + err = app_help_get_keygrip_string (cert, r_gripstr, NULL, NULL); + ksba_cert_release (cert); + + return err; +} + + + +/* Helper to do_learn_status: Send information about all known + keypairs back. */ +static gpg_error_t +send_keypairinfo (app_t app, ctrl_t ctrl, prkdf_object_t keyinfo) +{ + gpg_error_t err; + + for (; keyinfo; keyinfo = keyinfo->next) + { + char gripstr[40+1]; + char *buf, *p; + + buf = xtrymalloc (4 + keyinfo->objidlen*2 + 1); + if (!buf) + return gpg_error_from_syserror (); + p = stpcpy (buf, "HSM."); + bin2hex (keyinfo->objid, keyinfo->objidlen, p); + + err = keygripstr_from_prkdf (app, keyinfo, gripstr); + if (err) + { + log_error ("can't get keygrip from %04X\n", keyinfo->key_reference); + } + else + { + assert (strlen (gripstr) == 40); + send_status_info (ctrl, "KEYPAIRINFO", + gripstr, 40, + buf, strlen (buf), + NULL, (size_t)0); + } + xfree (buf); + } + return 0; +} + + + +/* This is the handler for the LEARN command. */ +static gpg_error_t +do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) +{ + gpg_error_t err; + + if ((flags & 1)) + err = 0; + else + { + err = send_certinfo (ctrl, "100", app->app_local->certificate_info); + if (!err) + err = send_certinfo (ctrl, "101", + app->app_local->trusted_certificate_info); + } + + if (!err) + err = send_keypairinfo (app, ctrl, app->app_local->private_key_info); + + return err; +} + + + +/* Read a certificate using the information in CDF and return the + certificate in a newly allocated buffer R_CERT and its length + R_CERTLEN. */ +static gpg_error_t +readcert_by_cdf (app_t app, cdf_object_t cdf, + unsigned char **r_cert, size_t *r_certlen) +{ + gpg_error_t err; + unsigned char *buffer = NULL; + const unsigned char *p, *save_p; + size_t buflen, n; + int class, tag, constructed, ndef; + size_t totobjlen, objlen, hdrlen; + int rootca; + int i; + + *r_cert = NULL; + *r_certlen = 0; + + /* First check whether it has been cached. */ + if (cdf->image) + { + *r_cert = xtrymalloc (cdf->imagelen); + if (!*r_cert) + return gpg_error_from_syserror (); + memcpy (*r_cert, cdf->image, cdf->imagelen); + *r_certlen = cdf->imagelen; + return 0; + } + + err = select_and_read_binary (app->slot, cdf->fid, "CD", + &buffer, &buflen, 4096); + if (err) + { + log_error ("error reading certificate with Id "); + for (i=0; i < cdf->objidlen; i++) + log_printf ("%02X", cdf->objid[i]); + log_printf (": %s\n", gpg_strerror (err)); + goto leave; + } + + /* Check whether this is really a certificate. */ + p = buffer; + n = buflen; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + + if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) + rootca = 0; + else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed ) + rootca = 1; + else + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + totobjlen = objlen + hdrlen; + assert (totobjlen <= buflen); + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + + if (!rootca + && class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed) + { + /* The certificate seems to be contained in a userCertificate + container. Skip this and assume the following sequence is + the certificate. */ + if (n < objlen) + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + p += objlen; + n -= objlen; + save_p = p; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) ) + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + totobjlen = objlen + hdrlen; + assert (save_p + totobjlen <= buffer + buflen); + memmove (buffer, save_p, totobjlen); + } + + *r_cert = buffer; + buffer = NULL; + *r_certlen = totobjlen; + + /* Try to cache it. */ + if (!cdf->image && (cdf->image = xtrymalloc (*r_certlen))) + { + memcpy (cdf->image, *r_cert, *r_certlen); + cdf->imagelen = *r_certlen; + } + + + leave: + xfree (buffer); + return err; +} + + + +/* Handler for the READCERT command. + + Read the certificate with id CERTID (as returned by learn_status in + the CERTINFO status lines) and return it in the freshly allocated + buffer to be stored at R_CERT and its length at R_CERTLEN. A error + code will be returned on failure and R_CERT and R_CERTLEN will be + set to (NULL,0). */ +static gpg_error_t +do_readcert (app_t app, const char *certid, + unsigned char **r_cert, size_t *r_certlen) +{ + gpg_error_t err; + cdf_object_t cdf; + + *r_cert = NULL; + *r_certlen = 0; + err = cdf_object_from_certid (app, certid, &cdf); + if (!err) + err = readcert_by_cdf (app, cdf, r_cert, r_certlen); + return err; +} + + + +/* Implement the GETATTR command. This is similar to the LEARN + command but returns just one value via the status interface. */ +static gpg_error_t +do_getattr (app_t app, ctrl_t ctrl, const char *name) +{ + if (!strcmp (name, "$AUTHKEYID")) + { + char *buf, *p; + prkdf_object_t prkdf; + + /* We return the ID of the first private key capable of + signing. */ + for (prkdf = app->app_local->private_key_info; prkdf; + prkdf = prkdf->next) + if (prkdf->usageflags.sign) + break; + if (prkdf) + { + buf = xtrymalloc (4 + prkdf->objidlen*2 + 1); + if (!buf) + return gpg_error_from_syserror (); + p = stpcpy (buf, "HSM."); + bin2hex (prkdf->objid, prkdf->objidlen, p); + + send_status_info (ctrl, name, buf, strlen (buf), NULL, 0); + xfree (buf); + return 0; + } + } + else if (!strcmp (name, "$DISPSERIALNO")) + { + send_status_info (ctrl, name, app->serialno, app->serialnolen, NULL, 0); + return 0; + } + + return gpg_error (GPG_ERR_INV_NAME); +} + + + +/* Apply PKCS#1 V1.5 padding for signature operation. The function + * combines padding, digest info and the hash value. The buffer must + * be allocated by the caller matching the key size. */ +static void +apply_PKCS_padding(const unsigned char *dig, int diglen, + const unsigned char *prefix, int prefixlen, + unsigned char *buff, int bufflen) +{ + int i, n_ff; + + /* Caller must ensure a sufficient buffer. */ + if (diglen + prefixlen + 4 > bufflen) + return; + n_ff = bufflen - diglen - prefixlen - 3; + + *buff++ = 0x00; + *buff++ = 0x01; + for (i=0; i < n_ff; i++) + *buff++ = 0xFF; + *buff++ = 0x00; + + if (prefix) + memcpy (buff, prefix, prefixlen); + buff += prefixlen; + memcpy (buff, dig, diglen); +} + + + +/* Decode a digest info structure (DI,DILEN) to extract the hash + * value. The buffer HASH to receive the digest must be provided by + * the caller with HASHLEN pointing to the inbound length. HASHLEN is + * updated to the outbound length. */ +static int +hash_from_digestinfo (const unsigned char *di, size_t dilen, + unsigned char *hash, size_t *hashlen) +{ + const unsigned char *p,*pp; + size_t n, nn, objlen, hdrlen; + int class, tag, constructed, ndef; + gpg_error_t err; + + p = di; + n = dilen; + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if ( err ) + return err; + + pp = p; + nn = objlen; + + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if ( err ) + return err; + + pp += objlen; + nn -= objlen; + + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || tag != TAG_OCTET_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if ( err ) + return err; + + if (*hashlen < objlen) + return gpg_error (GPG_ERR_TOO_SHORT); + memcpy (hash, pp, objlen); + *hashlen = objlen; + return 0; +} + + +/* Perform PIN verification + */ +static gpg_error_t +verify_pin (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + pininfo_t pininfo; + char *pinvalue; + char *prompt; + int sw; + + sw = apdu_send_simple (app->slot, 0, 0x00, ISO7816_VERIFY, 0x00, 0x81, + -1, NULL); + + if (sw == SW_SUCCESS) + return 0; /* PIN already verified */ + + if (sw == SW_REF_DATA_INV) + { + log_error ("SmartCard-HSM not initialized. Run sc-hsm-tool first\n"); + return gpg_error (GPG_ERR_NO_PIN); + } + + if (sw == SW_CHV_BLOCKED) + { + log_error ("PIN Blocked\n"); + return gpg_error (GPG_ERR_PIN_BLOCKED); + } + + memset (&pininfo, 0, sizeof pininfo); + pininfo.fixedlen = 0; + pininfo.minlen = 6; + pininfo.maxlen = 15; + + prompt = "||Please enter the PIN"; + + if (!opt.disable_pinpad + && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) ) + { + err = pincb (pincb_arg, prompt, NULL); + if (err) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (err)); + return err; + } + + err = iso7816_verify_kp (app->slot, 0x81, &pininfo); + pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */ + } + else + { + err = pincb (pincb_arg, prompt, &pinvalue); + if (err) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (err)); + return err; + } + + err = iso7816_verify (app->slot, 0x81, pinvalue, strlen(pinvalue)); + xfree (pinvalue); + } + if (err) + { + log_error ("PIN verification failed: %s\n", gpg_strerror (err)); + return err; + } + log_debug ("PIN verification succeeded\n"); + return err; +} + + + +/* Handler for the PKSIGN command. + + 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; + that callback should return the PIN in an allocated buffer and + store that as the 3rd argument. + + The API is somewhat inconsistent: The caller can either supply + a plain hash and the algorithm in hashalgo or a complete + DigestInfo structure. The former is detect by characteristic length + of the provided data (20,28,32,48 or 64 byte). + + The function returns the RSA block in the size of the modulus or + the ECDSA signature in X9.62 format (SEQ/INT(r)/INT(s)) +*/ +static gpg_error_t +do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + 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 }; + static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */ + { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, + 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; + static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */ + { 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, + 0x1C }; + static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */ + { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20 }; + static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */ + { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, + 0x00, 0x04, 0x30 }; + static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */ + { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40 }; + + gpg_error_t err; + unsigned char cdsblk[256]; /* Raw PKCS#1 V1.5 block with padding + (RSA) or hash. */ + prkdf_object_t prkdf; /* The private key object. */ + size_t cdsblklen; + unsigned char algoid; + int sw; + + (void)ctrl; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + + if (indatalen > 124) /* Limit for 1024 bit key */ + return gpg_error (GPG_ERR_INV_VALUE); + + err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); + if (err) + return err; + if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover + ||prkdf->usageflags.non_repudiation)) + { + log_error ("key %s may not be used for signing\n", keyidstr); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + if (prkdf->keytype == KEY_TYPE_RSA) + { + algoid = 0x20; + + cdsblklen = prkdf->keysize >> 3; + if (!cdsblklen) + cdsblklen = 256; + + if (hashalgo == GCRY_MD_SHA1 && indatalen == 20) + apply_PKCS_padding (indata, indatalen, + sha1_prefix, sizeof(sha1_prefix), + cdsblk, cdsblklen); + else if (hashalgo == GCRY_MD_MD5 && indatalen == 20) + apply_PKCS_padding (indata, indatalen, + rmd160_prefix, sizeof(rmd160_prefix), + cdsblk, cdsblklen); + else if (hashalgo == GCRY_MD_SHA224 && indatalen == 28) + apply_PKCS_padding (indata, indatalen, + sha224_prefix, sizeof(sha224_prefix), + cdsblk, cdsblklen); + else if (hashalgo == GCRY_MD_SHA256 && indatalen == 32) + apply_PKCS_padding (indata, indatalen, + sha256_prefix, sizeof(sha256_prefix), + cdsblk, cdsblklen); + else if (hashalgo == GCRY_MD_SHA384 && indatalen == 48) + apply_PKCS_padding (indata, indatalen, + sha384_prefix, sizeof(sha384_prefix), + cdsblk, cdsblklen); + else if (hashalgo == GCRY_MD_SHA512 && indatalen == 64) + apply_PKCS_padding (indata, indatalen, + sha512_prefix, sizeof(sha512_prefix), + cdsblk, cdsblklen); + else /* Assume it's already a digest info or TLS_MD5SHA1 */ + apply_PKCS_padding (indata, indatalen, NULL, 0, cdsblk, cdsblklen); + } + else + { + algoid = 0x70; + if (indatalen != 20 && indatalen != 28 && indatalen != 32 + && indatalen != 48 && indatalen != 64) + { + cdsblklen = sizeof(cdsblk); + err = hash_from_digestinfo (indata, indatalen, cdsblk, &cdsblklen); + if (err) + { + log_error ("DigestInfo invalid: %s\n", gpg_strerror (err)); + return err; + } + } + else + { + memcpy (cdsblk, indata, indatalen); + cdsblklen = indatalen; + } + } + + err = verify_pin (app, pincb, pincb_arg); + if (err) + return err; + + sw = apdu_send_le (app->slot, 1, 0x80, 0x68, prkdf->key_reference, algoid, + cdsblklen, cdsblk, 0, outdata, outdatalen); + return iso7816_map_sw (sw); +} + + + +/* Handler for the PKAUTH command. + + This is basically the same as the PKSIGN command but we first check + that the requested key is suitable for authentication; that is, it + must match the criteria used for the attribute $AUTHKEYID. See + do_sign for calling conventions; there is no HASHALGO, though. */ +static gpg_error_t +do_auth (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + gpg_error_t err; + prkdf_object_t prkdf; + int algo; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + + err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); + if (err) + return err; + if (!prkdf->usageflags.sign) + { + log_error ("key %s may not be used for authentication\n", keyidstr); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + algo = indatalen == 36? MD_USER_TLS_MD5SHA1 : GCRY_MD_SHA1; + return do_sign (app, ctrl, keyidstr, algo, pincb, pincb_arg, + indata, indatalen, outdata, outdatalen); +} + + + +/* Check PKCS#1 V1.5 padding and extract plain text. The function + * allocates a buffer for the plain text. The caller must release the + * buffer. */ +static gpg_error_t +strip_PKCS15_padding(unsigned char *src, int srclen, unsigned char **dst, + size_t *dstlen) +{ + unsigned char *p; + + if (srclen < 2) + return gpg_error (GPG_ERR_DECRYPT_FAILED); + if (*src++ != 0x00) + return gpg_error (GPG_ERR_DECRYPT_FAILED); + if (*src++ != 0x02) + return gpg_error (GPG_ERR_DECRYPT_FAILED); + srclen -= 2; + while ((srclen > 0) && *src) + { + src++; + srclen--; + } + + if (srclen < 2) + return gpg_error (GPG_ERR_DECRYPT_FAILED); + + src++; + srclen--; + + p = xtrymalloc (srclen); + if (!p) + return gpg_error_from_syserror (); + + memcpy (p, src, srclen); + *dst = p; + *dstlen = srclen; + + return 0; +} + + +/* Decrypt a PKCS#1 V1.5 formatted cryptogram using the referenced + key. */ +static gpg_error_t +do_decipher (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen, + unsigned int *r_info) +{ + gpg_error_t err; + unsigned char p1blk[256]; /* Enciphered P1 block */ + prkdf_object_t prkdf; /* The private key object. */ + unsigned char *rspdata; + size_t rspdatalen; + size_t p1blklen; + int sw; + + (void)ctrl; + + if (!keyidstr || !*keyidstr || !indatalen) + return gpg_error (GPG_ERR_INV_VALUE); + + err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); + if (err) + return err; + if (!(prkdf->usageflags.decrypt || prkdf->usageflags.unwrap)) + { + log_error ("key %s may not be used for deciphering\n", keyidstr); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + if (prkdf->keytype != KEY_TYPE_RSA) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + p1blklen = prkdf->keysize >> 3; + if (!p1blklen) + p1blklen = 256; + + /* The input may be shorter (due to MPIs not storing leading zeroes) + or longer than the block size. We put INDATA right aligned into + the buffer. If INDATA is longer than the block size we truncate + it on the left. */ + memset (p1blk, 0, sizeof(p1blk)); + if (indatalen > p1blklen) + memcpy (p1blk, (unsigned char *)indata + (indatalen - p1blklen), p1blklen); + else + memcpy (p1blk + (p1blklen - indatalen), indata, indatalen); + + + err = verify_pin(app, pincb, pincb_arg); + if (err) + return err; + + sw = apdu_send_le (app->slot, 1, 0x80, 0x62, prkdf->key_reference, 0x21, + p1blklen, p1blk, 0, &rspdata, &rspdatalen); + err = iso7816_map_sw (sw); + if (err) + { + log_error ("Decrypt failed: %s\n", gpg_strerror (err)); + return err; + } + + err = strip_PKCS15_padding (rspdata, rspdatalen, outdata, outdatalen); + xfree (rspdata); + + if (!err) + *r_info |= APP_DECIPHER_INFO_NOPAD; + + return err; +} + + + +/* + * Select the SmartCard-HSM application on the card in SLOT. + */ +gpg_error_t +app_select_sc_hsm (app_t app) +{ + int slot = app->slot; + int rc; + + rc = iso7816_select_application (slot, sc_hsm_aid, sizeof sc_hsm_aid, 0); + if (!rc) + { + app->apptype = APPTYPE_SC_HSM; + + app->app_local = xtrycalloc (1, sizeof *app->app_local); + if (!app->app_local) + { + rc = gpg_error_from_syserror (); + goto leave; + } + + rc = read_meta (app); + if (rc) + goto leave; + + app->fnc.deinit = do_deinit; + app->fnc.learn_status = do_learn_status; + app->fnc.readcert = do_readcert; + app->fnc.getattr = do_getattr; + app->fnc.setattr = NULL; + app->fnc.genkey = NULL; + app->fnc.sign = do_sign; + app->fnc.auth = do_auth; + app->fnc.decipher = do_decipher; + app->fnc.change_pin = NULL; + app->fnc.check_pin = NULL; + + leave: + if (rc) + do_deinit (app); + } + + return rc; +} diff --git a/scd/app.c b/scd/app.c new file mode 100644 index 0000000..846fc77 --- /dev/null +++ b/scd/app.c @@ -0,0 +1,1356 @@ +/* app.c - Application selection. + * Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <npth.h> + +#include "scdaemon.h" +#include "../common/exechelp.h" +#include "iso7816.h" +#include "apdu.h" +#include "../common/tlv.h" + +static npth_mutex_t app_list_lock; +static app_t app_top; + + +/* List of all supported apps. */ +static struct +{ + apptype_t apptype; + char const *name; +} supported_app_list[] = + {{ APPTYPE_OPENPGP , "openpgp" }, + { APPTYPE_NKS , "nks" }, + { APPTYPE_P15 , "p15" }, + { APPTYPE_GELDKARTE, "geldkarte" }, + { APPTYPE_DINSIG , "dinsig" }, + { APPTYPE_SC_HSM , "sc-hsm" }, + { APPTYPE_NONE , NULL } + /* APPTYPE_UNDEFINED is special and not listed here. */ + }; + + + +static void +print_progress_line (void *opaque, const char *what, int pc, int cur, int tot) +{ + ctrl_t ctrl = opaque; + char line[100]; + + if (ctrl) + { + snprintf (line, sizeof line, "%s %c %d %d", what, pc, cur, tot); + send_status_direct (ctrl, "PROGRESS", line); + } +} + + +/* Map an application type to a string. Never returns NULL. */ +const char * +strapptype (apptype_t t) +{ + int i; + + for (i=0; supported_app_list[i].apptype; i++) + if (supported_app_list[i].apptype == t) + return supported_app_list[i].name; + return t == APPTYPE_UNDEFINED? "undefined" : t? "?" : "none"; +} + + +/* Return the apptype for NAME. */ +static apptype_t +apptype_from_name (const char *name) +{ + int i; + + if (!name) + return APPTYPE_NONE; + + for (i=0; supported_app_list[i].apptype; i++) + if (!ascii_strcasecmp (supported_app_list[i].name, name)) + return supported_app_list[i].apptype; + if (!ascii_strcasecmp ("undefined", name)) + return APPTYPE_UNDEFINED; + return APPTYPE_NONE; +} + + +/* Lock the reader SLOT. This function shall be used right before + calling any of the actual application functions to serialize access + to the reader. We do this always even if the reader is not + actually used. This allows an actual connection to assume that it + never shares a reader (while performing one command). Returns 0 on + success; only then the unlock_reader function must be called after + returning from the handler. */ +static gpg_error_t +lock_app (app_t app, ctrl_t ctrl) +{ + if (npth_mutex_lock (&app->lock)) + { + gpg_error_t err = gpg_error_from_syserror (); + log_error ("failed to acquire APP lock for %p: %s\n", + app, gpg_strerror (err)); + return err; + } + + apdu_set_progress_cb (app->slot, print_progress_line, ctrl); + apdu_set_prompt_cb (app->slot, popup_prompt, ctrl); + + return 0; +} + +/* Release a lock on the reader. See lock_reader(). */ +static void +unlock_app (app_t app) +{ + apdu_set_progress_cb (app->slot, NULL, NULL); + apdu_set_prompt_cb (app->slot, NULL, NULL); + + if (npth_mutex_unlock (&app->lock)) + { + gpg_error_t err = gpg_error_from_syserror (); + log_error ("failed to release APP lock for %p: %s\n", + app, gpg_strerror (err)); + } +} + + +/* This function may be called to print information pertaining to the + current state of this module to the log. */ +void +app_dump_state (void) +{ + app_t a; + + npth_mutex_lock (&app_list_lock); + for (a = app_top; a; a = a->next) + log_info ("app_dump_state: app=%p type='%s'\n", a, strapptype (a->apptype)); + npth_mutex_unlock (&app_list_lock); +} + +/* Check whether the application NAME is allowed. This does not mean + we have support for it though. */ +static int +is_app_allowed (const char *name) +{ + strlist_t l; + + for (l=opt.disabled_applications; l; l = l->next) + if (!strcmp (l->d, name)) + return 0; /* no */ + return 1; /* yes */ +} + + +static gpg_error_t +check_conflict (app_t app, const char *name) +{ + if (!app || !name + || (app->apptype && app->apptype == apptype_from_name (name))) + return 0; + + if (app->apptype && app->apptype == APPTYPE_UNDEFINED) + return 0; + + log_info ("application '%s' in use - can't switch\n", + strapptype (app->apptype)); + + return gpg_error (GPG_ERR_CONFLICT); +} + +/* This function is used by the serialno command to check for an + application conflict which may appear if the serialno command is + used to request a specific application and the connection has + already done a select_application. */ +gpg_error_t +check_application_conflict (const char *name, app_t app) +{ + return check_conflict (app, name); +} + + +gpg_error_t +app_reset (app_t app, ctrl_t ctrl, int send_reset) +{ + gpg_error_t err = 0; + + if (send_reset) + { + int sw; + + lock_app (app, ctrl); + sw = apdu_reset (app->slot); + if (sw) + err = gpg_error (GPG_ERR_CARD_RESET); + + app->reset_requested = 1; + unlock_app (app); + + scd_kick_the_loop (); + gnupg_sleep (1); + } + else + { + ctrl->app_ctx = NULL; + release_application (app, 0); + } + + return err; +} + +static gpg_error_t +app_new_register (int slot, ctrl_t ctrl, const char *name, + int periodical_check_needed) +{ + gpg_error_t err = 0; + app_t app = NULL; + unsigned char *result = NULL; + size_t resultlen; + int want_undefined; + + /* Need to allocate a new one. */ + app = xtrycalloc (1, sizeof *app); + if (!app) + { + err = gpg_error_from_syserror (); + log_info ("error allocating context: %s\n", gpg_strerror (err)); + return err; + } + + app->slot = slot; + app->card_status = (unsigned int)-1; + + if (npth_mutex_init (&app->lock, NULL)) + { + err = gpg_error_from_syserror (); + log_error ("error initializing mutex: %s\n", gpg_strerror (err)); + xfree (app); + return err; + } + + err = lock_app (app, ctrl); + if (err) + { + xfree (app); + return err; + } + + want_undefined = (name && !strcmp (name, "undefined")); + + /* Try to read the GDO file first to get a default serial number. + We skip this if the undefined application has been requested. */ + if (!want_undefined) + { + err = iso7816_select_file (slot, 0x3F00, 1); + if (gpg_err_code (err) == GPG_ERR_CARD) + { + /* Might be SW==0x7D00. Let's test whether it is a Yubikey + * by selecting its manager application and then reading the + * config. */ + static char const yk_aid[] = + { 0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }; /*MGR*/ + static char const otp_aid[] = + { 0xA0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01 }; /*OTP*/ + unsigned char *buf; + size_t buflen; + const unsigned char *s0; + unsigned char formfactor; + size_t n; + + if (!iso7816_select_application (slot, yk_aid, sizeof yk_aid, + 0x0001) + && !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0, + NULL, &buf, &buflen)) + { + app->cardtype = CARDTYPE_YUBIKEY; + if (opt.verbose) + { + log_info ("Yubico: config="); + log_printhex (buf, buflen, ""); + } + + /* We skip the first byte which seems to be the total + * length of the config data. */ + if (buflen > 1) + { + s0 = find_tlv (buf+1, buflen-1, 0x04, &n); /* Form factor */ + formfactor = (s0 && n == 1)? *s0 : 0; + + s0 = find_tlv (buf+1, buflen-1, 0x02, &n); /* Serial */ + if (s0 && n <= 4) + { + app->serialno = xtrymalloc (3 + 1 + 4); + if (app->serialno) + { + app->serialnolen = 3 + 1 + 4; + app->serialno[0] = 0xff; + app->serialno[1] = 0x02; + app->serialno[2] = 0x0; + app->serialno[3] = formfactor; + memset (app->serialno + 4, 0, 4 - n); + memcpy (app->serialno + 4 + 4 - n, s0, n); + err = app_munge_serialno (app); + } + } + + s0 = find_tlv (buf+1, buflen-1, 0x05, &n); /* version */ + if (s0 && n == 3) + app->cardversion = ((s0[0]<<16)|(s0[1]<<8)|s0[2]); + else if (!s0) + { + /* No version - this is not a Yubikey 5. We now + * switch to the OTP app and take the first + * three bytes of the response as version + * number. */ + xfree (buf); + buf = NULL; + if (!iso7816_select_application_ext (slot, + otp_aid, sizeof otp_aid, + 1, &buf, &buflen) + && buflen > 3) + app->cardversion = ((buf[0]<<16)|(buf[1]<<8)|buf[2]); + } + } + xfree (buf); + } + } + else + { + unsigned char *atr; + size_t atrlen; + + /* This is heuristics to identify different implementations. */ + atr = apdu_get_atr (slot, &atrlen); + if (atr) + { + if (atrlen == 21 && atr[2] == 0x11) + app->cardtype = CARDTYPE_GNUK; + else if (atrlen == 21 && atr[7] == 0x75) + app->cardtype = CARDTYPE_ZEITCONTROL; + xfree (atr); + } + } + + if (!err && app->cardtype != CARDTYPE_YUBIKEY) + err = iso7816_select_file (slot, 0x2F02, 0); + if (!err && app->cardtype != CARDTYPE_YUBIKEY) + err = iso7816_read_binary (slot, 0, 0, &result, &resultlen); + if (!err && app->cardtype != CARDTYPE_YUBIKEY) + { + size_t n; + const unsigned char *p; + + p = find_tlv_unchecked (result, resultlen, 0x5A, &n); + if (p) + resultlen -= (p-result); + if (p && n > resultlen && n == 0x0d && resultlen+1 == n) + { + /* The object 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. */ + log_info ("enabling BMI testcard workaround\n"); + n--; + } + + if (p && n <= resultlen) + { + /* The GDO file is pretty short, thus we simply reuse it for + storing the serial number. */ + memmove (result, p, n); + app->serialno = result; + app->serialnolen = n; + err = app_munge_serialno (app); + if (err) + goto leave; + } + else + xfree (result); + result = NULL; + } + } + + /* For certain error codes, there is no need to try more. */ + if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT + || gpg_err_code (err) == GPG_ERR_ENODEV) + goto leave; + + /* Figure out the application to use. */ + if (want_undefined) + { + /* We switch to the "undefined" application only if explicitly + requested. */ + app->apptype = APPTYPE_UNDEFINED; + err = 0; + } + else + err = gpg_error (GPG_ERR_NOT_FOUND); + + /* Fixme: Use a table like we do in 2.3. */ + if (err && is_app_allowed ("openpgp") + && (!name || !strcmp (name, "openpgp"))) + err = app_select_openpgp (app); + if (err && is_app_allowed ("nks") && (!name || !strcmp (name, "nks"))) + err = app_select_nks (app); + if (err && is_app_allowed ("p15") && (!name || !strcmp (name, "p15"))) + err = app_select_p15 (app); + if (err && is_app_allowed ("geldkarte") + && (!name || !strcmp (name, "geldkarte"))) + err = app_select_geldkarte (app); + if (err && is_app_allowed ("dinsig") && (!name || !strcmp (name, "dinsig"))) + err = app_select_dinsig (app); + if (err && is_app_allowed ("sc-hsm") && (!name || !strcmp (name, "sc-hsm"))) + err = app_select_sc_hsm (app); + if (err && name && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE) + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + + leave: + if (err) + { + if (name) + log_info ("can't select application '%s': %s\n", + name, gpg_strerror (err)); + else + log_info ("no supported card application found: %s\n", + gpg_strerror (err)); + unlock_app (app); + xfree (app); + return err; + } + + app->periodical_check_needed = periodical_check_needed; + + npth_mutex_lock (&app_list_lock); + app->next = app_top; + app_top = app; + npth_mutex_unlock (&app_list_lock); + unlock_app (app); + return 0; +} + +/* If called with NAME as NULL, select the best fitting application + and return a context; otherwise select the application with NAME + and return a context. Returns an error code and stores NULL at + R_APP if no application was found or no card is present. */ +gpg_error_t +select_application (ctrl_t ctrl, const char *name, app_t *r_app, + int scan, const unsigned char *serialno_bin, + size_t serialno_bin_len) +{ + gpg_error_t err = 0; + app_t a, a_prev = NULL; + + *r_app = NULL; + + if (scan || !app_top) + { + struct dev_list *l; + int new_app = 0; + + /* Scan the devices to find new device(s). */ + err = apdu_dev_list_start (opt.reader_port, &l); + if (err) + return err; + + while (1) + { + int slot; + int periodical_check_needed_this; + + slot = apdu_open_reader (l, !app_top); + if (slot < 0) + break; + + periodical_check_needed_this = apdu_connect (slot); + if (periodical_check_needed_this < 0) + { + /* We close a reader with no card. */ + err = gpg_error (GPG_ERR_ENODEV); + } + else + { + err = app_new_register (slot, ctrl, name, + periodical_check_needed_this); + new_app++; + } + + if (err) + apdu_close_reader (slot); + } + + apdu_dev_list_finish (l); + + /* If new device(s), kick the scdaemon loop. */ + if (new_app) + scd_kick_the_loop (); + } + + npth_mutex_lock (&app_list_lock); + for (a = app_top; a; a = a->next) + { + lock_app (a, ctrl); + if (serialno_bin == NULL) + break; + if (a->serialnolen == serialno_bin_len + && !memcmp (a->serialno, serialno_bin, a->serialnolen)) + break; + unlock_app (a); + a_prev = a; + } + + if (a) + { + err = check_conflict (a, name); + if (!err) + { + a->ref_count++; + *r_app = a; + if (a_prev) + { + a_prev->next = a->next; + a->next = app_top; + app_top = a; + } + } + unlock_app (a); + } + else + err = gpg_error (GPG_ERR_ENODEV); + + npth_mutex_unlock (&app_list_lock); + + return err; +} + + +char * +get_supported_applications (void) +{ + const char *list[] = { + "openpgp", + "nks", + "p15", + "geldkarte", + "dinsig", + "sc-hsm", + /* Note: "undefined" is not listed here because it needs special + treatment by the client. */ + NULL + }; + int idx; + size_t nbytes; + char *buffer, *p; + + for (nbytes=1, idx=0; list[idx]; idx++) + nbytes += strlen (list[idx]) + 1 + 1; + + buffer = xtrymalloc (nbytes); + if (!buffer) + return NULL; + + for (p=buffer, idx=0; list[idx]; idx++) + if (is_app_allowed (list[idx])) + p = stpcpy (stpcpy (p, list[idx]), ":\n"); + *p = 0; + + return buffer; +} + + +/* Deallocate the application. */ +static void +deallocate_app (app_t app) +{ + app_t a, a_prev = NULL; + + for (a = app_top; a; a = a->next) + if (a == app) + { + if (a_prev == NULL) + app_top = a->next; + else + a_prev->next = a->next; + break; + } + else + a_prev = a; + + if (app->ref_count) + log_error ("trying to release context used yet (%d)\n", app->ref_count); + + if (app->fnc.deinit) + { + app->fnc.deinit (app); + app->fnc.deinit = NULL; + } + + xfree (app->serialno); + + unlock_app (app); + xfree (app); +} + +/* Free the resources associated with the application APP. APP is + allowed to be NULL in which case this is a no-op. Note that we are + using reference counting to track the users of the application and + actually deferring the deallocation to allow for a later reuse by + a new connection. */ +void +release_application (app_t app, int locked_already) +{ + if (!app) + return; + + /* We don't deallocate app here. Instead, we keep it. This is + useful so that a card does not get reset even if only one session + is using the card - this way the PIN cache and other cached data + are preserved. */ + + if (!locked_already) + lock_app (app, NULL); + + if (!app->ref_count) + log_bug ("trying to release an already released context\n"); + + --app->ref_count; + if (!locked_already) + unlock_app (app); +} + + + +/* The serial number may need some cosmetics. Do it here. This + function shall only be called once after a new serial number has + been put into APP->serialno. + + Prefixes we use: + + FF 00 00 = For serial numbers starting with an FF + FF 01 00 = Some german p15 cards return an empty serial number so the + serial number from the EF(TokenInfo) is used instead. + FF 7F 00 = No serialno. + + All other serial number not starting with FF are used as they are. +*/ +gpg_error_t +app_munge_serialno (app_t app) +{ + if (app->serialnolen && app->serialno[0] == 0xff) + { + /* The serial number starts with our special prefix. This + requires that we put our default prefix "FF0000" in front. */ + unsigned char *p = xtrymalloc (app->serialnolen + 3); + if (!p) + return gpg_error_from_syserror (); + memcpy (p, "\xff\0", 3); + memcpy (p+3, app->serialno, app->serialnolen); + app->serialnolen += 3; + xfree (app->serialno); + app->serialno = p; + } + else if (!app->serialnolen) + { + unsigned char *p = xtrymalloc (3); + if (!p) + return gpg_error_from_syserror (); + memcpy (p, "\xff\x7f", 3); + app->serialnolen = 3; + xfree (app->serialno); + app->serialno = p; + } + return 0; +} + + + +/* Retrieve the serial number of the card. The serial number is + returned as a malloced string (hex encoded) in SERIAL. Caller must + free SERIAL unless the function returns an error. */ +char * +app_get_serialno (app_t app) +{ + char *serial; + + if (!app) + return NULL; + + if (!app->serialnolen) + serial = xtrystrdup ("FF7F00"); + else + serial = bin2hex (app->serialno, app->serialnolen, NULL); + + return serial; +} + + +/* Return an allocated string with the serial number in a format to be + * show to the user. With NOFALLBACK set to true return NULL if such an + * abbreviated S/N is not available, else return the full serial + * number as a hex string. May return NULL on malloc problem. */ +char * +app_get_dispserialno (app_t app, int nofallback) +{ + char *result, *p; + unsigned long sn; + + if (app && app->serialno && app->serialnolen == 3+1+4 + && !memcmp (app->serialno, "\xff\x02\x00", 3)) + { + /* This is a 4 byte S/N of a Yubikey which seems to be printed + * on the token in decimal. Maybe they will print larger S/N + * also in decimal but we can't be sure, thus do it only for + * these 32 bit numbers. */ + sn = app->serialno[4] * 16777216; + sn += app->serialno[5] * 65536; + sn += app->serialno[6] * 256; + sn += app->serialno[7]; + if ((app->cardversion >> 16) >= 5) + result = xtryasprintf ("%lu %03lu %03lu", + (sn/1000000ul), + (sn/1000ul % 1000ul), + (sn % 1000ul)); + else + result = xtryasprintf ("%lu", sn); + } + else if (app && app->cardtype == CARDTYPE_YUBIKEY) + { + /* Get back the printed Yubikey number from the OpenPGP AID + * Example: D2760001240100000006120808620000 + */ + result = app_get_serialno (app); + if (result && strlen (result) >= 28 && !strncmp (result+16, "0006", 4)) + { + sn = atoi_4 (result+20) * 10000; + sn += atoi_4 (result+24); + if ((app->cardversion >> 16) >= 5) + p = xtryasprintf ("%lu %03lu %03lu", + (sn/1000000ul), + (sn/1000ul % 1000ul), + (sn % 1000ul)); + else + p = xtryasprintf ("%lu", sn); + if (p) + { + xfree (result); + result = p; + } + } + else if (nofallback) + { + xfree (result); + result = NULL; + } + } + else if (app && app->apptype == APPTYPE_OPENPGP) + { + /* Extract number from standard OpenPGP AID. */ + result = app_get_serialno (app); + if (result && strlen (result) > 16+12) + { + memcpy (result, result+16, 4); + result[4] = ' '; + memcpy (result+5, result+20, 8); + result[13] = 0; + } + else if (nofallback) + { + xfree (result); + result = NULL; + } + } + else if (nofallback) + result = NULL; /* No Abbreviated S/N. */ + else + result = app_get_serialno (app); + + return result; +} + + +/* Write out the application specifig status lines for the LEARN + command. */ +gpg_error_t +app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) +{ + gpg_error_t err; + + if (!app) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->fnc.learn_status) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + /* We do not send APPTYPE if only keypairinfo is requested. */ + if (app->apptype && !(flags & 1)) + send_status_direct (ctrl, "APPTYPE", strapptype (app->apptype)); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.learn_status (app, ctrl, flags); + unlock_app (app); + return err; +} + + +/* Read the certificate with id CERTID (as returned by learn_status in + the CERTINFO status lines) and return it in the freshly allocated + buffer put into CERT and the length of the certificate put into + CERTLEN. */ +gpg_error_t +app_readcert (app_t app, ctrl_t ctrl, const char *certid, + unsigned char **cert, size_t *certlen) +{ + gpg_error_t err; + + if (!app) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.readcert) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.readcert (app, certid, cert, certlen); + unlock_app (app); + return err; +} + + +/* Read the key with ID KEYID. On success a canonical encoded + S-expression with the public key will get stored at PK and its + length (for assertions) at PKLEN; the caller must release that + buffer. On error NULL will be stored at PK and PKLEN and an error + code returned. + + This function might not be supported by all applications. */ +gpg_error_t +app_readkey (app_t app, ctrl_t ctrl, int advanced, const char *keyid, + unsigned char **pk, size_t *pklen) +{ + gpg_error_t err; + + if (pk) + *pk = NULL; + if (pklen) + *pklen = 0; + + if (!app || !keyid || !pk || !pklen) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.readkey) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err= app->fnc.readkey (app, ctrl, keyid, + advanced? APP_READKEY_FLAG_ADVANCED : 0, + pk, pklen); + unlock_app (app); + return err; +} + + +/* Perform a GETATTR operation. */ +gpg_error_t +app_getattr (app_t app, ctrl_t ctrl, const char *name) +{ + gpg_error_t err; + + if (!app || !name || !*name) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + + if (app->apptype && name && !strcmp (name, "APPTYPE")) + { + send_status_direct (ctrl, "APPTYPE", strapptype (app->apptype)); + return 0; + } + if (name && !strcmp (name, "SERIALNO")) + { + char *serial; + + serial = app_get_serialno (app); + if (!serial) + return gpg_error (GPG_ERR_INV_VALUE); + + send_status_direct (ctrl, "SERIALNO", serial); + xfree (serial); + return 0; + } + + if (!app->fnc.getattr) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.getattr (app, ctrl, name); + unlock_app (app); + return err; +} + +/* Perform a SETATTR operation. */ +gpg_error_t +app_setattr (app_t app, ctrl_t ctrl, const char *name, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + + if (!app || !name || !*name || !value) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.setattr) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.setattr (app, ctrl, name, pincb, pincb_arg, value, valuelen); + unlock_app (app); + return err; +} + +/* 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. */ +gpg_error_t +app_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + gpg_error_t err; + + if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.sign) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.sign (app, ctrl, keyidstr, hashalgo, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + unlock_app (app); + if (opt.verbose) + log_info ("operation sign result: %s\n", gpg_strerror (err)); + return err; +} + +/* 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. */ +gpg_error_t +app_auth (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + gpg_error_t err; + + if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.auth) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.auth (app, ctrl, keyidstr, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + unlock_app (app); + if (opt.verbose) + log_info ("operation auth result: %s\n", gpg_strerror (err)); + return err; +} + + +/* 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. */ +gpg_error_t +app_decipher (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen, + unsigned int *r_info) +{ + gpg_error_t err; + + *r_info = 0; + + if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.decipher) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.decipher (app, ctrl, keyidstr, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen, + r_info); + unlock_app (app); + if (opt.verbose) + log_info ("operation decipher result: %s\n", gpg_strerror (err)); + return err; +} + + +/* Perform the WRITECERT operation. */ +gpg_error_t +app_writecert (app_t app, ctrl_t ctrl, + const char *certidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *data, size_t datalen) +{ + gpg_error_t err; + + if (!app || !certidstr || !*certidstr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.writecert) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.writecert (app, ctrl, certidstr, + pincb, pincb_arg, data, datalen); + unlock_app (app); + if (opt.verbose) + log_info ("operation writecert result: %s\n", gpg_strerror (err)); + return err; +} + + +/* Perform the WRITEKEY operation. */ +gpg_error_t +app_writekey (app_t app, ctrl_t ctrl, + const char *keyidstr, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *keydata, size_t keydatalen) +{ + gpg_error_t err; + + if (!app || !keyidstr || !*keyidstr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.writekey) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.writekey (app, ctrl, keyidstr, flags, + pincb, pincb_arg, keydata, keydatalen); + unlock_app (app); + if (opt.verbose) + log_info ("operation writekey result: %s\n", gpg_strerror (err)); + return err; +} + + +/* Perform a SETATTR operation. */ +gpg_error_t +app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, + const char *keytype, unsigned int flags, time_t createtime, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + + if (!app || !keynostr || !*keynostr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.genkey) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.genkey (app, ctrl, keynostr, keytype, flags, + createtime, pincb, pincb_arg); + unlock_app (app); + if (opt.verbose) + log_info ("operation genkey result: %s\n", gpg_strerror (err)); + return err; +} + + +/* Perform a GET CHALLENGE operation. This function is special as it + directly accesses the card without any application specific + wrapper. */ +gpg_error_t +app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer) +{ + gpg_error_t err; + + if (!app || !nbytes || !buffer) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + err = lock_app (app, ctrl); + if (err) + return err; + err = iso7816_get_challenge (app->slot, nbytes, buffer); + unlock_app (app); + return err; +} + + + +/* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */ +gpg_error_t +app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, + unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + + if (!app || !chvnostr || !*chvnostr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.change_pin) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.change_pin (app, ctrl, chvnostr, flags, pincb, pincb_arg); + unlock_app (app); + if (opt.verbose) + log_info ("operation change_pin result: %s\n", gpg_strerror (err)); + return err; +} + + +/* Perform a VERIFY operation without doing anything else. This may + be used to initialize a the PIN cache for long lasting other + operations. Its use is highly application dependent. */ +gpg_error_t +app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + + if (!app || !keyidstr || !*keyidstr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.check_pin) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.check_pin (app, ctrl, keyidstr, pincb, pincb_arg); + unlock_app (app); + if (opt.verbose) + log_info ("operation check_pin result: %s\n", gpg_strerror (err)); + return err; +} + +static void +report_change (int slot, int old_status, int cur_status) +{ + char *homestr, *envstr; + char *fname; + char templ[50]; + estream_t fp; + + snprintf (templ, sizeof templ, "reader_%d.status", slot); + fname = make_filename (gnupg_homedir (), templ, NULL ); + fp = es_fopen (fname, "w"); + if (fp) + { + es_fprintf (fp, "%s\n", + (cur_status & 1)? "USABLE": + (cur_status & 4)? "ACTIVE": + (cur_status & 2)? "PRESENT": "NOCARD"); + es_fclose (fp); + } + xfree (fname); + + homestr = make_filename (gnupg_homedir (), NULL); + if (gpgrt_asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0) + log_error ("out of core while building environment\n"); + else + { + gpg_error_t err; + const char *args[9], *envs[2]; + char numbuf1[30], numbuf2[30], numbuf3[30]; + + envs[0] = envstr; + envs[1] = NULL; + + sprintf (numbuf1, "%d", slot); + sprintf (numbuf2, "0x%04X", old_status); + sprintf (numbuf3, "0x%04X", cur_status); + args[0] = "--reader-port"; + args[1] = numbuf1; + args[2] = "--old-code"; + args[3] = numbuf2; + args[4] = "--new-code"; + args[5] = numbuf3; + args[6] = "--status"; + args[7] = ((cur_status & 1)? "USABLE": + (cur_status & 4)? "ACTIVE": + (cur_status & 2)? "PRESENT": "NOCARD"); + args[8] = NULL; + + fname = make_filename (gnupg_homedir (), "scd-event", NULL); + err = gnupg_spawn_process_detached (fname, args, envs); + if (err && gpg_err_code (err) != GPG_ERR_ENOENT) + log_error ("failed to run event handler '%s': %s\n", + fname, gpg_strerror (err)); + xfree (fname); + xfree (envstr); + } + xfree (homestr); +} + +int +scd_update_reader_status_file (void) +{ + app_t a, app_next; + int periodical_check_needed = 0; + + npth_mutex_lock (&app_list_lock); + for (a = app_top; a; a = app_next) + { + int sw; + unsigned int status; + + lock_app (a, NULL); + app_next = a->next; + + if (a->reset_requested) + status = 0; + else + { + sw = apdu_get_status (a->slot, 0, &status); + if (sw == SW_HOST_NO_READER) + { + /* Most likely the _reader_ has been unplugged. */ + status = 0; + } + else if (sw) + { + /* Get status failed. Ignore that. */ + if (a->periodical_check_needed) + periodical_check_needed = 1; + unlock_app (a); + continue; + } + } + + if (a->card_status != status) + { + report_change (a->slot, a->card_status, status); + send_client_notifications (a, status == 0); + + if (status == 0) + { + log_debug ("Removal of a card: %d\n", a->slot); + apdu_close_reader (a->slot); + deallocate_app (a); + } + else + { + a->card_status = status; + if (a->periodical_check_needed) + periodical_check_needed = 1; + unlock_app (a); + } + } + else + { + if (a->periodical_check_needed) + periodical_check_needed = 1; + unlock_app (a); + } + } + npth_mutex_unlock (&app_list_lock); + + return periodical_check_needed; +} + +/* This function must be called once to initialize this module. This + has to be done before a second thread is spawned. We can't do the + static initialization because Pth emulation code might not be able + to do a static init; in particular, it is not possible for W32. */ +gpg_error_t +initialize_module_command (void) +{ + gpg_error_t err; + + if (npth_mutex_init (&app_list_lock, NULL)) + { + err = gpg_error_from_syserror (); + log_error ("app: error initializing mutex: %s\n", gpg_strerror (err)); + return err; + } + + return apdu_init (); +} + +void +app_send_card_list (ctrl_t ctrl) +{ + app_t a; + char buf[65]; + + npth_mutex_lock (&app_list_lock); + for (a = app_top; a; a = a->next) + { + if (DIM (buf) < 2 * a->serialnolen + 1) + continue; + + bin2hex (a->serialno, a->serialnolen, buf); + send_status_direct (ctrl, "SERIALNO", buf); + } + npth_mutex_unlock (&app_list_lock); +} diff --git a/scd/atr.c b/scd/atr.c new file mode 100644 index 0000000..4f5a3b8 --- /dev/null +++ b/scd/atr.c @@ -0,0 +1,290 @@ +/* atr.c - ISO 7816 ATR functions + * Copyright (C) 2003, 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include <gpg-error.h> +#include "../common/logging.h" +#include "atr.h" + +static int const fi_table[16] = { 0, 372, 558, 744, 1116,1488, 1860, -1, + -1, 512, 768, 1024, 1536, 2048, -1, -1 }; +static int const di_table[16] = { -1, 1, 2, 4, 8, 16, -1, -1, + 0, -1, -2, -4, -8, -16, -32, -64}; + + +/* Dump the ATR in (BUFFER,BUFLEN) to a human readable format and + return that as a malloced buffer. The caller must release this + buffer using es_free! On error this function returns NULL and sets + ERRNO. */ +char * +atr_dump (const void *buffer, size_t buflen) +{ + const unsigned char *atr = buffer; + size_t atrlen = buflen; + estream_t fp; + int have_ta, have_tb, have_tc, have_td; + int n_historical; + int idx, val; + unsigned char chksum; + char *result; + + fp = es_fopenmem (0, "rwb,samethread"); + if (!fp) + return NULL; + + if (!atrlen) + { + es_fprintf (fp, "error: empty ATR\n"); + goto bailout; + } + + for (idx=0; idx < atrlen ; idx++) + es_fprintf (fp, "%s%02X", idx?" ":"", atr[idx]); + es_putc ('\n', fp); + + if (*atr == 0x3b) + es_fputs ("Direct convention\n", fp); + else if (*atr == 0x3f) + es_fputs ("Inverse convention\n", fp); + else + es_fprintf (fp,"error: invalid TS character 0x%02x\n", *atr); + if (!--atrlen) + goto bailout; + atr++; + + chksum = *atr; + for (idx=1; idx < atrlen-1; idx++) + chksum ^= atr[idx]; + + have_ta = !!(*atr & 0x10); + have_tb = !!(*atr & 0x20); + have_tc = !!(*atr & 0x40); + have_td = !!(*atr & 0x80); + n_historical = (*atr & 0x0f); + es_fprintf (fp, "%d historical characters indicated\n", n_historical); + + if (have_ta + have_tb + have_tc + have_td + n_historical > atrlen) + es_fputs ("error: ATR shorter than indicated by format character\n", fp); + if (!--atrlen) + goto bailout; + atr++; + + if (have_ta) + { + es_fputs ("TA1: F=", fp); + val = fi_table[(*atr >> 4) & 0x0f]; + if (!val) + es_fputs ("internal clock", fp); + else if (val == -1) + es_fputs ("RFU", fp); + else + es_fprintf (fp, "%d", val); + es_fputs (" D=", fp); + val = di_table[*atr & 0x0f]; + if (!val) + es_fputs ("[impossible value]\n", fp); + else if (val == -1) + es_fputs ("RFU\n", fp); + else if (val < 0 ) + es_fprintf (fp, "1/%d\n", val); + else + es_fprintf (fp, "%d\n", val); + + if (!--atrlen) + goto bailout; + atr++; + } + + if (have_tb) + { + es_fprintf (fp, "TB1: II=%d PI1=%d%s\n", + ((*atr >> 5) & 3), (*atr & 0x1f), + (*atr & 0x80)? " [high bit not cleared]":""); + if (!--atrlen) + goto bailout; + atr++; + } + + if (have_tc) + { + if (*atr == 255) + es_fputs ("TC1: guard time shortened to 1 etu\n", fp); + else + es_fprintf (fp, "TC1: (extra guard time) N=%d\n", *atr); + + if (!--atrlen) + goto bailout; + atr++; + } + + if (have_td) + { + have_ta = !!(*atr & 0x10); + have_tb = !!(*atr & 0x20); + have_tc = !!(*atr & 0x40); + have_td = !!(*atr & 0x80); + es_fprintf (fp, "TD1: protocol T%d supported\n", (*atr & 0x0f)); + + if (have_ta + have_tb + have_tc + have_td + n_historical > atrlen) + es_fputs ("error: ATR shorter than indicated by format character\n", + fp); + + if (!--atrlen) + goto bailout; + atr++; + } + else + have_ta = have_tb = have_tc = have_td = 0; + + if (have_ta) + { + es_fprintf (fp, "TA2: (PTS) %stoggle, %splicit, T=%02X\n", + (*atr & 0x80)? "no-":"", + (*atr & 0x10)? "im": "ex", + (*atr & 0x0f)); + if ((*atr & 0x60)) + es_fprintf (fp, "note: reserved bits are set (TA2=0x%02X)\n", *atr); + if (!--atrlen) + goto bailout; + atr++; + } + + if (have_tb) + { + es_fprintf (fp, "TB2: PI2=%d\n", *atr); + if (!--atrlen) + goto bailout; + atr++; + } + + if (have_tc) + { + es_fprintf (fp, "TC2: PWI=%d\n", *atr); + if (!--atrlen) + goto bailout; + atr++; + } + + if (have_td) + { + have_ta = !!(*atr & 0x10); + have_tb = !!(*atr & 0x20); + have_tc = !!(*atr & 0x40); + have_td = !!(*atr & 0x80); + es_fprintf (fp, "TD2: protocol T%d supported\n", *atr & 0x0f); + + if (have_ta + have_tb + have_tc + have_td + n_historical > atrlen) + es_fputs ("error: ATR shorter than indicated by format character\n", + fp); + + if (!--atrlen) + goto bailout; + atr++; + } + else + have_ta = have_tb = have_tc = have_td = 0; + + for (idx = 3; have_ta || have_tb || have_tc || have_td; idx++) + { + if (have_ta) + { + es_fprintf (fp, "TA%d: IFSC=%d\n", idx, *atr); + if (!--atrlen) + goto bailout; + atr++; + } + + if (have_tb) + { + es_fprintf (fp, "TB%d: BWI=%d CWI=%d\n", + idx, (*atr >> 4) & 0x0f, *atr & 0x0f); + if (!--atrlen) + goto bailout; + atr++; + } + + if (have_tc) + { + es_fprintf (fp, "TC%d: 0x%02X\n", idx, *atr); + if (!--atrlen) + goto bailout; + atr++; + } + + if (have_td) + { + have_ta = !!(*atr & 0x10); + have_tb = !!(*atr & 0x20); + have_tc = !!(*atr & 0x40); + have_td = !!(*atr & 0x80); + es_fprintf (fp, "TD%d: protocol T%d supported\n", idx, *atr & 0x0f); + + if (have_ta + have_tb + have_tc + have_td + n_historical > atrlen) + es_fputs ("error: " + "ATR shorter than indicated by format character\n", + fp); + + if (!--atrlen) + goto bailout; + atr++; + } + else + have_ta = have_tb = have_tc = have_td = 0; + } + + if (n_historical + 1 > atrlen) + es_fputs ("error: ATR shorter than required for historical bytes " + "and checksum\n", fp); + + if (n_historical) + { + es_fputs ("HCH:", fp); + for (; n_historical && atrlen ; n_historical--, atrlen--, atr++) + es_fprintf (fp, " %02X", *atr); + es_putc ('\n', fp); + } + + if (!atrlen) + es_fputs ("error: checksum missing\n", fp); + else if (*atr == chksum) + es_fprintf (fp, "TCK: %02X (good)\n", *atr); + else + es_fprintf (fp, "TCK: %02X (bad; computed %02X)\n", *atr, chksum); + + atrlen--; + if (atrlen) + es_fprintf (fp, "error: %u bytes garbage at end of ATR\n", + (unsigned int)atrlen ); + + bailout: + es_putc ('\0', fp); /* We want a string. */ + if (es_fclose_snatch (fp, (void**)&result, NULL)) + { + log_error ("oops: es_fclose_snatch failed: %s\n", strerror (errno)); + return NULL; + } + + return result; +} diff --git a/scd/atr.h b/scd/atr.h new file mode 100644 index 0000000..d39e243 --- /dev/null +++ b/scd/atr.h @@ -0,0 +1,27 @@ +/* atr.h - ISO 7816 ATR functions + * 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef ATR_H +#define ATR_H + +char *atr_dump (const void *buffer, size_t buflen); + + + +#endif /*ATR_H*/ diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c new file mode 100644 index 0000000..214165f --- /dev/null +++ b/scd/ccid-driver.c @@ -0,0 +1,4080 @@ +/* ccid-driver.c - USB ChipCardInterfaceDevices driver + * Copyright (C) 2003, 2004, 2005, 2006, 2007 + * 2008, 2009, 2013 Free Software Foundation, Inc. + * Written by Werner Koch. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * + * ALTERNATIVELY, this file may be distributed under the terms of the + * following license, in which case the provisions of this license are + * required INSTEAD OF the GNU General Public License. If you wish to + * allow use of your version of this file only under the terms of the + * GNU General Public License, and not to allow others to use your + * version of this file under the terms of the following license, + * indicate your decision by deleting this paragraph and the license + * below. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/* CCID (ChipCardInterfaceDevices) is a specification for accessing + smartcard via a reader connected to the USB. + + This is a limited driver allowing to use some CCID drivers directly + without any other specila drivers. This is a fallback driver to be + used when nothing else works or the system should be kept minimal + for security reasons. It makes use of the libusb library to gain + portable access to USB. + + This driver has been tested with the SCM SCR335 and SPR532 + smartcard readers and requires that a reader implements APDU or + TPDU level exchange and does fully automatic initialization. +*/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(HAVE_LIBUSB) || defined(TEST) + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <time.h> +#include <unistd.h> +#ifdef HAVE_NPTH +# include <npth.h> +#endif /*HAVE_NPTH*/ + +#include <libusb.h> + +#include "scdaemon.h" +#include "iso7816.h" +#define CCID_DRIVER_INCLUDE_USB_IDS 1 +#include "ccid-driver.h" + +#define DRVNAME "ccid-driver: " + +/* Max length of buffer with out CCID message header of 10-byte + Sending: 547 for RSA-4096 key import + APDU size = 540 (24+4+256+256) + command + lc + le = 4 + 3 + 0 + Sending: write data object of cardholder certificate + APDU size = 2048 + command + lc + le = 4 + 3 + 0 + Receiving: 2048 for cardholder certificate +*/ +#define CCID_MAX_BUF (2048+7+10) + +/* CCID command timeout. */ +#define CCID_CMD_TIMEOUT (5*1000) + +/* Number of supported devices. See MAX_READER in apdu.c. */ +#define CCID_MAX_DEVICE 4 + + +/* Depending on how this source is used we either define our error + output to go to stderr or to the GnuPG based logging functions. We + use the latter when GNUPG_MAJOR_VERSION or GNUPG_SCD_MAIN_HEADER + are defined. */ +#if defined(GNUPG_MAJOR_VERSION) || defined(GNUPG_SCD_MAIN_HEADER) + +#if defined(GNUPG_SCD_MAIN_HEADER) +# include GNUPG_SCD_MAIN_HEADER +#elif GNUPG_MAJOR_VERSION == 1 /* GnuPG Version is < 1.9. */ +# include "options.h" +# include "util.h" +# include "memory.h" +# include "cardglue.h" +# else /* This is the modularized GnuPG 1.9 or later. */ +# include "scdaemon.h" +#endif + + +# define DEBUGOUT(t) do { if (debug_level) \ + log_debug (DRVNAME t); } while (0) +# define DEBUGOUT_1(t,a) do { if (debug_level) \ + log_debug (DRVNAME t,(a)); } while (0) +# define DEBUGOUT_2(t,a,b) do { if (debug_level) \ + log_debug (DRVNAME t,(a),(b)); } while (0) +# define DEBUGOUT_3(t,a,b,c) do { if (debug_level) \ + log_debug (DRVNAME t,(a),(b),(c));} while (0) +# define DEBUGOUT_4(t,a,b,c,d) do { if (debug_level) \ + log_debug (DRVNAME t,(a),(b),(c),(d));} while (0) +# define DEBUGOUT_CONT(t) do { if (debug_level) \ + log_printf (t); } while (0) +# define DEBUGOUT_CONT_1(t,a) do { if (debug_level) \ + log_printf (t,(a)); } while (0) +# define DEBUGOUT_CONT_2(t,a,b) do { if (debug_level) \ + log_printf (t,(a),(b)); } while (0) +# define DEBUGOUT_CONT_3(t,a,b,c) do { if (debug_level) \ + log_printf (t,(a),(b),(c)); } while (0) +# define DEBUGOUT_LF() do { if (debug_level) \ + log_printf ("\n"); } while (0) + +#else /* Other usage of this source - don't use gnupg specifics. */ + +# define DEBUGOUT(t) do { if (debug_level) \ + fprintf (stderr, DRVNAME t); } while (0) +# define DEBUGOUT_1(t,a) do { if (debug_level) \ + fprintf (stderr, DRVNAME t, (a)); } while (0) +# define DEBUGOUT_2(t,a,b) do { if (debug_level) \ + fprintf (stderr, DRVNAME t, (a), (b)); } while (0) +# define DEBUGOUT_3(t,a,b,c) do { if (debug_level) \ + fprintf (stderr, DRVNAME t, (a), (b), (c)); } while (0) +# define DEBUGOUT_4(t,a,b,c,d) do { if (debug_level) \ + fprintf (stderr, DRVNAME t, (a), (b), (c), (d));} while(0) +# define DEBUGOUT_CONT(t) do { if (debug_level) \ + fprintf (stderr, t); } while (0) +# define DEBUGOUT_CONT_1(t,a) do { if (debug_level) \ + fprintf (stderr, t, (a)); } while (0) +# define DEBUGOUT_CONT_2(t,a,b) do { if (debug_level) \ + fprintf (stderr, t, (a), (b)); } while (0) +# define DEBUGOUT_CONT_3(t,a,b,c) do { if (debug_level) \ + fprintf (stderr, t, (a), (b), (c)); } while (0) +# define DEBUGOUT_LF() do { if (debug_level) \ + putc ('\n', stderr); } while (0) + +#endif /* This source not used by scdaemon. */ + + +#ifndef EAGAIN +#define EAGAIN EWOULDBLOCK +#endif + + + +enum { + RDR_to_PC_NotifySlotChange= 0x50, + RDR_to_PC_HardwareError = 0x51, + + PC_to_RDR_SetParameters = 0x61, + PC_to_RDR_IccPowerOn = 0x62, + PC_to_RDR_IccPowerOff = 0x63, + PC_to_RDR_GetSlotStatus = 0x65, + PC_to_RDR_Secure = 0x69, + PC_to_RDR_T0APDU = 0x6a, + PC_to_RDR_Escape = 0x6b, + PC_to_RDR_GetParameters = 0x6c, + PC_to_RDR_ResetParameters = 0x6d, + PC_to_RDR_IccClock = 0x6e, + PC_to_RDR_XfrBlock = 0x6f, + PC_to_RDR_Mechanical = 0x71, + PC_to_RDR_Abort = 0x72, + PC_to_RDR_SetDataRate = 0x73, + + RDR_to_PC_DataBlock = 0x80, + RDR_to_PC_SlotStatus = 0x81, + RDR_to_PC_Parameters = 0x82, + RDR_to_PC_Escape = 0x83, + RDR_to_PC_DataRate = 0x84 +}; + + +/* Two macro to detect whether a CCID command has failed and to get + the error code. These macros assume that we can access the + mandatory first 10 bytes of a CCID message in BUF. */ +#define CCID_COMMAND_FAILED(buf) ((buf)[7] & 0x40) +#define CCID_ERROR_CODE(buf) (((unsigned char *)(buf))[8]) + + +/* Store information on the driver's state. A pointer to such a + structure is used as handle for most functions. */ +struct ccid_driver_s +{ + libusb_device_handle *idev; + unsigned int bai; + unsigned short id_vendor; + unsigned short id_product; + int ifc_no; + int ep_bulk_out; + int ep_bulk_in; + int ep_intr; + int seqno; + unsigned char t1_ns; + unsigned char t1_nr; + unsigned char nonnull_nad; + int max_ifsd; + int max_ccid_msglen; + int ifsc; + unsigned char apdu_level:2; /* Reader supports short APDU level + exchange. With a value of 2 short + and extended level is supported.*/ + unsigned int auto_voltage:1; + unsigned int auto_param:1; + unsigned int auto_pps:1; + unsigned int auto_ifsd:1; + unsigned int has_pinpad:2; + unsigned int enodev_seen:1; + int powered_off; + + time_t last_progress; /* Last time we sent progress line. */ + + /* The progress callback and its first arg as supplied to + ccid_set_progress_cb. */ + void (*progress_cb)(void *, const char *, int, int, int); + void *progress_cb_arg; + + void (*prompt_cb)(void *, int); + void *prompt_cb_arg; + + unsigned char intr_buf[64]; + struct libusb_transfer *transfer; +}; + + +/* Object to keep infos about found ccid devices. */ +struct ccid_dev_table { + int n; /* Index to ccid_usb_dev_list */ + int interface_number; + int setting_number; + unsigned char *ifcdesc_extra; + int ep_bulk_out; + int ep_bulk_in; + int ep_intr; + size_t ifcdesc_extra_len; +}; + + +static int initialized_usb; /* Tracks whether USB has been initialized. */ +static int debug_level; /* Flag to control the debug output. + 0 = No debugging + 1 = USB I/O info + 2 = Level 1 + T=1 protocol tracing + 3 = Level 2 + USB/I/O tracing of SlotStatus. + */ +static int ccid_usb_thread_is_alive; + +static libusb_device **ccid_usb_dev_list; +static struct ccid_dev_table ccid_dev_table[CCID_MAX_DEVICE]; + + + +static unsigned int compute_edc (const unsigned char *data, size_t datalen, + int use_crc); +static int bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen, + int no_debug); +static int bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, + size_t *nread, int expected_type, int seqno, int timeout, + int no_debug); +static int abort_cmd (ccid_driver_t handle, int seqno, int init); +static int send_escape_cmd (ccid_driver_t handle, const unsigned char *data, + size_t datalen, unsigned char *result, + size_t resultmax, size_t *resultlen); + + +static int +map_libusb_error (int usberr) +{ + switch (usberr) + { + case 0: return 0; + case LIBUSB_ERROR_IO: return CCID_DRIVER_ERR_USB_IO; + case LIBUSB_ERROR_ACCESS: return CCID_DRIVER_ERR_USB_ACCESS; + case LIBUSB_ERROR_NO_DEVICE:return CCID_DRIVER_ERR_USB_NO_DEVICE; + case LIBUSB_ERROR_BUSY: return CCID_DRIVER_ERR_USB_BUSY; + case LIBUSB_ERROR_TIMEOUT: return CCID_DRIVER_ERR_USB_TIMEOUT; + case LIBUSB_ERROR_OVERFLOW: return CCID_DRIVER_ERR_USB_OVERFLOW; + } + return CCID_DRIVER_ERR_USB_OTHER; +} + + +/* Convert a little endian stored 4 byte value into an unsigned + integer. */ +static unsigned int +convert_le_u32 (const unsigned char *buf) +{ + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | ((unsigned int)buf[3] << 24); +} + + +/* Convert a little endian stored 2 byte value into an unsigned + integer. */ +static unsigned int +convert_le_u16 (const unsigned char *buf) +{ + return buf[0] | (buf[1] << 8); +} + +static void +set_msg_len (unsigned char *msg, unsigned int length) +{ + msg[1] = length; + msg[2] = length >> 8; + msg[3] = length >> 16; + msg[4] = length >> 24; +} + + +static void +print_progress (ccid_driver_t handle) +{ + time_t ct = time (NULL); + + /* We don't want to print progress lines too often. */ + if (ct == handle->last_progress) + return; + + if (handle->progress_cb) + handle->progress_cb (handle->progress_cb_arg, "card_busy", 'w', 0, 0); + + handle->last_progress = ct; +} + + + +/* Pint an error message for a failed CCID command including a textual + error code. MSG shall be the CCID message at a minimum of 10 bytes. */ +static void +print_command_failed (const unsigned char *msg) +{ + const char *t; + char buffer[100]; + int ec; + + if (!debug_level) + return; + + ec = CCID_ERROR_CODE (msg); + switch (ec) + { + case 0x00: t = "Command not supported"; break; + + case 0xE0: t = "Slot busy"; break; + case 0xEF: t = "PIN cancelled"; break; + case 0xF0: t = "PIN timeout"; break; + + case 0xF2: t = "Automatic sequence ongoing"; break; + case 0xF3: t = "Deactivated Protocol"; break; + case 0xF4: t = "Procedure byte conflict"; break; + case 0xF5: t = "ICC class not supported"; break; + case 0xF6: t = "ICC protocol not supported"; break; + case 0xF7: t = "Bad checksum in ATR"; break; + case 0xF8: t = "Bad TS in ATR"; break; + + case 0xFB: t = "An all inclusive hardware error occurred"; break; + case 0xFC: t = "Overrun error while talking to the ICC"; break; + case 0xFD: t = "Parity error while talking to the ICC"; break; + case 0xFE: t = "CCID timed out while talking to the ICC"; break; + case 0xFF: t = "Host aborted the current activity"; break; + + default: + if (ec > 0 && ec < 128) + sprintf (buffer, "Parameter error at offset %d", ec); + else + sprintf (buffer, "Error code %02X", ec); + t = buffer; + break; + } + DEBUGOUT_1 ("CCID command failed: %s\n", t); +} + + +static void +print_pr_data (const unsigned char *data, size_t datalen, size_t off) +{ + int any = 0; + + for (; off < datalen; off++) + { + if (!any || !(off % 16)) + { + if (any) + DEBUGOUT_LF (); + DEBUGOUT_1 (" [%04lu] ", (unsigned long) off); + } + DEBUGOUT_CONT_1 (" %02X", data[off]); + any = 1; + } + if (any && (off % 16)) + DEBUGOUT_LF (); +} + + +static void +print_p2r_header (const char *name, const unsigned char *msg, size_t msglen) +{ + DEBUGOUT_1 ("%s:\n", name); + if (msglen < 7) + return; + DEBUGOUT_1 (" dwLength ..........: %u\n", convert_le_u32 (msg+1)); + DEBUGOUT_1 (" bSlot .............: %u\n", msg[5]); + DEBUGOUT_1 (" bSeq ..............: %u\n", msg[6]); +} + + +static void +print_p2r_iccpoweron (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_IccPowerOn", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_2 (" bPowerSelect ......: 0x%02x (%s)\n", msg[7], + msg[7] == 0? "auto": + msg[7] == 1? "5.0 V": + msg[7] == 2? "3.0 V": + msg[7] == 3? "1.8 V":""); + print_pr_data (msg, msglen, 8); +} + + +static void +print_p2r_iccpoweroff (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_IccPowerOff", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_getslotstatus (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_GetSlotStatus", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_xfrblock (const unsigned char *msg, size_t msglen) +{ + unsigned int val; + + print_p2r_header ("PC_to_RDR_XfrBlock", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bBWI ..............: 0x%02x\n", msg[7]); + val = convert_le_u16 (msg+8); + DEBUGOUT_2 (" wLevelParameter ...: 0x%04x%s\n", val, + val == 1? " (continued)": + val == 2? " (continues+ends)": + val == 3? " (continues+continued)": + val == 16? " (DataBlock-expected)":""); + print_pr_data (msg, msglen, 10); +} + + +static void +print_p2r_getparameters (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_GetParameters", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_resetparameters (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_ResetParameters", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_setparameters (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_SetParameters", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bProtocolNum ......: 0x%02x\n", msg[7]); + print_pr_data (msg, msglen, 8); +} + + +static void +print_p2r_escape (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_Escape", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_iccclock (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_IccClock", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bClockCommand .....: 0x%02x\n", msg[7]); + print_pr_data (msg, msglen, 8); +} + + +static void +print_p2r_to0apdu (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_T0APDU", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bmChanges .........: 0x%02x\n", msg[7]); + DEBUGOUT_1 (" bClassGetResponse .: 0x%02x\n", msg[8]); + DEBUGOUT_1 (" bClassEnvelope ....: 0x%02x\n", msg[9]); + print_pr_data (msg, msglen, 10); +} + + +static void +print_p2r_secure (const unsigned char *msg, size_t msglen) +{ + unsigned int val; + + print_p2r_header ("PC_to_RDR_Secure", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bBMI ..............: 0x%02x\n", msg[7]); + val = convert_le_u16 (msg+8); + DEBUGOUT_2 (" wLevelParameter ...: 0x%04x%s\n", val, + val == 1? " (continued)": + val == 2? " (continues+ends)": + val == 3? " (continues+continued)": + val == 16? " (DataBlock-expected)":""); + print_pr_data (msg, msglen, 10); +} + + +static void +print_p2r_mechanical (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_Mechanical", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bFunction .........: 0x%02x\n", msg[7]); + print_pr_data (msg, msglen, 8); +} + + +static void +print_p2r_abort (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_Abort", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_setdatarate (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_SetDataRate", msg, msglen); + if (msglen < 10) + return; + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_unknown (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("Unknown PC_to_RDR command", msg, msglen); + if (msglen < 10) + return; + print_pr_data (msg, msglen, 0); +} + + +static void +print_r2p_header (const char *name, const unsigned char *msg, size_t msglen) +{ + DEBUGOUT_1 ("%s:\n", name); + if (msglen < 9) + return; + DEBUGOUT_1 (" dwLength ..........: %u\n", convert_le_u32 (msg+1)); + DEBUGOUT_1 (" bSlot .............: %u\n", msg[5]); + DEBUGOUT_1 (" bSeq ..............: %u\n", msg[6]); + DEBUGOUT_1 (" bStatus ...........: %u\n", msg[7]); + if (msg[8]) + DEBUGOUT_1 (" bError ............: %u\n", msg[8]); +} + + +static void +print_r2p_datablock (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_DataBlock", msg, msglen); + if (msglen < 10) + return; + if (msg[9]) + DEBUGOUT_2 (" bChainParameter ...: 0x%02x%s\n", msg[9], + msg[9] == 1? " (continued)": + msg[9] == 2? " (continues+ends)": + msg[9] == 3? " (continues+continued)": + msg[9] == 16? " (XferBlock-expected)":""); + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_slotstatus (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_SlotStatus", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_2 (" bClockStatus ......: 0x%02x%s\n", msg[9], + msg[9] == 0? " (running)": + msg[9] == 1? " (stopped-L)": + msg[9] == 2? " (stopped-H)": + msg[9] == 3? " (stopped)":""); + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_parameters (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_Parameters", msg, msglen); + if (msglen < 10) + return; + + DEBUGOUT_1 (" protocol ..........: T=%d\n", msg[9]); + if (msglen == 17 && msg[9] == 1) + { + /* Protocol T=1. */ + DEBUGOUT_1 (" bmFindexDindex ....: %02X\n", msg[10]); + DEBUGOUT_1 (" bmTCCKST1 .........: %02X\n", msg[11]); + DEBUGOUT_1 (" bGuardTimeT1 ......: %02X\n", msg[12]); + DEBUGOUT_1 (" bmWaitingIntegersT1: %02X\n", msg[13]); + DEBUGOUT_1 (" bClockStop ........: %02X\n", msg[14]); + DEBUGOUT_1 (" bIFSC .............: %d\n", msg[15]); + DEBUGOUT_1 (" bNadValue .........: %d\n", msg[16]); + } + else + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_escape (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_Escape", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" buffer[9] .........: %02X\n", msg[9]); + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_datarate (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_DataRate", msg, msglen); + if (msglen < 10) + return; + if (msglen >= 18) + { + DEBUGOUT_1 (" dwClockFrequency ..: %u\n", convert_le_u32 (msg+10)); + DEBUGOUT_1 (" dwDataRate ..... ..: %u\n", convert_le_u32 (msg+14)); + print_pr_data (msg, msglen, 18); + } + else + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_unknown (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("Unknown RDR_to_PC command", msg, msglen); + if (msglen < 10) + return; + DEBUGOUT_1 (" bMessageType ......: %02X\n", msg[0]); + DEBUGOUT_1 (" buffer[9] .........: %02X\n", msg[9]); + print_pr_data (msg, msglen, 10); +} + + +/* Parse a CCID descriptor, optionally print all available features + and test whether this reader is usable by this driver. Returns 0 + if it is usable. + + Note, that this code is based on the one in lsusb.c of the + usb-utils package, I wrote on 2003-09-01. -wk. */ +static int +parse_ccid_descriptor (ccid_driver_t handle, unsigned short bcd_device, + const unsigned char *buf, size_t buflen) +{ + unsigned int i; + unsigned int us; + int have_t1 = 0, have_tpdu=0; + + handle->nonnull_nad = 0; + handle->auto_ifsd = 0; + handle->max_ifsd = 32; + handle->has_pinpad = 0; + handle->apdu_level = 0; + handle->auto_voltage = 0; + handle->auto_param = 0; + handle->auto_pps = 0; + DEBUGOUT_3 ("idVendor: %04X idProduct: %04X bcdDevice: %04X\n", + handle->id_vendor, handle->id_product, bcd_device); + if (buflen < 54 || buf[0] < 54) + { + DEBUGOUT ("CCID device descriptor is too short\n"); + return -1; + } + + DEBUGOUT ("ChipCard Interface Descriptor:\n"); + DEBUGOUT_1 (" bLength %5u\n", buf[0]); + DEBUGOUT_1 (" bDescriptorType %5u\n", buf[1]); + DEBUGOUT_2 (" bcdCCID %2x.%02x", buf[3], buf[2]); + if (buf[3] != 1 || buf[2] != 0) + DEBUGOUT_CONT(" (Warning: Only accurate for version 1.0)"); + DEBUGOUT_LF (); + + DEBUGOUT_1 (" nMaxSlotIndex %5u\n", buf[4]); + DEBUGOUT_2 (" bVoltageSupport %5u %s\n", + buf[5], (buf[5] == 1? "5.0V" : buf[5] == 2? "3.0V" + : buf[5] == 3? "1.8V":"?")); + + us = convert_le_u32 (buf+6); + DEBUGOUT_1 (" dwProtocols %5u ", us); + if ((us & 1)) + DEBUGOUT_CONT (" T=0"); + if ((us & 2)) + { + DEBUGOUT_CONT (" T=1"); + have_t1 = 1; + } + if ((us & ~3)) + DEBUGOUT_CONT (" (Invalid values detected)"); + DEBUGOUT_LF (); + + us = convert_le_u32(buf+10); + DEBUGOUT_1 (" dwDefaultClock %5u\n", us); + us = convert_le_u32(buf+14); + DEBUGOUT_1 (" dwMaxiumumClock %5u\n", us); + DEBUGOUT_1 (" bNumClockSupported %5u\n", buf[18]); + us = convert_le_u32(buf+19); + DEBUGOUT_1 (" dwDataRate %7u bps\n", us); + us = convert_le_u32(buf+23); + DEBUGOUT_1 (" dwMaxDataRate %7u bps\n", us); + DEBUGOUT_1 (" bNumDataRatesSupp. %5u\n", buf[27]); + + us = convert_le_u32(buf+28); + DEBUGOUT_1 (" dwMaxIFSD %5u\n", us); + handle->max_ifsd = us; + + us = convert_le_u32(buf+32); + DEBUGOUT_1 (" dwSyncProtocols %08X ", us); + if ((us&1)) + DEBUGOUT_CONT ( " 2-wire"); + if ((us&2)) + DEBUGOUT_CONT ( " 3-wire"); + if ((us&4)) + DEBUGOUT_CONT ( " I2C"); + DEBUGOUT_LF (); + + us = convert_le_u32(buf+36); + DEBUGOUT_1 (" dwMechanical %08X ", us); + if ((us & 1)) + DEBUGOUT_CONT (" accept"); + if ((us & 2)) + DEBUGOUT_CONT (" eject"); + if ((us & 4)) + DEBUGOUT_CONT (" capture"); + if ((us & 8)) + DEBUGOUT_CONT (" lock"); + DEBUGOUT_LF (); + + us = convert_le_u32(buf+40); + DEBUGOUT_1 (" dwFeatures %08X\n", us); + if ((us & 0x0002)) + { + DEBUGOUT (" Auto configuration based on ATR (assumes auto voltage)\n"); + handle->auto_voltage = 1; + } + if ((us & 0x0004)) + DEBUGOUT (" Auto activation on insert\n"); + if ((us & 0x0008)) + { + DEBUGOUT (" Auto voltage selection\n"); + handle->auto_voltage = 1; + } + if ((us & 0x0010)) + DEBUGOUT (" Auto clock change\n"); + if ((us & 0x0020)) + DEBUGOUT (" Auto baud rate change\n"); + if ((us & 0x0040)) + { + DEBUGOUT (" Auto parameter negotiation made by CCID\n"); + handle->auto_param = 1; + } + else if ((us & 0x0080)) + { + DEBUGOUT (" Auto PPS made by CCID\n"); + handle->auto_pps = 1; + } + if ((us & (0x0040 | 0x0080)) == (0x0040 | 0x0080)) + DEBUGOUT (" WARNING: conflicting negotiation features\n"); + + if ((us & 0x0100)) + DEBUGOUT (" CCID can set ICC in clock stop mode\n"); + if ((us & 0x0200)) + { + DEBUGOUT (" NAD value other than 0x00 accepted\n"); + handle->nonnull_nad = 1; + } + if ((us & 0x0400)) + { + DEBUGOUT (" Auto IFSD exchange\n"); + handle->auto_ifsd = 1; + } + + if ((us & 0x00010000)) + { + DEBUGOUT (" TPDU level exchange\n"); + have_tpdu = 1; + } + else if ((us & 0x00020000)) + { + DEBUGOUT (" Short APDU level exchange\n"); + handle->apdu_level = 1; + } + else if ((us & 0x00040000)) + { + DEBUGOUT (" Short and extended APDU level exchange\n"); + handle->apdu_level = 2; + } + else if ((us & 0x00070000)) + DEBUGOUT (" WARNING: conflicting exchange levels\n"); + + us = convert_le_u32(buf+44); + DEBUGOUT_1 (" dwMaxCCIDMsgLen %5u\n", us); + handle->max_ccid_msglen = us; + + DEBUGOUT ( " bClassGetResponse "); + if (buf[48] == 0xff) + DEBUGOUT_CONT ("echo\n"); + else + DEBUGOUT_CONT_1 (" %02X\n", buf[48]); + + DEBUGOUT ( " bClassEnvelope "); + if (buf[49] == 0xff) + DEBUGOUT_CONT ("echo\n"); + else + DEBUGOUT_CONT_1 (" %02X\n", buf[48]); + + DEBUGOUT ( " wlcdLayout "); + if (!buf[50] && !buf[51]) + DEBUGOUT_CONT ("none\n"); + else + DEBUGOUT_CONT_2 ("%u cols %u lines\n", buf[50], buf[51]); + + DEBUGOUT_1 (" bPINSupport %5u ", buf[52]); + if ((buf[52] & 1)) + { + DEBUGOUT_CONT ( " verification"); + handle->has_pinpad |= 1; + } + if ((buf[52] & 2)) + { + DEBUGOUT_CONT ( " modification"); + handle->has_pinpad |= 2; + } + DEBUGOUT_LF (); + + DEBUGOUT_1 (" bMaxCCIDBusySlots %5u\n", buf[53]); + + if (buf[0] > 54) + { + DEBUGOUT (" junk "); + for (i=54; i < buf[0]-54; i++) + DEBUGOUT_CONT_1 (" %02X", buf[i]); + DEBUGOUT_LF (); + } + + if (!have_t1 || !(have_tpdu || handle->apdu_level)) + { + DEBUGOUT ("this drivers requires that the reader supports T=1, " + "TPDU or APDU level exchange - this is not available\n"); + return -1; + } + + + /* SCM drivers get stuck in their internal USB stack if they try to + send a frame of n*wMaxPacketSize back to us. Given that + wMaxPacketSize is 64 for these readers we set the IFSD to a value + lower than that: + 64 - 10 CCID header - 4 T1frame - 2 reserved = 48 + Product Ids: + 0xe001 - SCR 331 + 0x5111 - SCR 331-DI + 0x5115 - SCR 335 + 0xe003 - SPR 532 + The + 0x5117 - SCR 3320 USB ID-000 reader + seems to be very slow but enabling this workaround boosts the + performance to a more or less acceptable level (tested by David). + + */ + if (handle->id_vendor == VENDOR_SCM + && handle->max_ifsd > 48 + && ( (handle->id_product == SCM_SCR331 && bcd_device < 0x0516) + ||(handle->id_product == SCM_SCR331DI && bcd_device < 0x0620) + ||(handle->id_product == SCM_SCR335 && bcd_device < 0x0514) + ||(handle->id_product == SCM_SPR532 && bcd_device < 0x0504) + ||(handle->id_product == SCM_SCR3320 && bcd_device < 0x0522) + )) + { + DEBUGOUT ("enabling workaround for buggy SCM readers\n"); + handle->max_ifsd = 48; + } + + if (handle->id_vendor == VENDOR_GEMPC) + { + DEBUGOUT ("enabling product quirk: disable non-null NAD\n"); + handle->nonnull_nad = 0; + } + + return 0; +} + + +static char * +get_escaped_usb_string (libusb_device_handle *idev, int idx, + const char *prefix, const char *suffix) +{ + int rc; + unsigned char buf[280]; + unsigned char *s; + unsigned int langid; + size_t i, n, len; + char *result; + + if (!idx) + return NULL; + + /* Fixme: The next line is for the current Valgrid without support + for USB IOCTLs. */ + memset (buf, 0, sizeof buf); + + /* First get the list of supported languages and use the first one. + If we do don't find it we try to use English. Note that this is + all in a 2 bute Unicode encoding using little endian. */ +#ifdef USE_NPTH + npth_unprotect (); +#endif + rc = libusb_control_transfer (idev, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_DESCRIPTOR, + (LIBUSB_DT_STRING << 8), 0, + buf, sizeof buf, 1000 /* ms timeout */); +#ifdef USE_NPTH + npth_protect (); +#endif + if (rc < 4) + langid = 0x0409; /* English. */ + else + langid = (buf[3] << 8) | buf[2]; + +#ifdef USE_NPTH + npth_unprotect (); +#endif + rc = libusb_control_transfer (idev, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_DESCRIPTOR, + (LIBUSB_DT_STRING << 8) + idx, langid, + buf, sizeof buf, 1000 /* ms timeout */); +#ifdef USE_NPTH + npth_protect (); +#endif + if (rc < 2 || buf[1] != LIBUSB_DT_STRING) + return NULL; /* Error or not a string. */ + len = buf[0]; + if (len > rc) + return NULL; /* Larger than our buffer. */ + + for (s=buf+2, i=2, n=0; i+1 < len; i += 2, s += 2) + { + if (s[1]) + n++; /* High byte set. */ + else if (*s <= 0x20 || *s >= 0x7f || *s == '%' || *s == ':') + n += 3 ; + else + n++; + } + + result = malloc (strlen (prefix) + n + strlen (suffix) + 1); + if (!result) + return NULL; + + strcpy (result, prefix); + n = strlen (prefix); + for (s=buf+2, i=2; i+1 < len; i += 2, s += 2) + { + if (s[1]) + result[n++] = '\xff'; /* High byte set. */ + else if (*s <= 0x20 || *s >= 0x7f || *s == '%' || *s == ':') + { + sprintf (result+n, "%%%02X", *s); + n += 3; + } + else + result[n++] = *s; + } + strcpy (result+n, suffix); + + return result; +} + +/* This function creates an reader id to be used to find the same + physical reader after a reset. It returns an allocated and possibly + percent escaped string or NULL if not enough memory is available. */ +static char * +make_reader_id (libusb_device_handle *idev, + unsigned int vendor, unsigned int product, + unsigned char serialno_index) +{ + char *rid; + char prefix[20]; + + sprintf (prefix, "%04X:%04X:", (vendor & 0xffff), (product & 0xffff)); + rid = get_escaped_usb_string (idev, serialno_index, prefix, ":0"); + if (!rid) + { + rid = malloc (strlen (prefix) + 3 + 1); + if (!rid) + return NULL; + strcpy (rid, prefix); + strcat (rid, "X:0"); + } + return rid; +} + + +/* Helper to find the endpoint from an interface descriptor. */ +static int +find_endpoint (const struct libusb_interface_descriptor *ifcdesc, int mode) +{ + int no; + int want_bulk_in = 0; + + if (mode == 1) + want_bulk_in = 0x80; + for (no=0; no < ifcdesc->bNumEndpoints; no++) + { + const struct libusb_endpoint_descriptor *ep = ifcdesc->endpoint + no; + if (ep->bDescriptorType != LIBUSB_DT_ENDPOINT) + ; + else if (mode == 2 + && ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) + == LIBUSB_TRANSFER_TYPE_INTERRUPT) + && (ep->bEndpointAddress & 0x80)) + return ep->bEndpointAddress; + else if ((mode == 0 || mode == 1) + && ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) + == LIBUSB_TRANSFER_TYPE_BULK) + && (ep->bEndpointAddress & 0x80) == want_bulk_in) + return ep->bEndpointAddress; + } + + return -1; +} + + +/* Helper for scan_devices. This function returns true if a + requested device has been found or the caller should stop scanning + for other reasons. */ +static void +scan_usb_device (int *count, char **rid_list, struct libusb_device *dev) +{ + int ifc_no; + int set_no; + const struct libusb_interface_descriptor *ifcdesc; + char *rid; + libusb_device_handle *idev = NULL; + int err; + struct libusb_config_descriptor *config; + struct libusb_device_descriptor desc; + char *p; + + err = libusb_get_device_descriptor (dev, &desc); + if (err) + return; + + err = libusb_get_active_config_descriptor (dev, &config); + if (err) + return; + + for (ifc_no=0; ifc_no < config->bNumInterfaces; ifc_no++) + for (set_no=0; set_no < config->interface[ifc_no].num_altsetting; set_no++) + { + ifcdesc = (config->interface[ifc_no].altsetting + set_no); + /* The second condition is for older SCM SPR 532 who did + not know about the assigned CCID class. The third + condition does the same for a Cherry SmartTerminal + ST-2000. Instead of trying to interpret the strings + we simply check the product ID. */ + if (ifcdesc && ifcdesc->extra + && ((ifcdesc->bInterfaceClass == 11 + && ifcdesc->bInterfaceSubClass == 0 + && ifcdesc->bInterfaceProtocol == 0) + || (ifcdesc->bInterfaceClass == 255 + && desc.idVendor == VENDOR_SCM + && desc.idProduct == SCM_SPR532) + || (ifcdesc->bInterfaceClass == 255 + && desc.idVendor == VENDOR_CHERRY + && desc.idProduct == CHERRY_ST2000))) + { + ++*count; + + err = libusb_open (dev, &idev); + if (err) + { + DEBUGOUT_1 ("usb_open failed: %s\n", libusb_error_name (err)); + continue; /* with next setting. */ + } + + rid = make_reader_id (idev, desc.idVendor, desc.idProduct, + desc.iSerialNumber); + if (!rid) + { + libusb_free_config_descriptor (config); + return; + } + + /* We are collecting infos about all available CCID + readers. Store them and continue. */ + DEBUGOUT_2 ("found CCID reader %d (ID=%s)\n", *count, rid); + p = malloc ((*rid_list? strlen (*rid_list):0) + 1 + + strlen (rid) + 1); + if (p) + { + *p = 0; + if (*rid_list) + { + strcat (p, *rid_list); + free (*rid_list); + } + strcat (p, rid); + strcat (p, "\n"); + *rid_list = p; + } + else /* Out of memory. */ + { + libusb_free_config_descriptor (config); + free (rid); + return; + } + + free (rid); + libusb_close (idev); + idev = NULL; + } + } + + libusb_free_config_descriptor (config); +} + +/* Scan all CCID devices. + + The function returns 0 if a reader has been found or when a scan + returned without error. + + R_RID should be the address where to store the list of reader_ids + we found. If on return this list is empty, no CCID device has been + found; otherwise it points to an allocated linked list of reader + IDs. +*/ +static int +scan_devices (char **r_rid) +{ + char *rid_list = NULL; + int count = 0; + libusb_device **dev_list = NULL; + libusb_device *dev; + int i; + ssize_t n; + + /* Set return values to a default. */ + if (r_rid) + *r_rid = NULL; + + n = libusb_get_device_list (NULL, &dev_list); + + for (i = 0; i < n; i++) + { + dev = dev_list[i]; + scan_usb_device (&count, &rid_list, dev); + } + + libusb_free_device_list (dev_list, 1); + + *r_rid = rid_list; + return 0; +} + + +/* Set the level of debugging to LEVEL and return the old level. -1 + just returns the old level. A level of 0 disables debugging, 1 + enables debugging, 2 enables additional tracing of the T=1 + protocol, 3 additionally enables debugging for GetSlotStatus, other + values are not yet defined. + + Note that libusb may provide its own debugging feature which is + enabled by setting the envvar USB_DEBUG. */ +int +ccid_set_debug_level (int level) +{ + int old = debug_level; + if (level != -1) + debug_level = level; + return old; +} + + +char * +ccid_get_reader_list (void) +{ + char *reader_list; + + if (!initialized_usb) + { + int rc; + if ((rc = libusb_init (NULL))) + { + DEBUGOUT_1 ("usb_init failed: %s.\n", libusb_error_name (rc)); + return NULL; + } + initialized_usb = 1; + } + + if (scan_devices (&reader_list)) + return NULL; /* Error. */ + return reader_list; +} + + +/* Vendor specific custom initialization. */ +static int +ccid_vendor_specific_init (ccid_driver_t handle) +{ + int r = 0; + + if (handle->id_vendor == VENDOR_VEGA && handle->id_product == VEGA_ALPHA) + { + /* + * Vega alpha has a feature to show retry counter on the pinpad + * display. But it assumes that the card returns the value of + * retry counter by VERIFY with empty data (return code of + * 63Cx). Unfortunately, existing OpenPGP cards don't support + * VERIFY command with empty data. This vendor specific command + * sequence is to disable the feature. + */ + const unsigned char cmd[] = { '\xb5', '\x01', '\x00', '\x03', '\x00' }; + + r = send_escape_cmd (handle, cmd, sizeof (cmd), NULL, 0, NULL); + } + else if (handle->id_vendor == VENDOR_SCM && handle->id_product == SCM_SPR532) + { + /* + * It seems that SEQ may be out of sync between host and the card reader, + * and SET_INTERFACE doesn't reset it. Make sure it works at the init. + */ + abort_cmd (handle, 0, 1); + } + + if (r != 0 && r != CCID_DRIVER_ERR_CARD_INACTIVE + && r != CCID_DRIVER_ERR_NO_CARD) + return r; + else + return 0; +} + + +static int +ccid_vendor_specific_setup (ccid_driver_t handle) +{ + if (handle->id_vendor == VENDOR_SCM && handle->id_product == SCM_SPR532) + { +#ifdef USE_NPTH + npth_unprotect (); +#endif + libusb_clear_halt (handle->idev, handle->ep_intr); +#ifdef USE_NPTH + npth_protect (); +#endif + } + return 0; +} + + +static int +ccid_vendor_specific_pinpad_setup (ccid_driver_t handle) +{ + if (handle->id_vendor == VENDOR_SCM && handle->id_product == SCM_SPR532) + { + DEBUGOUT ("sending escape sequence to switch to a case 1 APDU\n"); + send_escape_cmd (handle, (const unsigned char*)"\x80\x02\x00", 3, + NULL, 0, NULL); + } + return 0; +} + + +gpg_error_t +ccid_dev_scan (int *idx_max_p, void **t_p) +{ + ssize_t n; + libusb_device *dev; + int i; + int ifc_no; + int set_no; + int idx = 0; + int err = 0; + + *idx_max_p = 0; + *t_p = NULL; + + if (!initialized_usb) + { + int rc; + if ((rc = libusb_init (NULL))) + { + DEBUGOUT_1 ("usb_init failed: %s.\n", libusb_error_name (rc)); + return gpg_error (GPG_ERR_ENODEV); + } + initialized_usb = 1; + } + + n = libusb_get_device_list (NULL, &ccid_usb_dev_list); + for (i = 0; i < n; i++) + { + struct libusb_config_descriptor *config; + struct libusb_device_descriptor desc; + + dev = ccid_usb_dev_list[i]; + + if (libusb_get_device_descriptor (dev, &desc)) + continue; + + if (libusb_get_active_config_descriptor (dev, &config)) + continue; + + for (ifc_no=0; ifc_no < config->bNumInterfaces; ifc_no++) + for (set_no=0; set_no < config->interface[ifc_no].num_altsetting; + set_no++) + { + const struct libusb_interface_descriptor *ifcdesc; + + ifcdesc = &config->interface[ifc_no].altsetting[set_no]; + /* The second condition is for older SCM SPR 532 who did + not know about the assigned CCID class. The third + condition does the same for a Cherry SmartTerminal + ST-2000. Instead of trying to interpret the strings + we simply check the product ID. */ + if (ifcdesc && ifcdesc->extra + && ((ifcdesc->bInterfaceClass == 11 + && ifcdesc->bInterfaceSubClass == 0 + && ifcdesc->bInterfaceProtocol == 0) + || (ifcdesc->bInterfaceClass == 255 + && desc.idVendor == VENDOR_SCM + && desc.idProduct == SCM_SPR532) + || (ifcdesc->bInterfaceClass == 255 + && desc.idVendor == VENDOR_CHERRY + && desc.idProduct == CHERRY_ST2000))) + { + /* Found a reader. */ + unsigned char *ifcdesc_extra; + + ifcdesc_extra = malloc (ifcdesc->extra_length); + if (!ifcdesc_extra) + { + err = gpg_error_from_syserror (); + libusb_free_config_descriptor (config); + goto scan_finish; + } + memcpy (ifcdesc_extra, ifcdesc->extra, ifcdesc->extra_length); + + ccid_dev_table[idx].n = i; + ccid_dev_table[idx].interface_number = ifc_no; + ccid_dev_table[idx].setting_number = set_no; + ccid_dev_table[idx].ifcdesc_extra = ifcdesc_extra; + ccid_dev_table[idx].ifcdesc_extra_len = ifcdesc->extra_length; + ccid_dev_table[idx].ep_bulk_out = find_endpoint (ifcdesc, 0); + ccid_dev_table[idx].ep_bulk_in = find_endpoint (ifcdesc, 1); + ccid_dev_table[idx].ep_intr = find_endpoint (ifcdesc, 2); + + idx++; + if (idx >= CCID_MAX_DEVICE) + { + libusb_free_config_descriptor (config); + err = 0; + goto scan_finish; + } + } + } + + libusb_free_config_descriptor (config); + } + + scan_finish: + + if (err) + { + for (i = 0; i < idx; i++) + { + free (ccid_dev_table[i].ifcdesc_extra); + ccid_dev_table[i].n = 0; + ccid_dev_table[i].interface_number = 0; + ccid_dev_table[i].setting_number = 0; + ccid_dev_table[i].ifcdesc_extra = NULL; + ccid_dev_table[i].ifcdesc_extra_len = 0; + ccid_dev_table[i].ep_bulk_out = 0; + ccid_dev_table[i].ep_bulk_in = 0; + ccid_dev_table[i].ep_intr = 0; + } + libusb_free_device_list (ccid_usb_dev_list, 1); + ccid_usb_dev_list = NULL; + } + else + { + *idx_max_p = idx; + if (idx) + *t_p = ccid_dev_table; + else + *t_p = NULL; + } + + return err; +} + +void +ccid_dev_scan_finish (void *tbl0, int max) +{ + int i; + struct ccid_dev_table *tbl = tbl0; + + for (i = 0; i < max; i++) + { + free (tbl[i].ifcdesc_extra); + tbl[i].n = 0; + tbl[i].interface_number = 0; + tbl[i].setting_number = 0; + tbl[i].ifcdesc_extra = NULL; + tbl[i].ifcdesc_extra_len = 0; + tbl[i].ep_bulk_out = 0; + tbl[i].ep_bulk_in = 0; + tbl[i].ep_intr = 0; + } + libusb_free_device_list (ccid_usb_dev_list, 1); + ccid_usb_dev_list = NULL; +} + +unsigned int +ccid_get_BAI (int idx, void *tbl0) +{ + int n; + int bus, addr, intf; + unsigned int bai; + libusb_device *dev; + struct ccid_dev_table *tbl = tbl0; + + n = tbl[idx].n; + dev = ccid_usb_dev_list[n]; + + bus = libusb_get_bus_number (dev); + addr = libusb_get_device_address (dev); + intf = tbl[idx].interface_number; + bai = (bus << 16) | (addr << 8) | intf; + + return bai; +} + +int +ccid_compare_BAI (ccid_driver_t handle, unsigned int bai) +{ + return handle->bai == bai; +} + + +static void +intr_cb (struct libusb_transfer *transfer) +{ + ccid_driver_t handle = transfer->user_data; + + DEBUGOUT_2 ("CCID: interrupt callback %d (%d)\n", + transfer->status, transfer->actual_length); + + if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT) + { + int err; + + submit_again: + /* Submit the URB again to keep watching the INTERRUPT transfer. */ + err = libusb_submit_transfer (transfer); + if (err == LIBUSB_ERROR_NO_DEVICE) + goto device_removed; + + DEBUGOUT_1 ("CCID submit transfer again %d\n", err); + } + else if (transfer->status == LIBUSB_TRANSFER_COMPLETED) + { + size_t len = transfer->actual_length; + unsigned char *p = transfer->buffer; + int card_removed = 0; + + while (len) + { + if (*p == RDR_to_PC_NotifySlotChange) + { + if (len < 2) + break; + + DEBUGOUT_1 ("CCID: NotifySlotChange: %02x\n", p[1]); + + if ((p[1] & 1)) + card_removed = 0; + else + card_removed = 1; + + p += 2; + len -= 2; + } + else if (*p == RDR_to_PC_HardwareError) + { + if (len < 4) + break; + + DEBUGOUT_1 ("CCID: hardware error detected: %02x\n", p[3]); + p += 4; + len -= 4; + } + else + { + DEBUGOUT_1 ("CCID: unknown intr: %02x\n", p[0]); + break; + } + } + + if (card_removed) + { + DEBUGOUT ("CCID: card removed\n"); + handle->powered_off = 1; +#if defined(GNUPG_MAJOR_VERSION) + scd_kick_the_loop (); +#endif + } + else + { + /* Event other than card removal. */ + goto submit_again; + } + } + else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) + handle->powered_off = 1; + else if (transfer->status == LIBUSB_TRANSFER_OVERFLOW) + { + /* Something goes wrong. Ignore. */ + DEBUGOUT ("CCID: interrupt transfer overflow\n"); + } + else + { + device_removed: + DEBUGOUT ("CCID: device removed\n"); + handle->powered_off = 1; +#if defined(GNUPG_MAJOR_VERSION) + scd_kick_the_loop (); +#endif + } +} + +static void +ccid_setup_intr (ccid_driver_t handle) +{ + struct libusb_transfer *transfer; + int err; + + transfer = libusb_alloc_transfer (0); + handle->transfer = transfer; + libusb_fill_interrupt_transfer (transfer, handle->idev, handle->ep_intr, + handle->intr_buf, sizeof (handle->intr_buf), + intr_cb, handle, 0); + err = libusb_submit_transfer (transfer); + DEBUGOUT_2 ("CCID submit transfer (%x): %d", handle->ep_intr, err); +} + + +static void * +ccid_usb_thread (void *arg) +{ + libusb_context *ctx = arg; + + while (ccid_usb_thread_is_alive) + { +#ifdef USE_NPTH + npth_unprotect (); +#endif + libusb_handle_events_completed (ctx, NULL); +#ifdef USE_NPTH + npth_protect (); +#endif + } + + return NULL; +} + + +static int +ccid_open_usb_reader (const char *spec_reader_name, + int idx, void *ccid_table0, + ccid_driver_t *handle, char **rdrname_p) +{ + libusb_device *dev; + libusb_device_handle *idev = NULL; + char *rid = NULL; + int rc = 0; + int ifc_no, set_no; + struct libusb_device_descriptor desc; + int n; + int bus, addr; + unsigned int bai; + struct ccid_dev_table *ccid_table = ccid_table0; + + n = ccid_table[idx].n; + ifc_no = ccid_table[idx].interface_number; + set_no = ccid_table[idx].setting_number; + + dev = ccid_usb_dev_list[n]; + bus = libusb_get_bus_number (dev); + addr = libusb_get_device_address (dev); + bai = (bus << 16) | (addr << 8) | ifc_no; + + rc = libusb_open (dev, &idev); + if (rc) + { + DEBUGOUT_1 ("usb_open failed: %s\n", libusb_error_name (rc)); + free (*handle); + *handle = NULL; + return map_libusb_error (rc); + } + + if (ccid_usb_thread_is_alive++ == 0) + { + npth_t thread; + npth_attr_t tattr; + int err; + + err = npth_attr_init (&tattr); + if (err) + { + DEBUGOUT_1 ("npth_attr_init failed: %s\n", strerror (err)); + free (*handle); + *handle = NULL; + return err; + } + + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + err = npth_create (&thread, &tattr, ccid_usb_thread, NULL); + if (err) + { + DEBUGOUT_1 ("npth_create failed: %s\n", strerror (err)); + free (*handle); + *handle = NULL; + return err; + } + npth_setname_np (thread, "ccid_usb_thread"); + + npth_attr_destroy (&tattr); + } + + rc = libusb_get_device_descriptor (dev, &desc); + if (rc) + { + DEBUGOUT ("get_device_descripor failed\n"); + rc = map_libusb_error (rc); + goto leave; + } + + rid = make_reader_id (idev, desc.idVendor, desc.idProduct, + desc.iSerialNumber); + + /* Check to see if reader name matches the spec. */ + if (spec_reader_name + && strncmp (rid, spec_reader_name, strlen (spec_reader_name))) + { + DEBUGOUT ("device not matched\n"); + rc = CCID_DRIVER_ERR_NO_READER; + goto leave; + } + + (*handle)->id_vendor = desc.idVendor; + (*handle)->id_product = desc.idProduct; + (*handle)->idev = idev; + (*handle)->bai = bai; + (*handle)->ifc_no = ifc_no; + (*handle)->ep_bulk_out = ccid_table[idx].ep_bulk_out; + (*handle)->ep_bulk_in = ccid_table[idx].ep_bulk_in; + (*handle)->ep_intr = ccid_table[idx].ep_intr; + + DEBUGOUT_2 ("using CCID reader %d (ID=%s)\n", idx, rid); + + if (parse_ccid_descriptor (*handle, desc.bcdDevice, + ccid_table[idx].ifcdesc_extra, + ccid_table[idx].ifcdesc_extra_len)) + { + DEBUGOUT ("device not supported\n"); + rc = CCID_DRIVER_ERR_NO_READER; + goto leave; + } + +#ifdef USE_NPTH + npth_unprotect (); +#endif + rc = libusb_claim_interface (idev, ifc_no); + if (rc) + { +#ifdef USE_NPTH + npth_protect (); +#endif + DEBUGOUT_1 ("usb_claim_interface failed: %d\n", rc); + rc = map_libusb_error (rc); + goto leave; + } + + /* Submit SET_INTERFACE control transfer which can reset the device. */ + rc = libusb_set_interface_alt_setting (idev, ifc_no, set_no); + if (rc) + { +#ifdef USE_NPTH + npth_protect (); +#endif + DEBUGOUT_1 ("usb_set_interface_alt_setting failed: %d\n", rc); + rc = map_libusb_error (rc); + goto leave; + } + +#ifdef USE_NPTH + npth_protect (); +#endif + + rc = ccid_vendor_specific_init (*handle); + + leave: + if (rc) + { + --ccid_usb_thread_is_alive; + free (rid); + libusb_release_interface (idev, ifc_no); + libusb_close (idev); + free (*handle); + *handle = NULL; + } + else + { + if (rdrname_p) + *rdrname_p = rid; + else + free (rid); + } + + return rc; +} + +/* Open the reader with the internal number READERNO and return a + pointer to be used as handle in HANDLE. Returns 0 on success. */ +int +ccid_open_reader (const char *spec_reader_name, int idx, + void *ccid_table0, + ccid_driver_t *handle, char **rdrname_p) +{ + struct ccid_dev_table *ccid_table = ccid_table0; + + *handle = calloc (1, sizeof **handle); + if (!*handle) + { + DEBUGOUT ("out of memory\n"); + return CCID_DRIVER_ERR_OUT_OF_CORE; + } + + return ccid_open_usb_reader (spec_reader_name, idx, ccid_table, + handle, rdrname_p); +} + + +int +ccid_require_get_status (ccid_driver_t handle) +{ + /* When a card reader supports interrupt transfer to check the + status of card, it is possible to submit only an interrupt + transfer, and no check is required by application layer. USB can + detect removal of a card and can detect removal of a reader. + */ + if (handle->ep_intr >= 0) + { + if (handle->id_vendor != VENDOR_SCM) + return 0; + + /* + * For card reader with interrupt transfer support, ideally, + * removal is detected by intr_cb, but some card reader + * (e.g. SPR532) has a possible case of missing report to + * intr_cb, and another case of valid report to intr_cb. + * + * For such a reader, the removal should be able to be detected + * by PC_to_RDR_GetSlotStatus, too. Thus, calls to + * ccid_slot_status should go on wire even if "on_wire" is not + * requested. + * + */ + if (handle->transfer == NULL) + return 0; + } + + /* Libusb actually detects the removal of USB device in use. + However, there is no good API to handle the removal (yet), + cleanly and with good portability. + + There is libusb_set_pollfd_notifiers function, but it doesn't + offer libusb_device_handle* data to its callback. So, when it + watches multiple devices, there is no way to know which device is + removed. + + Once, we will have a good programming interface of libusb, we can + list tokens (with no interrupt transfer support, but always with + card inserted) here to return 0, so that scdaemon can submit + minimum packet on wire. + */ + return 1; +} + +static int +send_power_off (ccid_driver_t handle) +{ + int rc; + unsigned char msg[100]; + size_t msglen; + unsigned char seqno; + + msg[0] = PC_to_RDR_IccPowerOff; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 0; /* RFU */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + set_msg_len (msg, 0); + msglen = 10; + + rc = bulk_out (handle, msg, msglen, 0); + if (!rc) + bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus, + seqno, 2000, 0); + return rc; +} + +static void +do_close_reader (ccid_driver_t handle) +{ + int rc; + + if (!handle->powered_off) + send_power_off (handle); + + if (handle->transfer) + { + if (!handle->powered_off) + { + DEBUGOUT ("libusb_cancel_transfer\n"); + + rc = libusb_cancel_transfer (handle->transfer); + if (rc != LIBUSB_ERROR_NOT_FOUND) + while (!handle->powered_off) + { + DEBUGOUT ("libusb_handle_events_completed\n"); +#ifdef USE_NPTH + npth_unprotect (); +#endif + libusb_handle_events_completed (NULL, &handle->powered_off); +#ifdef USE_NPTH + npth_protect (); +#endif + } + } + + libusb_free_transfer (handle->transfer); + handle->transfer = NULL; + } + + DEBUGOUT ("libusb_release_interface and libusb_close\n"); + libusb_release_interface (handle->idev, handle->ifc_no); + --ccid_usb_thread_is_alive; + libusb_close (handle->idev); + handle->idev = NULL; +} + + +int +ccid_set_progress_cb (ccid_driver_t handle, + void (*cb)(void *, const char *, int, int, int), + void *cb_arg) +{ + if (!handle) + return CCID_DRIVER_ERR_INV_VALUE; + + handle->progress_cb = cb; + handle->progress_cb_arg = cb_arg; + return 0; +} + + +int +ccid_set_prompt_cb (ccid_driver_t handle, + void (*cb)(void *, int), void *cb_arg) +{ + if (!handle) + return CCID_DRIVER_ERR_INV_VALUE; + + handle->prompt_cb = cb; + handle->prompt_cb_arg = cb_arg; + return 0; +} + + +/* Close the reader HANDLE. */ +int +ccid_close_reader (ccid_driver_t handle) +{ + if (!handle) + return 0; + + do_close_reader (handle); + free (handle); + return 0; +} + + +/* Return False if a card is present and powered. */ +int +ccid_check_card_presence (ccid_driver_t handle) +{ + (void)handle; /* Not yet implemented. */ + return -1; +} + + +/* Write a MSG of length MSGLEN to the designated bulk out endpoint. + Returns 0 on success. */ +static int +bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen, + int no_debug) +{ + int rc; + int transferred; + + /* No need to continue and clutter the log with USB write error + messages after we got the first ENODEV. */ + if (handle->enodev_seen) + return CCID_DRIVER_ERR_NO_READER; + + if (debug_level && (!no_debug || debug_level >= 3)) + { + switch (msglen? msg[0]:0) + { + case PC_to_RDR_IccPowerOn: + print_p2r_iccpoweron (msg, msglen); + break; + case PC_to_RDR_IccPowerOff: + print_p2r_iccpoweroff (msg, msglen); + break; + case PC_to_RDR_GetSlotStatus: + print_p2r_getslotstatus (msg, msglen); + break; + case PC_to_RDR_XfrBlock: + print_p2r_xfrblock (msg, msglen); + break; + case PC_to_RDR_GetParameters: + print_p2r_getparameters (msg, msglen); + break; + case PC_to_RDR_ResetParameters: + print_p2r_resetparameters (msg, msglen); + break; + case PC_to_RDR_SetParameters: + print_p2r_setparameters (msg, msglen); + break; + case PC_to_RDR_Escape: + print_p2r_escape (msg, msglen); + break; + case PC_to_RDR_IccClock: + print_p2r_iccclock (msg, msglen); + break; + case PC_to_RDR_T0APDU: + print_p2r_to0apdu (msg, msglen); + break; + case PC_to_RDR_Secure: + print_p2r_secure (msg, msglen); + break; + case PC_to_RDR_Mechanical: + print_p2r_mechanical (msg, msglen); + break; + case PC_to_RDR_Abort: + print_p2r_abort (msg, msglen); + break; + case PC_to_RDR_SetDataRate: + print_p2r_setdatarate (msg, msglen); + break; + default: + print_p2r_unknown (msg, msglen); + break; + } + } + +#ifdef USE_NPTH + npth_unprotect (); +#endif + rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_out, + msg, msglen, &transferred, + 5000 /* ms timeout */); +#ifdef USE_NPTH + npth_protect (); +#endif + if (rc == 0 && transferred == msglen) + return 0; + + if (rc) + { + DEBUGOUT_1 ("usb_bulk_write error: %s\n", libusb_error_name (rc)); + if (rc == LIBUSB_ERROR_NO_DEVICE) + { + handle->enodev_seen = 1; + return CCID_DRIVER_ERR_NO_READER; + } + } + + return 0; +} + + +/* Read a maximum of LENGTH bytes from the bulk in endpoint into + BUFFER and return the actual read number if bytes in NREAD. SEQNO + is the sequence number used to send the request and EXPECTED_TYPE + the type of message we expect. Does checks on the ccid + header. TIMEOUT is the timeout value in ms. NO_DEBUG may be set to + avoid debug messages in case of no error; this can be overriden + with a glibal debug level of at least 3. Returns 0 on success. */ +static int +bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, + size_t *nread, int expected_type, int seqno, int timeout, + int no_debug) +{ + int rc; + int msglen; + int notified = 0; + int bwi = 1; + + /* Fixme: The next line for the current Valgrind without support + for USB IOCTLs. */ + memset (buffer, 0, length); + retry: + +#ifdef USE_NPTH + npth_unprotect (); +#endif + rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_in, + buffer, length, &msglen, bwi*timeout); +#ifdef USE_NPTH + npth_protect (); +#endif + if (rc) + { + DEBUGOUT_1 ("usb_bulk_read error: %s\n", libusb_error_name (rc)); + if (rc == LIBUSB_ERROR_NO_DEVICE) + handle->enodev_seen = 1; + + return map_libusb_error (rc); + } + if (msglen < 0) + return CCID_DRIVER_ERR_INV_VALUE; /* Faulty libusb. */ + *nread = msglen; + + if (msglen < 10) + { + DEBUGOUT_1 ("bulk-in msg too short (%u)\n", (unsigned int)msglen); + abort_cmd (handle, seqno, 0); + return CCID_DRIVER_ERR_INV_VALUE; + } + if (buffer[5] != 0) + { + DEBUGOUT_1 ("unexpected bulk-in slot (%d)\n", buffer[5]); + return CCID_DRIVER_ERR_INV_VALUE; + } + if (buffer[6] != seqno) + { + DEBUGOUT_2 ("bulk-in seqno does not match (%d/%d)\n", + seqno, buffer[6]); + /* Retry until we are synced again. */ + goto retry; + } + + /* We need to handle the time extension request before we check that + we got the expected message type. This is in particular required + for the Cherry keyboard which sends a time extension request for + each key hit. */ + if (!(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80) + { + /* Card present and active, time extension requested. */ + DEBUGOUT_2 ("time extension requested (%02X,%02X)\n", + buffer[7], buffer[8]); + + bwi = 1; + if (buffer[8] != 0 && buffer[8] != 0xff) + bwi = buffer[8]; + + /* Gnuk enhancement to prompt user input by ack button */ + if (buffer[8] == 0xff && !notified) + { + notified = 1; + handle->prompt_cb (handle->prompt_cb_arg, 1); + } + + goto retry; + } + + if (notified) + handle->prompt_cb (handle->prompt_cb_arg, 0); + + if (buffer[0] != expected_type && buffer[0] != RDR_to_PC_SlotStatus) + { + DEBUGOUT_1 ("unexpected bulk-in msg type (%02x)\n", buffer[0]); + abort_cmd (handle, seqno, 0); + return CCID_DRIVER_ERR_INV_VALUE; + } + + if (debug_level && (!no_debug || debug_level >= 3)) + { + switch (buffer[0]) + { + case RDR_to_PC_DataBlock: + print_r2p_datablock (buffer, msglen); + break; + case RDR_to_PC_SlotStatus: + print_r2p_slotstatus (buffer, msglen); + break; + case RDR_to_PC_Parameters: + print_r2p_parameters (buffer, msglen); + break; + case RDR_to_PC_Escape: + print_r2p_escape (buffer, msglen); + break; + case RDR_to_PC_DataRate: + print_r2p_datarate (buffer, msglen); + break; + default: + print_r2p_unknown (buffer, msglen); + break; + } + } + if (CCID_COMMAND_FAILED (buffer)) + print_command_failed (buffer); + + /* Check whether a card is at all available. Note: If you add new + error codes here, check whether they need to be ignored in + send_escape_cmd. */ + switch ((buffer[7] & 0x03)) + { + case 0: /* no error */ break; + case 1: rc = CCID_DRIVER_ERR_CARD_INACTIVE; break; + case 2: rc = CCID_DRIVER_ERR_NO_CARD; break; + case 3: /* RFU */ break; + } + + if (rc) + { + /* + * Communication failure by device side. + * Possibly, it was forcibly suspended and resumed. + */ + if (handle->ep_intr < 0) + { + DEBUGOUT ("CCID: card inactive/removed\n"); + handle->powered_off = 1; + } + +#if defined(GNUPG_MAJOR_VERSION) + scd_kick_the_loop (); +#endif + } + + return rc; +} + + + +/* Send an abort sequence and wait until everything settled. */ +static int +abort_cmd (ccid_driver_t handle, int seqno, int init) +{ + int rc; + unsigned char dummybuf[8]; + unsigned char msg[100]; + int msglen; + + seqno &= 0xff; + DEBUGOUT_1 ("sending abort sequence for seqno %d\n", seqno); + /* Send the abort command to the control pipe. Note that we don't + need to keep track of sent abort commands because there should + never be another thread using the same slot concurrently. */ +#ifdef USE_NPTH + npth_unprotect (); +#endif + rc = libusb_control_transfer (handle->idev, + 0x21,/* bmRequestType: host-to-device, + class specific, to interface. */ + 1, /* ABORT */ + (seqno << 8 | 0 /* slot */), + handle->ifc_no, + dummybuf, 0, + 1000 /* ms timeout */); +#ifdef USE_NPTH + npth_protect (); +#endif + if (rc) + { + DEBUGOUT_1 ("usb_control_msg error: %s\n", libusb_error_name (rc)); + if (!init) + return map_libusb_error (rc); + } + + /* Now send the abort command to the bulk out pipe using the same + SEQNO and SLOT. Do this in a loop to so that all seqno are + tried. */ + seqno--; /* Adjust for next increment. */ + do + { + int transferred; + + seqno++; + msg[0] = PC_to_RDR_Abort; + msg[5] = 0; /* slot */ + msg[6] = seqno; + msg[7] = 0; /* RFU */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + msglen = 10; + set_msg_len (msg, 0); + +#ifdef USE_NPTH + npth_unprotect (); +#endif + rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_out, + msg, msglen, &transferred, + init? 100: 5000 /* ms timeout */); +#ifdef USE_NPTH + npth_protect (); +#endif + if (rc == 0 && transferred == msglen) + rc = 0; + else if (rc) + DEBUGOUT_1 ("usb_bulk_write error in abort_cmd: %s\n", + libusb_error_name (rc)); + + if (rc) + return map_libusb_error (rc); + +#ifdef USE_NPTH + npth_unprotect (); +#endif + rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_in, + msg, sizeof msg, &msglen, + init? 100: 5000 /*ms timeout*/); +#ifdef USE_NPTH + npth_protect (); +#endif + if (rc) + { + DEBUGOUT_1 ("usb_bulk_read error in abort_cmd: %s\n", + libusb_error_name (rc)); + if (init && rc == LIBUSB_ERROR_TIMEOUT) + continue; + else + return map_libusb_error (rc); + } + + if (msglen < 10) + { + DEBUGOUT_1 ("bulk-in msg in abort_cmd too short (%u)\n", + (unsigned int)msglen); + return CCID_DRIVER_ERR_INV_VALUE; + } + if (msg[5] != 0) + { + DEBUGOUT_1 ("unexpected bulk-in slot (%d) in abort_cmd\n", msg[5]); + return CCID_DRIVER_ERR_INV_VALUE; + } + + DEBUGOUT_3 ("status: %02X error: %02X octet[9]: %02X\n", + msg[7], msg[8], msg[9]); + if (CCID_COMMAND_FAILED (msg)) + print_command_failed (msg); + } + while (rc == LIBUSB_ERROR_TIMEOUT + || (msg[0] != RDR_to_PC_SlotStatus && msg[5] != 0 && msg[6] != seqno)); + + handle->seqno = ((seqno + 1) & 0xff); + DEBUGOUT ("sending abort sequence succeeded\n"); + + return 0; +} + + +/* Note that this function won't return the error codes NO_CARD or + CARD_INACTIVE. IF RESULT is not NULL, the result from the + operation will get returned in RESULT and its length in RESULTLEN. + If the response is larger than RESULTMAX, an error is returned and + the required buffer length returned in RESULTLEN. */ +static int +send_escape_cmd (ccid_driver_t handle, + const unsigned char *data, size_t datalen, + unsigned char *result, size_t resultmax, size_t *resultlen) +{ + int rc; + unsigned char msg[100]; + size_t msglen; + unsigned char seqno; + + if (resultlen) + *resultlen = 0; + + if (datalen > sizeof msg - 10) + return CCID_DRIVER_ERR_INV_VALUE; /* Escape data too large. */ + + msg[0] = PC_to_RDR_Escape; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 0; /* RFU */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + memcpy (msg+10, data, datalen); + msglen = 10 + datalen; + set_msg_len (msg, datalen); + + rc = bulk_out (handle, msg, msglen, 0); + if (rc) + return rc; + rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Escape, + seqno, 5000, 0); + if (result) + switch (rc) + { + /* We need to ignore certain errorcode here. */ + case 0: + case CCID_DRIVER_ERR_CARD_INACTIVE: + case CCID_DRIVER_ERR_NO_CARD: + { + if (msglen > resultmax) + rc = CCID_DRIVER_ERR_INV_VALUE; /* Response too large. */ + else + { + memcpy (result, msg, msglen); + if (resultlen) + *resultlen = msglen; + rc = 0; + } + } + break; + default: + break; + } + + return rc; +} + + +int +ccid_transceive_escape (ccid_driver_t handle, + const unsigned char *data, size_t datalen, + unsigned char *resp, size_t maxresplen, size_t *nresp) +{ + return send_escape_cmd (handle, data, datalen, resp, maxresplen, nresp); +} + + + +/* experimental */ +int +ccid_poll (ccid_driver_t handle) +{ + int rc; + unsigned char msg[10]; + int msglen; + int i, j; + + rc = libusb_interrupt_transfer (handle->idev, handle->ep_intr, + msg, sizeof msg, &msglen, + 0 /* ms timeout */ ); + if (rc == LIBUSB_ERROR_TIMEOUT) + return 0; + + if (rc) + { + DEBUGOUT_1 ("usb_intr_read error: %s\n", libusb_error_name (rc)); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + + if (msglen < 1) + { + DEBUGOUT ("intr-in msg too short\n"); + return CCID_DRIVER_ERR_INV_VALUE; + } + + if (msg[0] == RDR_to_PC_NotifySlotChange) + { + DEBUGOUT ("notify slot change:"); + for (i=1; i < msglen; i++) + for (j=0; j < 4; j++) + DEBUGOUT_CONT_3 (" %d:%c%c", + (i-1)*4+j, + (msg[i] & (1<<(j*2)))? 'p':'-', + (msg[i] & (2<<(j*2)))? '*':' '); + DEBUGOUT_LF (); + } + else if (msg[0] == RDR_to_PC_HardwareError) + { + DEBUGOUT ("hardware error occurred\n"); + } + else + { + DEBUGOUT_1 ("unknown intr-in msg of type %02X\n", msg[0]); + } + + return 0; +} + + +/* Note that this function won't return the error codes NO_CARD or + CARD_INACTIVE */ +int +ccid_slot_status (ccid_driver_t handle, int *statusbits, int on_wire) +{ + int rc; + unsigned char msg[100]; + size_t msglen; + unsigned char seqno; + int retries = 0; + + if (handle->powered_off) + return CCID_DRIVER_ERR_NO_READER; + + /* If the card (with its lower-level driver) doesn't require + GET_STATUS on wire (because it supports INTERRUPT transfer for + status change, or it's a token which has a card always inserted), + no need to send on wire. */ + if (!on_wire && !ccid_require_get_status (handle)) + { + /* Setup interrupt transfer at the initial call of slot_status + with ON_WIRE == 0 */ + if (handle->transfer == NULL) + ccid_setup_intr (handle); + + *statusbits = 0; + return 0; + } + + retry: + msg[0] = PC_to_RDR_GetSlotStatus; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 0; /* RFU */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + set_msg_len (msg, 0); + + rc = bulk_out (handle, msg, 10, 1); + if (rc) + return rc; + /* Note that we set the NO_DEBUG flag here, so that the logs won't + get cluttered up by a ticker function checking for the slot + status and debugging enabled. */ + rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus, + seqno, retries? 1000 : 200, 1); + if ((rc == CCID_DRIVER_ERR_CARD_IO_ERROR || rc == CCID_DRIVER_ERR_USB_TIMEOUT) + && retries < 3) + { + if (!retries) + { + DEBUGOUT ("USB: CALLING USB_CLEAR_HALT\n"); +#ifdef USE_NPTH + npth_unprotect (); +#endif + libusb_clear_halt (handle->idev, handle->ep_bulk_in); + libusb_clear_halt (handle->idev, handle->ep_bulk_out); +#ifdef USE_NPTH + npth_protect (); +#endif + } + else + DEBUGOUT ("USB: RETRYING bulk_in AGAIN\n"); + retries++; + goto retry; + } + if (rc && rc != CCID_DRIVER_ERR_NO_CARD && rc != CCID_DRIVER_ERR_CARD_INACTIVE) + return rc; + *statusbits = (msg[7] & 3); + + return 0; +} + + +/* Parse ATR string (of ATRLEN) and update parameters at PARAM. + Calling this routine, it should prepare default values at PARAM + beforehand. This routine assumes that card is accessed by T=1 + protocol. It doesn't analyze historical bytes at all. + + Returns < 0 value on error: + -1 for parse error or integrity check error + -2 for card doesn't support T=1 protocol + -3 for parameters are nod explicitly defined by ATR + -4 for this driver doesn't support CRC + + Returns >= 0 on success: + 0 for card is negotiable mode + 1 for card is specific mode (and not negotiable) + */ +static int +update_param_by_atr (unsigned char *param, unsigned char *atr, size_t atrlen) +{ + int i = -1; + int t, y, chk; + int historical_bytes_num, negotiable = 1; + +#define NEXTBYTE() do { i++; if (atrlen <= i) return -1; } while (0) + + NEXTBYTE (); + + if (atr[i] == 0x3F) + param[1] |= 0x02; /* Convention is inverse. */ + NEXTBYTE (); + + y = (atr[i] >> 4); + historical_bytes_num = atr[i] & 0x0f; + NEXTBYTE (); + + if ((y & 1)) + { + param[0] = atr[i]; /* TA1 - Fi & Di */ + NEXTBYTE (); + } + + if ((y & 2)) + NEXTBYTE (); /* TB1 - ignore */ + + if ((y & 4)) + { + param[2] = atr[i]; /* TC1 - Guard Time */ + NEXTBYTE (); + } + + if ((y & 8)) + { + y = (atr[i] >> 4); /* TD1 */ + t = atr[i] & 0x0f; + NEXTBYTE (); + + if ((y & 1)) + { /* TA2 - PPS mode */ + if ((atr[i] & 0x0f) != 1) + return -2; /* Wrong card protocol (!= 1). */ + + if ((atr[i] & 0x10) != 0x10) + return -3; /* Transmission parameters are implicitly defined. */ + + negotiable = 0; /* TA2 means specific mode. */ + NEXTBYTE (); + } + + if ((y & 2)) + NEXTBYTE (); /* TB2 - ignore */ + + if ((y & 4)) + NEXTBYTE (); /* TC2 - ignore */ + + if ((y & 8)) + { + y = (atr[i] >> 4); /* TD2 */ + t = atr[i] & 0x0f; + NEXTBYTE (); + } + else + y = 0; + + while (y) + { + if ((y & 1)) + { /* TAx */ + if (t == 1) + param[5] = atr[i]; /* IFSC */ + else if (t == 15) + /* XXX: check voltage? */ + param[4] = (atr[i] >> 6); /* ClockStop */ + + NEXTBYTE (); + } + + if ((y & 2)) + { + if (t == 1) + param[3] = atr[i]; /* TBx - BWI & CWI */ + NEXTBYTE (); + } + + if ((y & 4)) + { + if (t == 1) + param[1] |= (atr[i] & 0x01); /* TCx - LRC/CRC */ + NEXTBYTE (); + + if (param[1] & 0x01) + return -4; /* CRC not supported yet. */ + } + + if ((y & 8)) + { + y = (atr[i] >> 4); /* TDx */ + t = atr[i] & 0x0f; + NEXTBYTE (); + } + else + y = 0; + } + } + + i += historical_bytes_num - 1; + NEXTBYTE (); + if (atrlen != i+1) + return -1; + +#undef NEXTBYTE + + chk = 0; + do + { + chk ^= atr[i]; + i--; + } + while (i > 0); + + if (chk != 0) + return -1; + + return negotiable; +} + + +/* Return the ATR of the card. This is not a cached value and thus an + actual reset is done. */ +int +ccid_get_atr (ccid_driver_t handle, + unsigned char *atr, size_t maxatrlen, size_t *atrlen) +{ + int rc; + int statusbits; + unsigned char msg[100]; + unsigned char *tpdu; + size_t msglen, tpdulen; + unsigned char seqno; + int use_crc = 0; + unsigned int edc; + int tried_iso = 0; + int got_param; + unsigned char param[7] = { /* For Protocol T=1 */ + 0x11, /* bmFindexDindex */ + 0x10, /* bmTCCKST1 */ + 0x00, /* bGuardTimeT1 */ + 0x4d, /* bmWaitingIntegersT1 */ + 0x00, /* bClockStop */ + 0x20, /* bIFSC */ + 0x00 /* bNadValue */ + }; + + /* First check whether a card is available. */ + rc = ccid_slot_status (handle, &statusbits, 1); + if (rc) + return rc; + if (statusbits == 2) + return CCID_DRIVER_ERR_NO_CARD; + + /* + * In the first invocation of ccid_slot_status, card reader may + * return CCID_DRIVER_ERR_CARD_INACTIVE and handle->powered_off may + * become 1. Because inactive card is no problem (we are turning it + * ON here), clear the flag. + */ + handle->powered_off = 0; + + /* For an inactive and also for an active card, issue the PowerOn + command to get the ATR. */ + again: + msg[0] = PC_to_RDR_IccPowerOn; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + /* power select (0=auto, 1=5V, 2=3V, 3=1.8V) */ + msg[7] = handle->auto_voltage ? 0 : 1; + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + set_msg_len (msg, 0); + msglen = 10; + + rc = bulk_out (handle, msg, msglen, 0); + if (rc) + return rc; + rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock, + seqno, 5000, 0); + if (rc) + return rc; + if (!tried_iso && CCID_COMMAND_FAILED (msg) && CCID_ERROR_CODE (msg) == 0xbb + && ((handle->id_vendor == VENDOR_CHERRY + && handle->id_product == 0x0005) + || (handle->id_vendor == VENDOR_GEMPC + && handle->id_product == 0x4433) + )) + { + tried_iso = 1; + /* Try switching to ISO mode. */ + if (!send_escape_cmd (handle, (const unsigned char*)"\xF1\x01", 2, + NULL, 0, NULL)) + goto again; + } + else if (statusbits == 0 && CCID_COMMAND_FAILED (msg)) + { + /* Card was active already, and something went wrong with + PC_to_RDR_IccPowerOn command. It may be baud-rate mismatch + between the card and the reader. To recover from this state, + send PC_to_RDR_IccPowerOff command to reset the card and try + again. + */ + rc = send_power_off (handle); + if (rc) + return rc; + + statusbits = 1; + goto again; + } + else if (CCID_COMMAND_FAILED (msg)) + return CCID_DRIVER_ERR_CARD_IO_ERROR; + + + handle->powered_off = 0; + + if (atr) + { + size_t n = msglen - 10; + + if (n > maxatrlen) + n = maxatrlen; + memcpy (atr, msg+10, n); + *atrlen = n; + } + + param[6] = handle->nonnull_nad? ((1 << 4) | 0): 0; + rc = update_param_by_atr (param, msg+10, msglen - 10); + if (rc < 0) + { + DEBUGOUT_1 ("update_param_by_atr failed: %d\n", rc); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + + got_param = 0; + + if (handle->auto_param) + { + msg[0] = PC_to_RDR_GetParameters; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 0; /* RFU */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + set_msg_len (msg, 0); + msglen = 10; + rc = bulk_out (handle, msg, msglen, 0); + if (!rc) + rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters, + seqno, 2000, 0); + if (rc) + DEBUGOUT ("GetParameters failed\n"); + else if (msglen == 17 && msg[9] == 1) + got_param = 1; + } + else if (handle->auto_pps) + ; + else if (rc == 1) /* It's negotiable, send PPS. */ + { + msg[0] = PC_to_RDR_XfrBlock; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 0; + msg[8] = 0; + msg[9] = 0; + msg[10] = 0xff; /* PPSS */ + msg[11] = 0x11; /* PPS0: PPS1, Protocol T=1 */ + msg[12] = param[0]; /* PPS1: Fi / Di */ + msg[13] = 0xff ^ 0x11 ^ param[0]; /* PCK */ + set_msg_len (msg, 4); + msglen = 10 + 4; + + rc = bulk_out (handle, msg, msglen, 0); + if (rc) + return rc; + + rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock, + seqno, 5000, 0); + if (rc) + return rc; + + if (msglen != 10 + 4) + { + DEBUGOUT_1 ("Setting PPS failed: %zu\n", msglen); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + + if (msg[10] != 0xff || msg[11] != 0x11 || msg[12] != param[0]) + { + DEBUGOUT_1 ("Setting PPS failed: 0x%02x\n", param[0]); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + } + + /* Setup parameters to select T=1. */ + msg[0] = PC_to_RDR_SetParameters; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 1; /* Select T=1. */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + + if (!got_param) + memcpy (&msg[10], param, 7); + set_msg_len (msg, 7); + msglen = 10 + 7; + + rc = bulk_out (handle, msg, msglen, 0); + if (rc) + return rc; + rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters, + seqno, 5000, 0); + if (rc) + DEBUGOUT ("SetParameters failed (ignored)\n"); + + if (!rc && msglen > 15 && msg[15] >= 16 && msg[15] <= 254 ) + handle->ifsc = msg[15]; + else + handle->ifsc = 128; /* Something went wrong, assume 128 bytes. */ + + if (handle->nonnull_nad && msglen > 16 && msg[16] == 0) + { + DEBUGOUT ("Use Null-NAD, clearing handle->nonnull_nad.\n"); + handle->nonnull_nad = 0; + } + + handle->t1_ns = 0; + handle->t1_nr = 0; + + /* Send an S-Block with our maximum IFSD to the CCID. */ + if (!handle->apdu_level && !handle->auto_ifsd) + { + tpdu = msg+10; + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; + tpdu[1] = (0xc0 | 0 | 1); /* S-block request: change IFSD */ + tpdu[2] = 1; + tpdu[3] = handle->max_ifsd? handle->max_ifsd : 32; + tpdulen = 4; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; + + msg[0] = PC_to_RDR_XfrBlock; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 0; + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + set_msg_len (msg, tpdulen); + msglen = 10 + tpdulen; + + if (debug_level > 1) + DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n", + ((msg[11] & 0xc0) == 0x80)? 'R' : + (msg[11] & 0x80)? 'S' : 'I', + ((msg[11] & 0x80)? !!(msg[11]& 0x10) + : !!(msg[11] & 0x40)), + (!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":"")); + + rc = bulk_out (handle, msg, msglen, 0); + if (rc) + return rc; + + + rc = bulk_in (handle, msg, sizeof msg, &msglen, + RDR_to_PC_DataBlock, seqno, 5000, 0); + if (rc) + return rc; + + tpdu = msg + 10; + tpdulen = msglen - 10; + + if (tpdulen < 4) + return CCID_DRIVER_ERR_ABORTED; + + if (debug_level > 1) + DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n", + ((msg[11] & 0xc0) == 0x80)? 'R' : + (msg[11] & 0x80)? 'S' : 'I', + ((msg[11] & 0x80)? !!(msg[11]& 0x10) + : !!(msg[11] & 0x40)), + ((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0, + (!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":"")); + + if ((tpdu[1] & 0xe0) != 0xe0 || tpdu[2] != 1) + { + DEBUGOUT ("invalid response for S-block (Change-IFSD)\n"); + return -1; + } + DEBUGOUT_1 ("IFSD has been set to %d\n", tpdu[3]); + } + + ccid_vendor_specific_setup (handle); + return 0; +} + + + + +static unsigned int +compute_edc (const unsigned char *data, size_t datalen, int use_crc) +{ + if (use_crc) + { + return 0x42; /* Not yet implemented. */ + } + else + { + unsigned char crc = 0; + + for (; datalen; datalen--) + crc ^= *data++; + return crc; + } +} + + +/* Return true if APDU is an extended length one. */ +static int +is_exlen_apdu (const unsigned char *apdu, size_t apdulen) +{ + if (apdulen < 7 || apdu[4]) + return 0; /* Too short or no Z byte. */ + return 1; +} + + +/* Helper for ccid_transceive used for APDU level exchanges. */ +static int +ccid_transceive_apdu_level (ccid_driver_t handle, + const unsigned char *apdu_buf, size_t apdu_len, + unsigned char *resp, size_t maxresplen, + size_t *nresp) +{ + int rc; + unsigned char msg[CCID_MAX_BUF]; + const unsigned char *apdu_p; + size_t apdu_part_len; + size_t msglen; + unsigned char seqno; + int bwi = 0; + unsigned char chain = 0; + + if (apdu_len == 0 || apdu_len > sizeof (msg) - 10) + return CCID_DRIVER_ERR_INV_VALUE; /* Invalid length. */ + + apdu_p = apdu_buf; + while (1) + { + apdu_part_len = apdu_len; + if (apdu_part_len > handle->max_ccid_msglen - 10) + { + apdu_part_len = handle->max_ccid_msglen - 10; + chain |= 0x01; + } + + msg[0] = PC_to_RDR_XfrBlock; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = bwi; + msg[8] = chain; + msg[9] = 0; + memcpy (msg+10, apdu_p, apdu_part_len); + set_msg_len (msg, apdu_part_len); + msglen = 10 + apdu_part_len; + + rc = bulk_out (handle, msg, msglen, 0); + if (rc) + return rc; + + apdu_p += apdu_part_len; + apdu_len -= apdu_part_len; + + rc = bulk_in (handle, msg, sizeof msg, &msglen, + RDR_to_PC_DataBlock, seqno, CCID_CMD_TIMEOUT, 0); + if (rc) + return rc; + + if (!(chain & 0x01)) + break; + + chain = 0x02; + } + + apdu_len = 0; + while (1) + { + apdu_part_len = msglen - 10; + if (resp && apdu_len + apdu_part_len <= maxresplen) + memcpy (resp + apdu_len, msg+10, apdu_part_len); + apdu_len += apdu_part_len; + + if (!(msg[9] & 0x01)) + break; + + msg[0] = PC_to_RDR_XfrBlock; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = bwi; + msg[8] = 0x10; /* Request next data block */ + msg[9] = 0; + set_msg_len (msg, 0); + msglen = 10; + + rc = bulk_out (handle, msg, msglen, 0); + if (rc) + return rc; + + rc = bulk_in (handle, msg, sizeof msg, &msglen, + RDR_to_PC_DataBlock, seqno, CCID_CMD_TIMEOUT, 0); + if (rc) + return rc; + } + + if (resp) + { + if (apdu_len > maxresplen) + { + DEBUGOUT_2 ("provided buffer too short for received data " + "(%u/%u)\n", + (unsigned int)apdu_len, (unsigned int)maxresplen); + return CCID_DRIVER_ERR_INV_VALUE; + } + + *nresp = apdu_len; + } + + return 0; +} + + + +/* + Protocol T=1 overview + + Block Structure: + Prologue Field: + 1 byte Node Address (NAD) + 1 byte Protocol Control Byte (PCB) + 1 byte Length (LEN) + Information Field: + 0-254 byte APDU or Control Information (INF) + Epilogue Field: + 1 byte Error Detection Code (EDC) + + NAD: + bit 7 unused + bit 4..6 Destination Node Address (DAD) + bit 3 unused + bit 2..0 Source Node Address (SAD) + + If node adresses are not used, SAD and DAD should be set to 0 on + the first block sent to the card. If they are used they should + have different values (0 for one is okay); that first block sets up + the addresses of the nodes. + + PCB: + Information Block (I-Block): + bit 7 0 + bit 6 Sequence number (yep, that is modulo 2) + bit 5 Chaining flag + bit 4..0 reserved + Received-Ready Block (R-Block): + bit 7 1 + bit 6 0 + bit 5 0 + bit 4 Sequence number + bit 3..0 0 = no error + 1 = EDC or parity error + 2 = other error + other values are reserved + Supervisory Block (S-Block): + bit 7 1 + bit 6 1 + bit 5 clear=request,set=response + bit 4..0 0 = resynchronization request + 1 = information field size request + 2 = abort request + 3 = extension of BWT request + 4 = VPP error + other values are reserved + +*/ + +int +ccid_transceive (ccid_driver_t handle, + const unsigned char *apdu_buf, size_t apdu_buflen, + unsigned char *resp, size_t maxresplen, size_t *nresp) +{ + int rc; + /* The size of the buffer used to be 10+259. For the via_escape + hack we need one extra byte, thus 11+259. */ + unsigned char send_buffer[11+259], recv_buffer[11+259]; + const unsigned char *apdu; + size_t apdulen; + unsigned char *msg, *tpdu, *p; + size_t msglen, tpdulen, last_tpdulen, n; + unsigned char seqno; + unsigned int edc; + int use_crc = 0; + int hdrlen, pcboff; + size_t dummy_nresp; + int via_escape = 0; + int next_chunk = 1; + int sending = 1; + int retries = 0; + int resyncing = 0; + int nad_byte; + int wait_more = 0; + + if (!nresp) + nresp = &dummy_nresp; + *nresp = 0; + + /* Smarter readers allow sending APDUs directly; divert here. */ + if (handle->apdu_level) + { + /* We employ a hack for Omnikey readers which are able to send + TPDUs using an escape sequence. There is no documentation + but the Windows driver does it this way. Tested using a + CM6121. This method works also for the Cherry XX44 + keyboards; however there are problems with the + ccid_transceive_secure which leads to a loss of sync on the + CCID level. If Cherry wants to make their keyboard work + again, they should hand over some docs. */ + if ((handle->id_vendor == VENDOR_OMNIKEY) + && handle->apdu_level < 2 + && is_exlen_apdu (apdu_buf, apdu_buflen)) + via_escape = 1; + else + return ccid_transceive_apdu_level (handle, apdu_buf, apdu_buflen, + resp, maxresplen, nresp); + } + + /* The other readers we support require sending TPDUs. */ + + tpdulen = 0; /* Avoid compiler warning about no initialization. */ + msg = send_buffer; + hdrlen = via_escape? 11 : 10; + + /* NAD: DAD=1, SAD=0 */ + nad_byte = handle->nonnull_nad? ((1 << 4) | 0): 0; + if (via_escape) + nad_byte = 0; + + last_tpdulen = 0; /* Avoid gcc warning (controlled by RESYNCING). */ + for (;;) + { + if (next_chunk) + { + next_chunk = 0; + + apdu = apdu_buf; + apdulen = apdu_buflen; + assert (apdulen); + + /* Construct an I-Block. */ + tpdu = msg + hdrlen; + tpdu[0] = nad_byte; + tpdu[1] = ((handle->t1_ns & 1) << 6); /* I-block */ + if (apdulen > handle->ifsc ) + { + apdulen = handle->ifsc; + apdu_buf += handle->ifsc; + apdu_buflen -= handle->ifsc; + tpdu[1] |= (1 << 5); /* Set more bit. */ + } + tpdu[2] = apdulen; + memcpy (tpdu+3, apdu, apdulen); + tpdulen = 3 + apdulen; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; + } + + if (via_escape) + { + msg[0] = PC_to_RDR_Escape; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 0; /* RFU */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + msg[10] = 0x1a; /* Omnikey command to send a TPDU. */ + set_msg_len (msg, 1 + tpdulen); + } + else + { + msg[0] = PC_to_RDR_XfrBlock; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = (wait_more ? wait_more : 1); /* bBWI */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + set_msg_len (msg, tpdulen); + } + msglen = hdrlen + tpdulen; + if (!resyncing) + last_tpdulen = tpdulen; + pcboff = hdrlen+1; + + if (debug_level > 1) + DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n", + ((msg[pcboff] & 0xc0) == 0x80)? 'R' : + (msg[pcboff] & 0x80)? 'S' : 'I', + ((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10) + : !!(msg[pcboff] & 0x40)), + (!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)? + " [more]":"")); + + rc = bulk_out (handle, msg, msglen, 0); + if (rc) + return rc; + + msg = recv_buffer; + rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen, + via_escape? RDR_to_PC_Escape : RDR_to_PC_DataBlock, seqno, + (wait_more ? wait_more : 1) * CCID_CMD_TIMEOUT, 0); + if (rc) + return rc; + + tpdu = msg + hdrlen; + tpdulen = msglen - hdrlen; + resyncing = 0; + + if (tpdulen < 4) + { +#ifdef USE_NPTH + npth_unprotect (); +#endif + libusb_clear_halt (handle->idev, handle->ep_bulk_in); +#ifdef USE_NPTH + npth_protect (); +#endif + return CCID_DRIVER_ERR_ABORTED; + } + + if (debug_level > 1) + DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n", + ((msg[pcboff] & 0xc0) == 0x80)? 'R' : + (msg[pcboff] & 0x80)? 'S' : 'I', + ((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10) + : !!(msg[pcboff] & 0x40)), + ((msg[pcboff] & 0xc0) == 0x80)? (msg[pcboff] & 0x0f) : 0, + (!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)? + " [more]":"")); + + wait_more = 0; + if (!(tpdu[1] & 0x80)) + { /* This is an I-block. */ + retries = 0; + if (sending) + { /* last block sent was successful. */ + handle->t1_ns ^= 1; + sending = 0; + } + + if (!!(tpdu[1] & 0x40) != handle->t1_nr) + { /* Response does not match our sequence number. */ + msg = send_buffer; + tpdu = msg + hdrlen; + tpdu[0] = nad_byte; + tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4 | 2); /* R-block */ + tpdu[2] = 0; + tpdulen = 3; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; + + continue; + } + + handle->t1_nr ^= 1; + + p = tpdu + 3; /* Skip the prologue field. */ + n = tpdulen - 3 - 1; /* Strip the epilogue field. */ + /* fixme: verify the checksum. */ + if (resp) + { + if (n > maxresplen) + { + DEBUGOUT_2 ("provided buffer too short for received data " + "(%u/%u)\n", + (unsigned int)n, (unsigned int)maxresplen); + return CCID_DRIVER_ERR_INV_VALUE; + } + + memcpy (resp, p, n); + resp += n; + *nresp += n; + maxresplen -= n; + } + + if (!(tpdu[1] & 0x20)) + return 0; /* No chaining requested - ready. */ + + msg = send_buffer; + tpdu = msg + hdrlen; + tpdu[0] = nad_byte; + tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4); /* R-block */ + tpdu[2] = 0; + tpdulen = 3; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; + } + else if ((tpdu[1] & 0xc0) == 0x80) + { /* This is a R-block. */ + if ( (tpdu[1] & 0x0f)) + { + retries++; + if (via_escape && retries == 1 && (msg[pcboff] & 0x0f)) + { + /* Error probably due to switching to TPDU. Send a + resync request. We use the recv_buffer so that + we don't corrupt the send_buffer. */ + msg = recv_buffer; + tpdu = msg + hdrlen; + tpdu[0] = nad_byte; + tpdu[1] = 0xc0; /* S-block resync request. */ + tpdu[2] = 0; + tpdulen = 3; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; + resyncing = 1; + DEBUGOUT ("T=1: requesting resync\n"); + } + else if (retries > 3) + { + DEBUGOUT ("T=1: 3 failed retries\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + else + { + /* Error: repeat last block */ + msg = send_buffer; + tpdulen = last_tpdulen; + } + } + else if (sending && !!(tpdu[1] & 0x10) == handle->t1_ns) + { /* Response does not match our sequence number. */ + DEBUGOUT ("R-block with wrong seqno received on more bit\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + else if (sending) + { /* Send next chunk. */ + retries = 0; + msg = send_buffer; + next_chunk = 1; + handle->t1_ns ^= 1; + } + else + { + DEBUGOUT ("unexpected ACK R-block received\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + } + else + { /* This is a S-block. */ + retries = 0; + DEBUGOUT_2 ("T=1: S-block %s received cmd=%d\n", + (tpdu[1] & 0x20)? "response": "request", + (tpdu[1] & 0x1f)); + if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 1 && tpdu[2] == 1) + { + /* Information field size request. */ + unsigned char ifsc = tpdu[3]; + + if (ifsc < 16 || ifsc > 254) + return CCID_DRIVER_ERR_CARD_IO_ERROR; + + msg = send_buffer; + tpdu = msg + hdrlen; + tpdu[0] = nad_byte; + tpdu[1] = (0xc0 | 0x20 | 1); /* S-block response */ + tpdu[2] = 1; + tpdu[3] = ifsc; + tpdulen = 4; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; + DEBUGOUT_1 ("T=1: requesting an ifsc=%d\n", ifsc); + } + else if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 3 && tpdu[2]) + { + /* Wait time extension request. */ + unsigned char bwi = tpdu[3]; + + wait_more = bwi; + + msg = send_buffer; + tpdu = msg + hdrlen; + tpdu[0] = nad_byte; + tpdu[1] = (0xc0 | 0x20 | 3); /* S-block response */ + tpdu[2] = 1; + tpdu[3] = bwi; + tpdulen = 4; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; + DEBUGOUT_1 ("T=1: waittime extension of bwi=%d\n", bwi); + print_progress (handle); + } + else if ( (tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 0 && !tpdu[2]) + { + DEBUGOUT ("T=1: resync ack from reader\n"); + /* Repeat previous block. */ + msg = send_buffer; + tpdulen = last_tpdulen; + } + else + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + } /* end T=1 protocol loop. */ + + return 0; +} + + +/* Send the CCID Secure command to the reader. APDU_BUF should + contain the APDU template. PIN_MODE defines how the pin gets + formatted: + + 1 := The PIN is ASCII encoded and of variable length. The + length of the PIN entered will be put into Lc by the reader. + The APDU should me made up of 4 bytes without Lc. + + PINLEN_MIN and PINLEN_MAX define the limits for the pin length. 0 + may be used t enable reasonable defaults. + + When called with RESP and NRESP set to NULL, the function will + merely check whether the reader supports the secure command for the + given APDU and PIN_MODE. */ +int +ccid_transceive_secure (ccid_driver_t handle, + const unsigned char *apdu_buf, size_t apdu_buflen, + pininfo_t *pininfo, + unsigned char *resp, size_t maxresplen, size_t *nresp) +{ + int rc; + unsigned char send_buffer[10+259], recv_buffer[10+259]; + unsigned char *msg, *tpdu, *p; + size_t msglen, tpdulen, n; + unsigned char seqno; + size_t dummy_nresp; + int testmode; + int cherry_mode = 0; + int add_zero = 0; + int enable_varlen = 0; + + testmode = !resp && !nresp; + + if (!nresp) + nresp = &dummy_nresp; + *nresp = 0; + + if (apdu_buflen >= 4 && apdu_buf[1] == 0x20 && (handle->has_pinpad & 1)) + ; + else if (apdu_buflen >= 4 && apdu_buf[1] == 0x24 && (handle->has_pinpad & 2)) + ; + else + return CCID_DRIVER_ERR_NO_PINPAD; + + if (!pininfo->minlen) + pininfo->minlen = 1; + if (!pininfo->maxlen) + pininfo->maxlen = 15; + + /* Note that the 25 is the maximum value the SPR532 allows. */ + if (pininfo->minlen < 1 || pininfo->minlen > 25 + || pininfo->maxlen < 1 || pininfo->maxlen > 25 + || pininfo->minlen > pininfo->maxlen) + return CCID_DRIVER_ERR_INV_VALUE; + + /* We have only tested a few readers so better don't risk anything + and do not allow the use with other readers. */ + switch (handle->id_vendor) + { + case VENDOR_SCM: /* Tested with SPR 532. */ + case VENDOR_KAAN: /* Tested with KAAN Advanced (1.02). */ + case VENDOR_FSIJ: /* Tested with Gnuk (0.21). */ + pininfo->maxlen = 25; + enable_varlen = 1; + break; + case VENDOR_REINER:/* Tested with cyberJack go */ + case VENDOR_VASCO: /* Tested with DIGIPASS 920 */ + enable_varlen = 1; + break; + case VENDOR_CHERRY: + pininfo->maxlen = 15; + enable_varlen = 1; + /* The CHERRY XX44 keyboard echos an asterisk for each entered + character on the keyboard channel. We use a special variant + of PC_to_RDR_Secure which directs these characters to the + smart card's bulk-in channel. We also need to append a zero + Lc byte to the APDU. It seems that it will be replaced with + the actual length instead of being appended before the APDU + is send to the card. */ + add_zero = 1; + if (handle->id_product != CHERRY_ST2000) + cherry_mode = 1; + break; + case VENDOR_NXP: + if (handle->id_product == CRYPTOUCAN){ + pininfo->maxlen = 25; + enable_varlen = 1; + break; + } + return CCID_DRIVER_ERR_NOT_SUPPORTED; + case VENDOR_GEMPC: + if (handle->id_product == GEMPC_PINPAD) + { + enable_varlen = 0; + pininfo->minlen = 4; + pininfo->maxlen = 8; + break; + } + else if (handle->id_product == GEMPC_EZIO) + { + pininfo->maxlen = 25; + enable_varlen = 1; + break; + } + return CCID_DRIVER_ERR_NOT_SUPPORTED; + default: + if ((handle->id_vendor == VENDOR_VEGA && + handle->id_product == VEGA_ALPHA)) + { + enable_varlen = 0; + pininfo->minlen = 4; + pininfo->maxlen = 8; + break; + } + return CCID_DRIVER_ERR_NOT_SUPPORTED; + } + + if (enable_varlen) + pininfo->fixedlen = 0; + + if (testmode) + return 0; /* Success */ + + if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16) + return CCID_DRIVER_ERR_NOT_SUPPORTED; + + ccid_vendor_specific_pinpad_setup (handle); + + msg = send_buffer; + msg[0] = cherry_mode? 0x89 : PC_to_RDR_Secure; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 0; /* bBWI */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + msg[10] = apdu_buf[1] == 0x20 ? 0 : 1; + /* Perform PIN verification or PIN modification. */ + msg[11] = 0; /* Timeout in seconds. */ + msg[12] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */ + if (handle->id_vendor == VENDOR_SCM) + { + /* For the SPR532 the next 2 bytes need to be zero. We do this + for all SCM products. Kudos to Martin Paljak for this + hint. */ + msg[13] = msg[14] = 0; + } + else + { + msg[13] = pininfo->fixedlen; /* bmPINBlockString: + 0 bits of pin length to insert. + PIN block size by fixedlen. */ + msg[14] = 0x00; /* bmPINLengthFormat: + Units are bytes, position is 0. */ + } + + msglen = 15; + if (apdu_buf[1] == 0x24) + { + msg[msglen++] = 0; /* bInsertionOffsetOld */ + msg[msglen++] = pininfo->fixedlen; /* bInsertionOffsetNew */ + } + + /* The following is a little endian word. */ + msg[msglen++] = pininfo->maxlen; /* wPINMaxExtraDigit-Maximum. */ + msg[msglen++] = pininfo->minlen; /* wPINMaxExtraDigit-Minimum. */ + + if (apdu_buf[1] == 0x24) + msg[msglen++] = apdu_buf[2] == 0 ? 0x03 : 0x01; + /* bConfirmPIN + * 0x00: new PIN once + * 0x01: new PIN twice (confirmation) + * 0x02: old PIN and new PIN once + * 0x03: old PIN and new PIN twice (confirmation) + */ + + msg[msglen] = 0x02; /* bEntryValidationCondition: + Validation key pressed */ + if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen) + msg[msglen] |= 0x01; /* Max size reached. */ + msglen++; + + if (apdu_buf[1] == 0x20) + msg[msglen++] = 0x01; /* bNumberMessage. */ + else + msg[msglen++] = 0x03; /* bNumberMessage. */ + + msg[msglen++] = 0x09; /* wLangId-Low: English FIXME: use the first entry. */ + msg[msglen++] = 0x04; /* wLangId-High. */ + + if (apdu_buf[1] == 0x20) + msg[msglen++] = 0; /* bMsgIndex. */ + else + { + msg[msglen++] = 0; /* bMsgIndex1. */ + msg[msglen++] = 1; /* bMsgIndex2. */ + msg[msglen++] = 2; /* bMsgIndex3. */ + } + + /* Calculate Lc. */ + n = pininfo->fixedlen; + if (apdu_buf[1] == 0x24) + n += pininfo->fixedlen; + + /* bTeoProlog follows: */ + msg[msglen++] = handle->nonnull_nad? ((1 << 4) | 0): 0; + msg[msglen++] = ((handle->t1_ns & 1) << 6); /* I-block */ + if (n) + msg[msglen++] = n + 5; /* apdulen should be filled for fixed length. */ + else + msg[msglen++] = 0; /* The apdulen will be filled in by the reader. */ + /* APDU follows: */ + msg[msglen++] = apdu_buf[0]; /* CLA */ + msg[msglen++] = apdu_buf[1]; /* INS */ + msg[msglen++] = apdu_buf[2]; /* P1 */ + msg[msglen++] = apdu_buf[3]; /* P2 */ + if (add_zero) + msg[msglen++] = 0; + else if (pininfo->fixedlen != 0) + { + msg[msglen++] = n; + memset (&msg[msglen], 0xff, n); + msglen += n; + } + /* An EDC is not required. */ + set_msg_len (msg, msglen - 10); + + rc = bulk_out (handle, msg, msglen, 0); + if (rc) + return rc; + + msg = recv_buffer; + rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen, + RDR_to_PC_DataBlock, seqno, 30000, 0); + if (rc) + return rc; + + tpdu = msg + 10; + tpdulen = msglen - 10; + + if (handle->apdu_level) + { + if (resp) + { + if (tpdulen > maxresplen) + { + DEBUGOUT_2 ("provided buffer too short for received data " + "(%u/%u)\n", + (unsigned int)tpdulen, (unsigned int)maxresplen); + return CCID_DRIVER_ERR_INV_VALUE; + } + + memcpy (resp, tpdu, tpdulen); + *nresp = tpdulen; + } + return 0; + } + + if (tpdulen < 4) + { +#ifdef USE_NPTH + npth_unprotect (); +#endif + libusb_clear_halt (handle->idev, handle->ep_bulk_in); +#ifdef USE_NPTH + npth_protect (); +#endif + return CCID_DRIVER_ERR_ABORTED; + } + if (debug_level > 1) + DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n", + ((msg[11] & 0xc0) == 0x80)? 'R' : + (msg[11] & 0x80)? 'S' : 'I', + ((msg[11] & 0x80)? !!(msg[11]& 0x10) : !!(msg[11] & 0x40)), + ((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0, + (!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":"")); + + if (!(tpdu[1] & 0x80)) + { /* This is an I-block. */ + /* Last block sent was successful. */ + handle->t1_ns ^= 1; + + if (!!(tpdu[1] & 0x40) != handle->t1_nr) + { /* Response does not match our sequence number. */ + DEBUGOUT ("I-block with wrong seqno received\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + + handle->t1_nr ^= 1; + + p = tpdu + 3; /* Skip the prologue field. */ + n = tpdulen - 3 - 1; /* Strip the epilogue field. */ + /* fixme: verify the checksum. */ + if (resp) + { + if (n > maxresplen) + { + DEBUGOUT_2 ("provided buffer too short for received data " + "(%u/%u)\n", + (unsigned int)n, (unsigned int)maxresplen); + return CCID_DRIVER_ERR_INV_VALUE; + } + + memcpy (resp, p, n); + *nresp += n; + } + + if (!(tpdu[1] & 0x20)) + return 0; /* No chaining requested - ready. */ + + DEBUGOUT ("chaining requested but not supported for Secure operation\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + else if ((tpdu[1] & 0xc0) == 0x80) + { /* This is a R-block. */ + if ( (tpdu[1] & 0x0f)) + { /* Error: repeat last block */ + DEBUGOUT ("No retries supported for Secure operation\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + else if (!!(tpdu[1] & 0x10) == handle->t1_ns) + { /* Response does not match our sequence number. */ + DEBUGOUT ("R-block with wrong seqno received on more bit\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + else + { /* Send next chunk. */ + DEBUGOUT ("chaining not supported on Secure operation\n"); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + } + else + { /* This is a S-block. */ + DEBUGOUT_2 ("T=1: S-block %s received cmd=%d for Secure operation\n", + (tpdu[1] & 0x20)? "response": "request", + (tpdu[1] & 0x1f)); + return CCID_DRIVER_ERR_CARD_IO_ERROR; + } + + return 0; +} + + + + +#ifdef TEST + + +static void +print_error (int err) +{ + const char *p; + char buf[50]; + + switch (err) + { + case 0: p = "success"; break; + case CCID_DRIVER_ERR_OUT_OF_CORE: p = "out of core"; break; + case CCID_DRIVER_ERR_INV_VALUE: p = "invalid value"; break; + case CCID_DRIVER_ERR_NO_DRIVER: p = "no driver"; break; + case CCID_DRIVER_ERR_NOT_SUPPORTED: p = "not supported"; break; + case CCID_DRIVER_ERR_LOCKING_FAILED: p = "locking failed"; break; + case CCID_DRIVER_ERR_BUSY: p = "busy"; break; + case CCID_DRIVER_ERR_NO_CARD: p = "no card"; break; + case CCID_DRIVER_ERR_CARD_INACTIVE: p = "card inactive"; break; + case CCID_DRIVER_ERR_CARD_IO_ERROR: p = "card I/O error"; break; + case CCID_DRIVER_ERR_GENERAL_ERROR: p = "general error"; break; + case CCID_DRIVER_ERR_NO_READER: p = "no reader"; break; + case CCID_DRIVER_ERR_ABORTED: p = "aborted"; break; + default: sprintf (buf, "0x%05x", err); p = buf; break; + } + fprintf (stderr, "operation failed: %s\n", p); +} + + +static void +print_data (const unsigned char *data, size_t length) +{ + if (length >= 2) + { + fprintf (stderr, "operation status: %02X%02X\n", + data[length-2], data[length-1]); + length -= 2; + } + if (length) + { + fputs (" returned data:", stderr); + for (; length; length--, data++) + fprintf (stderr, " %02X", *data); + putc ('\n', stderr); + } +} + +static void +print_result (int rc, const unsigned char *data, size_t length) +{ + if (rc) + print_error (rc); + else if (data) + print_data (data, length); +} + +int +main (int argc, char **argv) +{ + gpg_error_t err; + ccid_driver_t ccid; + int slotstat; + unsigned char result[512]; + size_t resultlen; + int no_pinpad = 0; + int verify_123456 = 0; + int did_verify = 0; + int no_poll = 0; + int idx_max; + struct ccid_dev_table *ccid_table; + + if (argc) + { + argc--; + argv++; + } + + while (argc) + { + if ( !strcmp (*argv, "--list")) + { + char *p; + p = ccid_get_reader_list (); + if (!p) + return 1; + fputs (p, stderr); + free (p); + return 0; + } + else if ( !strcmp (*argv, "--debug")) + { + ccid_set_debug_level (ccid_set_debug_level (-1)+1); + argc--; argv++; + } + else if ( !strcmp (*argv, "--no-poll")) + { + no_poll = 1; + argc--; argv++; + } + else if ( !strcmp (*argv, "--no-pinpad")) + { + no_pinpad = 1; + argc--; argv++; + } + else if ( !strcmp (*argv, "--verify-123456")) + { + verify_123456 = 1; + argc--; argv++; + } + else + break; + } + + err = ccid_dev_scan (&idx_max, &ccid_table); + if (err) + return 1; + + if (idx_max == 0) + return 1; + + err = ccid_open_reader (argc? *argv:NULL, 0, ccid_table, &ccid, NULL); + if (err) + return 1; + + ccid_dev_scan_finish (ccid_table, idx_max); + + if (!no_poll) + ccid_poll (ccid); + fputs ("getting ATR ...\n", stderr); + err = ccid_get_atr (ccid, NULL, 0, NULL); + if (err) + { + print_error (err); + return 1; + } + + if (!no_poll) + ccid_poll (ccid); + fputs ("getting slot status ...\n", stderr); + err = ccid_slot_status (ccid, &slotstat, 1); + if (err) + { + print_error (err); + return 1; + } + + if (!no_poll) + ccid_poll (ccid); + + fputs ("selecting application OpenPGP ....\n", stderr); + { + static unsigned char apdu[] = { + 0, 0xA4, 4, 0, 6, 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01}; + err = ccid_transceive (ccid, + apdu, sizeof apdu, + result, sizeof result, &resultlen); + print_result (err, result, resultlen); + } + + + if (!no_poll) + ccid_poll (ccid); + + fputs ("getting OpenPGP DO 0x65 ....\n", stderr); + { + static unsigned char apdu[] = { 0, 0xCA, 0, 0x65, 254 }; + err = ccid_transceive (ccid, apdu, sizeof apdu, + result, sizeof result, &resultlen); + print_result (err, result, resultlen); + } + + if (!no_pinpad) + { + } + + if (!no_pinpad) + { + static unsigned char apdu[] = { 0, 0x20, 0, 0x81 }; + pininfo_t pininfo = { 0, 0, 0 }; + + if (ccid_transceive_secure (ccid, apdu, sizeof apdu, &pininfo, + NULL, 0, NULL)) + fputs ("can't verify using a PIN-Pad reader\n", stderr); + else + { + fputs ("verifying CHV1 using the PINPad ....\n", stderr); + + err = ccid_transceive_secure (ccid, apdu, sizeof apdu, &pininfo, + result, sizeof result, &resultlen); + print_result (err, result, resultlen); + did_verify = 1; + } + } + + if (verify_123456 && !did_verify) + { + fputs ("verifying that CHV1 is 123456....\n", stderr); + { + static unsigned char apdu[] = {0, 0x20, 0, 0x81, + 6, '1','2','3','4','5','6'}; + err = ccid_transceive (ccid, apdu, sizeof apdu, + result, sizeof result, &resultlen); + print_result (err, result, resultlen); + } + } + + if (!err) + { + fputs ("getting OpenPGP DO 0x5E ....\n", stderr); + { + static unsigned char apdu[] = { 0, 0xCA, 0, 0x5E, 254 }; + err = ccid_transceive (ccid, apdu, sizeof apdu, + result, sizeof result, &resultlen); + print_result (err, result, resultlen); + } + } + + ccid_close_reader (ccid); + + return 0; +} + +/* + * Local Variables: + * compile-command: "gcc -DTEST -DGPGRT_ENABLE_ES_MACROS -DHAVE_NPTH -DUSE_NPTH -Wall -I/usr/include/libusb-1.0 -I/usr/local/include -lusb-1.0 -g ccid-driver.c -lnpth -lgpg-error" + * End: + */ +#endif /*TEST*/ +#endif /*HAVE_LIBUSB*/ diff --git a/scd/ccid-driver.h b/scd/ccid-driver.h new file mode 100644 index 0000000..dc17c27 --- /dev/null +++ b/scd/ccid-driver.h @@ -0,0 +1,158 @@ +/* ccid-driver.h - USB ChipCardInterfaceDevices driver + * 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * + * ALTERNATIVELY, this file may be distributed under the terms of the + * following license, in which case the provisions of this license are + * required INSTEAD OF the GNU General Public License. If you wish to + * allow use of your version of this file only under the terms of the + * GNU General Public License, and not to allow others to use your + * version of this file under the terms of the following license, + * indicate your decision by deleting this paragraph and the license + * below. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id$ + */ + +#ifndef CCID_DRIVER_H +#define CCID_DRIVER_H + + +#ifdef CCID_DRIVER_INCLUDE_USB_IDS +/* We need to know the vendor to do some hacks. */ +enum { + VENDOR_CHERRY = 0x046a, + VENDOR_SCM = 0x04e6, + VENDOR_OMNIKEY= 0x076b, + VENDOR_GEMPC = 0x08e6, + VENDOR_VEGA = 0x0982, + VENDOR_REINER = 0x0c4b, + VENDOR_KAAN = 0x0d46, + VENDOR_FSIJ = 0x234b, + VENDOR_VASCO = 0x1a44, + VENDOR_NXP = 0x1fc9, +}; + + +/* Some product ids. */ +#define SCM_SCR331 0xe001 +#define SCM_SCR331DI 0x5111 +#define SCM_SCR335 0x5115 +#define SCM_SCR3320 0x5117 +#define SCM_SPR532 0xe003 /* Also used succeeding model SPR332. */ +#define CHERRY_ST2000 0x003e +#define VASCO_920 0x0920 +#define GEMPC_PINPAD 0x3478 +#define GEMPC_CT30 0x3437 +#define GEMPC_EZIO 0x34c2 /* (!=34c0) Also known as IDBridge CT710 */ +#define VEGA_ALPHA 0x0008 +#define CYBERJACK_GO 0x0504 +#define CRYPTOUCAN 0x81e6 + +#endif /*CCID_DRIVER_INCLUDE_USB_IDS*/ + + +/* The CID driver returns the same error codes as the status words + used by GnuPG's apdu.h. For ease of maintenance they should always + match. */ +#define CCID_DRIVER_ERR_OUT_OF_CORE 0x10001 +#define CCID_DRIVER_ERR_INV_VALUE 0x10002 +#define CCID_DRIVER_ERR_INCOMPLETE_CARD_RESPONSE = 0x10003 +#define CCID_DRIVER_ERR_NO_DRIVER 0x10004 +#define CCID_DRIVER_ERR_NOT_SUPPORTED 0x10005 +#define CCID_DRIVER_ERR_LOCKING_FAILED 0x10006 +#define CCID_DRIVER_ERR_BUSY 0x10007 +#define CCID_DRIVER_ERR_NO_CARD 0x10008 +#define CCID_DRIVER_ERR_CARD_INACTIVE 0x10009 +#define CCID_DRIVER_ERR_CARD_IO_ERROR 0x1000a +#define CCID_DRIVER_ERR_GENERAL_ERROR 0x1000b +#define CCID_DRIVER_ERR_NO_READER 0x1000c +#define CCID_DRIVER_ERR_ABORTED 0x1000d +#define CCID_DRIVER_ERR_NO_PINPAD 0x1000e +#define CCID_DRIVER_ERR_USB_OTHER 0x10020 +#define CCID_DRIVER_ERR_USB_IO 0x10021 +#define CCID_DRIVER_ERR_USB_ACCESS 0x10023 +#define CCID_DRIVER_ERR_USB_NO_DEVICE 0x10024 +#define CCID_DRIVER_ERR_USB_BUSY 0x10026 +#define CCID_DRIVER_ERR_USB_TIMEOUT 0x10027 +#define CCID_DRIVER_ERR_USB_OVERFLOW 0x10028 + +struct ccid_driver_s; +typedef struct ccid_driver_s *ccid_driver_t; + +struct ccid_dev_table; + +int ccid_set_debug_level (int level); +char *ccid_get_reader_list (void); + +gpg_error_t ccid_dev_scan (int *idx_max, void **t_p); +void ccid_dev_scan_finish (void *tbl0, int max); +unsigned int ccid_get_BAI (int, void *tbl0); +int ccid_compare_BAI (ccid_driver_t handle, unsigned int); +int ccid_open_reader (const char *spec_reader_name, + int idx, void *ccid_table0, + ccid_driver_t *handle, char **rdrname_p); +int ccid_set_progress_cb (ccid_driver_t handle, + void (*cb)(void *, const char *, int, int, int), + void *cb_arg); +int ccid_set_prompt_cb (ccid_driver_t handle, void (*cb)(void *, int), + void *cb_arg); +int ccid_shutdown_reader (ccid_driver_t handle); +int ccid_close_reader (ccid_driver_t handle); +int ccid_get_atr (ccid_driver_t handle, + unsigned char *atr, size_t maxatrlen, size_t *atrlen); +int ccid_slot_status (ccid_driver_t handle, int *statusbits, int on_wire); +int ccid_transceive (ccid_driver_t handle, + const unsigned char *apdu, size_t apdulen, + unsigned char *resp, size_t maxresplen, size_t *nresp); +int ccid_transceive_secure (ccid_driver_t handle, + const unsigned char *apdu, size_t apdulen, + pininfo_t *pininfo, + unsigned char *resp, size_t maxresplen, size_t *nresp); +int ccid_transceive_escape (ccid_driver_t handle, + const unsigned char *data, size_t datalen, + unsigned char *resp, size_t maxresplen, + size_t *nresp); +int ccid_require_get_status (ccid_driver_t handle); + + +#endif /*CCID_DRIVER_H*/ diff --git a/scd/command.c b/scd/command.c new file mode 100644 index 0000000..925fd75 --- /dev/null +++ b/scd/command.c @@ -0,0 +1,2130 @@ +/* command.c - SCdaemon command handler + * Copyright (C) 2001, 2002, 2003, 2004, 2005, + * 2007, 2008, 2009, 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <signal.h> +#ifdef USE_NPTH +# include <npth.h> +#endif + +#include "scdaemon.h" +#include <assuan.h> +#include <ksba.h> +#include "iso7816.h" +#include "apdu.h" /* Required for apdu_*_reader (). */ +#include "atr.h" +#include "../common/asshelp.h" +#include "../common/server-help.h" + +/* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN */ +#define MAXLEN_PIN 100 + +/* Maximum allowed size of key data as used in inquiries. */ +#define MAXLEN_KEYDATA 4096 + +/* Maximum allowed total data size for SETDATA. */ +#define MAXLEN_SETDATA 4096 + +/* Maximum allowed size of certificate data as used in inquiries. */ +#define MAXLEN_CERTDATA 16384 + + +#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) + +#define IS_LOCKED(c) (locked_session && locked_session != (c)->server_local) + + +/* Data used to associate an Assuan context with local server data. + This object describes the local properties of one session. */ +struct server_local_s +{ + /* We keep a list of all active sessions with the anchor at + SESSION_LIST (see below). This field is used for linking. */ + struct server_local_s *next_session; + + /* This object is usually assigned to a CTRL object (which is + globally visible). While enumerating all sessions we sometimes + need to access data of the CTRL object; thus we keep a + backpointer here. */ + ctrl_t ctrl_backlink; + + /* The Assuan context used by this session/server. */ + assuan_context_t assuan_ctx; + +#ifdef HAVE_W32_SYSTEM + void *event_signal; /* Or NULL if not used. */ +#else + int event_signal; /* Or 0 if not used. */ +#endif + + /* True if the card has been removed and a reset is required to + continue operation. */ + int card_removed; + + /* If set to true we will be terminate ourself at the end of the + this session. */ + int stopme; + +}; + + +/* To keep track of all running sessions, we link all active server + contexts and the anchor in this variable. */ +static struct server_local_s *session_list; + +/* If a session has been locked we store a link to its server object + in this variable. */ +static struct server_local_s *locked_session; + + +/* Convert the STRING into a newly allocated buffer while translating + the hex numbers. Stops at the first invalid character. Blanks and + colons are allowed to separate the hex digits. Returns NULL on + error or a newly malloced buffer and its length in LENGTH. */ +static unsigned char * +hex_to_buffer (const char *string, size_t *r_length) +{ + unsigned char *buffer; + const char *s; + size_t n; + + buffer = xtrymalloc (strlen (string)+1); + if (!buffer) + return NULL; + for (s=string, n=0; *s; s++) + { + if (spacep (s) || *s == ':') + continue; + if (hexdigitp (s) && hexdigitp (s+1)) + { + buffer[n++] = xtoi_2 (s); + s++; + } + else + break; + } + *r_length = n; + return buffer; +} + + + +/* Reset the card and free the application context. With SEND_RESET + set to true actually send a RESET to the reader; this is the normal + way of calling the function. If KEEP_LOCK is set and the session + is locked that lock wil not be released. */ +static void +do_reset (ctrl_t ctrl, int send_reset, int keep_lock) +{ + app_t app = ctrl->app_ctx; + + if (app) + app_reset (app, ctrl, IS_LOCKED (ctrl)? 0: send_reset); + + /* If we hold a lock, unlock now. */ + if (!keep_lock && locked_session && ctrl->server_local == locked_session) + { + locked_session = NULL; + log_info ("implicitly unlocking due to RESET\n"); + } +} + +static gpg_error_t +reset_notify (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + do_reset (ctrl, 1, has_option (line, "--keep-lock")); + return 0; +} + + +static gpg_error_t +option_handler (assuan_context_t ctx, const char *key, const char *value) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + if (!strcmp (key, "event-signal")) + { + /* A value of 0 is allowed to reset the event signal. */ +#ifdef HAVE_W32_SYSTEM + if (!*value) + return gpg_error (GPG_ERR_ASS_PARAMETER); +#ifdef _WIN64 + ctrl->server_local->event_signal = (void *)strtoull (value, NULL, 16); +#else + ctrl->server_local->event_signal = (void *)strtoul (value, NULL, 16); +#endif +#else + int i = *value? atoi (value) : -1; + if (i < 0) + return gpg_error (GPG_ERR_ASS_PARAMETER); + ctrl->server_local->event_signal = i; +#endif + } + + return 0; +} + + +/* If the card has not yet been opened, do it. */ +static gpg_error_t +open_card (ctrl_t ctrl) +{ + /* If we ever got a card not present error code, return that. Only + the SERIALNO command and a reset are able to clear from that + state. */ + if (ctrl->server_local->card_removed) + return gpg_error (GPG_ERR_CARD_REMOVED); + + if ( IS_LOCKED (ctrl) ) + return gpg_error (GPG_ERR_LOCKED); + + if (ctrl->app_ctx) + return 0; + + return select_application (ctrl, NULL, &ctrl->app_ctx, 0, NULL, 0); +} + +/* Explicitly open a card for a specific use of APPTYPE or SERIALNO. */ +static gpg_error_t +open_card_with_request (ctrl_t ctrl, const char *apptype, const char *serialno) +{ + gpg_error_t err; + unsigned char *serialno_bin = NULL; + size_t serialno_bin_len = 0; + app_t app = ctrl->app_ctx; + + /* If we are already initialized for one specific application we + need to check that the client didn't requested a specific + application different from the one in use before we continue. */ + if (apptype && ctrl->app_ctx) + return check_application_conflict (apptype, ctrl->app_ctx); + + /* Re-scan USB devices. Release APP, before the scan. */ + ctrl->app_ctx = NULL; + release_application (app, 0); + + if (serialno) + serialno_bin = hex_to_buffer (serialno, &serialno_bin_len); + + err = select_application (ctrl, apptype, &ctrl->app_ctx, 1, + serialno_bin, serialno_bin_len); + xfree (serialno_bin); + + return err; +} + + +static const char hlp_serialno[] = + "SERIALNO [--demand=<serialno>] [<apptype>]\n" + "\n" + "Return the serial number of the card using a status response. This\n" + "function should be used to check for the presence of a card.\n" + "\n" + "If --demand is given, an application on the card with SERIALNO is\n" + "selected and an error is returned if no such card available.\n" + "\n" + "If APPTYPE is given, an application of that type is selected and an\n" + "error is returned if the application is not supported or available.\n" + "The default is to auto-select the application using a hardwired\n" + "preference system. Note, that a future extension to this function\n" + "may enable specifying a list and order of applications to try.\n" + "\n" + "This function is special in that it can be used to reset the card.\n" + "Most other functions will return an error when a card change has\n" + "been detected and the use of this function is therefore required.\n" + "\n" + "Background: We want to keep the client clear of handling card\n" + "changes between operations; i.e. the client can assume that all\n" + "operations are done on the same card unless he calls this function."; +static gpg_error_t +cmd_serialno (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + struct server_local_s *sl; + int rc = 0; + char *serial; + const char *demand; + + if ( IS_LOCKED (ctrl) ) + return gpg_error (GPG_ERR_LOCKED); + + if ((demand = has_option_name (line, "--demand"))) + { + if (*demand != '=') + return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); + line = (char *)++demand; + for (; *line && !spacep (line); line++) + ; + if (*line) + *line++ = 0; + } + else + demand = NULL; + + line = skip_options (line); + + /* Clear the remove flag so that the open_card is able to reread it. */ + if (ctrl->server_local->card_removed) + ctrl->server_local->card_removed = 0; + + if ((rc = open_card_with_request (ctrl, *line? line:NULL, demand))) + { + ctrl->server_local->card_removed = 1; + return rc; + } + + /* Success, clear the card_removed flag for all sessions. */ + for (sl=session_list; sl; sl = sl->next_session) + { + ctrl_t c = sl->ctrl_backlink; + + if (c != ctrl) + c->server_local->card_removed = 0; + } + + serial = app_get_serialno (ctrl->app_ctx); + if (!serial) + return gpg_error (GPG_ERR_INV_VALUE); + + rc = assuan_write_status (ctx, "SERIALNO", serial); + xfree (serial); + return rc; +} + + +static const char hlp_learn[] = + "LEARN [--force] [--keypairinfo]\n" + "\n" + "Learn all useful information of the currently inserted card. When\n" + "used without the force options, the command might do an INQUIRE\n" + "like this:\n" + "\n" + " INQUIRE KNOWNCARDP <hexstring_with_serialNumber>\n" + "\n" + "The client should just send an \"END\" if the processing should go on\n" + "or a \"CANCEL\" to force the function to terminate with a Cancel\n" + "error message.\n" + "\n" + "With the option --keypairinfo only KEYPARIINFO status lines are\n" + "returned.\n" + "\n" + "The response of this command is a list of status lines formatted as\n" + "this:\n" + "\n" + " S APPTYPE <apptype>\n" + "\n" + "This returns the type of the application, currently the strings:\n" + "\n" + " P15 = PKCS-15 structure used\n" + " DINSIG = DIN SIG\n" + " OPENPGP = OpenPGP card\n" + " PIV = PIV card\n" + " NKS = NetKey card\n" + "\n" + "are implemented. These strings are aliases for the AID\n" + "\n" + " S KEYPAIRINFO <hexstring_with_keygrip> <hexstring_with_id>\n" + "\n" + "If there is no certificate yet stored on the card a single 'X' is\n" + "returned as the keygrip. In addition to the keypair info, information\n" + "about all certificates stored on the card is also returned:\n" + "\n" + " S CERTINFO <certtype> <hexstring_with_id>\n" + "\n" + "Where CERTTYPE is a number indicating the type of certificate:\n" + " 0 := Unknown\n" + " 100 := Regular X.509 cert\n" + " 101 := Trusted X.509 cert\n" + " 102 := Useful X.509 cert\n" + " 110 := Root CA cert in a special format (e.g. DINSIG)\n" + " 111 := Root CA cert as standard X509 cert.\n" + "\n" + "For certain cards, more information will be returned:\n" + "\n" + " S KEY-FPR <no> <hexstring>\n" + "\n" + "For OpenPGP cards this returns the stored fingerprints of the\n" + "keys. This can be used check whether a key is available on the\n" + "card. NO may be 1, 2 or 3.\n" + "\n" + " S CA-FPR <no> <hexstring>\n" + "\n" + "Similar to above, these are the fingerprints of keys assumed to be\n" + "ultimately trusted.\n" + "\n" + " S DISP-NAME <name_of_card_holder>\n" + "\n" + "The name of the card holder as stored on the card; percent\n" + "escaping takes place, spaces are encoded as '+'\n" + "\n" + " S PUBKEY-URL <url>\n" + "\n" + "The URL to be used for locating the entire public key.\n" + " \n" + "Note, that this function may even be used on a locked card."; +static gpg_error_t +cmd_learn (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc = 0; + int only_keypairinfo = has_option (line, "--keypairinfo"); + + 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 */ + if (!only_keypairinfo) + { + const char *reader; + char *serial; + app_t app = ctrl->app_ctx; + + if (!app) + return gpg_error (GPG_ERR_CARD_NOT_PRESENT); + + reader = apdu_get_reader_name (app->slot); + if (!reader) + return out_of_core (); + send_status_direct (ctrl, "READER", reader); + /* No need to free the string of READER. */ + + serial = app_get_serialno (ctrl->app_ctx); + if (!serial) + return gpg_error (GPG_ERR_INV_VALUE); + + rc = assuan_write_status (ctx, "SERIALNO", serial); + if (rc < 0) + { + xfree (serial); + return out_of_core (); + } + + if (!has_option (line, "--force")) + { + char *command; + + rc = gpgrt_asprintf (&command, "KNOWNCARDP %s", serial); + if (rc < 0) + { + xfree (serial); + return out_of_core (); + } + rc = assuan_inquire (ctx, command, NULL, NULL, 0); + xfree (command); + if (rc) + { + if (gpg_err_code (rc) != GPG_ERR_ASS_CANCELED) + log_error ("inquire KNOWNCARDP failed: %s\n", + gpg_strerror (rc)); + xfree (serial); + return rc; + } + /* Not canceled, so we have to proceed. */ + } + xfree (serial); + } + + /* Let the application print out its collection of useful status + information. */ + if (!rc) + rc = app_write_learn_status (ctrl->app_ctx, ctrl, only_keypairinfo); + + return rc; +} + + + +static const char hlp_readcert[] = + "READCERT <hexified_certid>|<keyid>\n" + "\n" + "Note, that this function may even be used on a locked card."; +static gpg_error_t +cmd_readcert (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *cert; + size_t ncert; + + if ((rc = open_card (ctrl))) + return rc; + + line = xstrdup (line); /* Need a copy of the line. */ + rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert); + if (rc) + log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); + xfree (line); + line = NULL; + if (!rc) + { + rc = assuan_send_data (ctx, cert, ncert); + xfree (cert); + if (rc) + return rc; + } + + return rc; +} + + +static const char hlp_readkey[] = + "READKEY [--advanced] [--info[-only]] <keyid>\n" + "\n" + "Return the public key for the given cert or key ID as a standard\n" + "S-expression. With --advanced the S-expression is returned in\n" + "advanced format. With --info a KEYPAIRINFO status line is also\n" + "emitted; with --info-only the regular output is suppressed."; +static gpg_error_t +cmd_readkey (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + int advanced = 0; + int opt_info = 0; + int opt_nokey = 0; + unsigned char *cert = NULL; + size_t ncert; + unsigned char *pk; + size_t pklen; + int direct_readkey = 0; + + if ((rc = open_card (ctrl))) + return rc; + + if (has_option (line, "--advanced")) + advanced = 1; + if (has_option (line, "--info")) + opt_info = 1; + if (has_option (line, "--info-only")) + opt_info = opt_nokey = 1; + + line = skip_options (line); + line = xstrdup (line); /* Need a copy of the line. */ + + /* If the application supports the READKEY function we use that. + Otherwise we use the old way by extracting it from the + certificate. */ + rc = app_readkey (ctrl->app_ctx, ctrl, advanced, line, &pk, &pklen); + if (!rc) + direct_readkey = 1; /* Yeah, got that key - send it back. */ + else if (gpg_err_code (rc) == GPG_ERR_UNSUPPORTED_OPERATION + || gpg_err_code (rc) == GPG_ERR_NOT_FOUND) + { + /* Fall back to certificate reading. */ + rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert); + if (rc) + log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); + else + { + rc = app_help_pubkey_from_cert (cert, ncert, &pk, &pklen); + if (rc) + log_error ("failed to parse the certificate: %s\n", + gpg_strerror (rc)); + } + } + else + log_error ("app_readkey failed: %s\n", gpg_strerror (rc)); + + if (!rc && pk && pklen && opt_info && !direct_readkey) + { + char keygripstr[KEYGRIP_LEN*2+1]; + char *algostr; + + rc = app_help_get_keygrip_string_pk (pk, pklen, + keygripstr, NULL, NULL, + &algostr); + if (rc) + { + log_error ("app_help_get_keygrip_string failed: %s\n", + gpg_strerror (rc)); + goto leave; + } + + /* FIXME: Using LINE is not correct because it might be an + * OID and has not been canonicalized (i.e. uppercased). */ + send_status_info (ctrl, "KEYPAIRINFO", + keygripstr, strlen (keygripstr), + line, strlen (line), + "-", (size_t)1, + "-", (size_t)1, + algostr, strlen (algostr), + NULL, (size_t)0); + xfree (algostr); + } + + + if (!rc && pk && pklen && !opt_nokey) + rc = assuan_send_data (ctx, pk, pklen); + + leave: + xfree (cert); + xfree (pk); + xfree (line); + return rc; +} + + + +static const char hlp_setdata[] = + "SETDATA [--append] <hexstring>\n" + "\n" + "The client should use this command to tell us the data he want to sign.\n" + "With the option --append, the data is appended to the data set by a\n" + "previous SETDATA command."; +static gpg_error_t +cmd_setdata (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int append; + int n, i, off; + char *p; + unsigned char *buf; + + append = (ctrl->in_data.value && has_option (line, "--append")); + + line = skip_options (line); + + if (locked_session && locked_session != ctrl->server_local) + return gpg_error (GPG_ERR_LOCKED); + + /* Parse the hexstring. */ + for (p=line,n=0; hexdigitp (p); p++, n++) + ; + if (*p) + return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring"); + if (!n) + return set_error (GPG_ERR_ASS_PARAMETER, "no data given"); + if ((n&1)) + return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits"); + n /= 2; + if (append) + { + if (ctrl->in_data.valuelen + n > MAXLEN_SETDATA) + return set_error (GPG_ERR_TOO_LARGE, + "limit on total size of data reached"); + buf = xtrymalloc (ctrl->in_data.valuelen + n); + } + else + buf = xtrymalloc (n); + if (!buf) + return out_of_core (); + + if (append) + { + memcpy (buf, ctrl->in_data.value, ctrl->in_data.valuelen); + off = ctrl->in_data.valuelen; + } + else + off = 0; + for (p=line, i=0; i < n; p += 2, i++) + buf[off+i] = xtoi_2 (p); + + xfree (ctrl->in_data.value); + ctrl->in_data.value = buf; + ctrl->in_data.valuelen = off+n; + return 0; +} + + + +static gpg_error_t +pin_cb (void *opaque, const char *info, char **retstr) +{ + assuan_context_t ctx = opaque; + char *command; + int rc; + unsigned char *value; + size_t valuelen; + + if (!retstr) + { + /* We prompt for pinpad entry. To make sure that the popup has + been show we use an inquire and not just a status message. + We ignore any value returned. */ + if (info) + { + log_debug ("prompting for pinpad entry '%s'\n", info); + rc = gpgrt_asprintf (&command, "POPUPPINPADPROMPT %s", info); + if (rc < 0) + return gpg_error (gpg_err_code_from_errno (errno)); + rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); + xfree (command); + } + else + { + log_debug ("dismiss pinpad entry prompt\n"); + rc = assuan_inquire (ctx, "DISMISSPINPADPROMPT", + &value, &valuelen, MAXLEN_PIN); + } + if (!rc) + xfree (value); + return rc; + } + + *retstr = NULL; + log_debug ("asking for PIN '%s'\n", info); + + rc = gpgrt_asprintf (&command, "NEEDPIN %s", info); + if (rc < 0) + return gpg_error (gpg_err_code_from_errno (errno)); + + /* Fixme: Write an inquire function which returns the result in + secure memory and check all further handling of the PIN. */ + assuan_begin_confidential (ctx); + rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); + assuan_end_confidential (ctx); + xfree (command); + if (rc) + return 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 = (char*)value; + return 0; +} + + +static const char hlp_pksign[] = + "PKSIGN [--hash=[rmd160|sha{1,224,256,384,512}|md5]] <hexified_id>\n" + "\n" + "The --hash option is optional; the default is SHA1."; +static gpg_error_t +cmd_pksign (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *outdata; + size_t outdatalen; + char *keyidstr; + int hash_algo; + + if (has_option (line, "--hash=rmd160")) + hash_algo = GCRY_MD_RMD160; + else if (has_option (line, "--hash=sha1")) + hash_algo = GCRY_MD_SHA1; + else if (has_option (line, "--hash=sha224")) + hash_algo = GCRY_MD_SHA224; + else if (has_option (line, "--hash=sha256")) + hash_algo = GCRY_MD_SHA256; + else if (has_option (line, "--hash=sha384")) + hash_algo = GCRY_MD_SHA384; + else if (has_option (line, "--hash=sha512")) + hash_algo = GCRY_MD_SHA512; + else if (has_option (line, "--hash=md5")) + hash_algo = GCRY_MD_MD5; + else if (!strstr (line, "--")) + hash_algo = GCRY_MD_SHA1; + else + return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm"); + + line = skip_options (line); + + 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 = xtrystrdup (line); + if (!keyidstr) + return out_of_core (); + + rc = app_sign (ctrl->app_ctx, ctrl, + keyidstr, hash_algo, + pin_cb, ctx, + ctrl->in_data.value, ctrl->in_data.valuelen, + &outdata, &outdatalen); + + xfree (keyidstr); + if (rc) + { + log_error ("app_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 rc; +} + + +static const char hlp_pkauth[] = + "PKAUTH <hexified_id>"; +static gpg_error_t +cmd_pkauth (assuan_context_t ctx, char *line) +{ + ctrl_t 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 = xtrystrdup (line); + if (!keyidstr) + return out_of_core (); + + rc = app_auth (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx, + ctrl->in_data.value, ctrl->in_data.valuelen, + &outdata, &outdatalen); + xfree (keyidstr); + if (rc) + { + log_error ("app_auth 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 rc; +} + + +static const char hlp_pkdecrypt[] = + "PKDECRYPT <hexified_id>"; +static gpg_error_t +cmd_pkdecrypt (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *outdata; + size_t outdatalen; + char *keyidstr; + unsigned int infoflags; + + if ((rc = open_card (ctrl))) + return rc; + + keyidstr = xtrystrdup (line); + if (!keyidstr) + return out_of_core (); + rc = app_decipher (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx, + ctrl->in_data.value, ctrl->in_data.valuelen, + &outdata, &outdatalen, &infoflags); + + xfree (keyidstr); + if (rc) + { + log_error ("app_decipher failed: %s\n", gpg_strerror (rc)); + } + else + { + /* If the card driver told us that there is no padding, send a + status line. If there is a padding it is assumed that the + caller knows what padding is used. It would have been better + to always send that information but for backward + compatibility we can't do that. */ + if ((infoflags & APP_DECIPHER_INFO_NOPAD)) + send_status_direct (ctrl, "PADDING", "0"); + rc = assuan_send_data (ctx, outdata, outdatalen); + xfree (outdata); + if (rc) + return rc; /* that is already an assuan error code */ + } + + return rc; +} + + +static const char hlp_getattr[] = + "GETATTR <name>\n" + "\n" + "This command is used to retrieve data from a smartcard. The\n" + "allowed names depend on the currently selected smartcard\n" + "application. NAME must be percent and '+' escaped. The value is\n" + "returned through status message, see the LEARN command for details.\n" + "\n" + "However, the current implementation assumes that Name is not escaped;\n" + "this works as long as no one uses arbitrary escaping. \n" + "\n" + "Note, that this function may even be used on a locked card."; +static gpg_error_t +cmd_getattr (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + const char *keyword; + + if ((rc = open_card (ctrl))) + return rc; + + keyword = line; + for (; *line && !spacep (line); line++) + ; + if (*line) + *line++ = 0; + + /* (We ignore any garbage for now.) */ + + /* FIXME: Applications should not return sensitive data if the card + is locked. */ + rc = app_getattr (ctrl->app_ctx, ctrl, keyword); + + return rc; +} + + +static const char hlp_setattr[] = + "SETATTR <name> <value> \n" + "\n" + "This command is used to store data on a smartcard. The allowed\n" + "names and values are depend on the currently selected smartcard\n" + "application. NAME and VALUE must be percent and '+' escaped.\n" + "\n" + "However, the current implementation assumes that NAME is not\n" + "escaped; this works as long as no one uses arbitrary escaping.\n" + "\n" + "A PIN will be requested for most NAMEs. See the corresponding\n" + "setattr function of the actually used application (app-*.c) for\n" + "details."; +static gpg_error_t +cmd_setattr (assuan_context_t ctx, char *orig_line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + char *keyword; + int keywordlen; + size_t nbytes; + char *line, *linebuf; + + if ((rc = open_card (ctrl))) + return rc; + + /* We need to use a copy of LINE, because PIN_CB uses the same + context and thus reuses the Assuan provided LINE. */ + line = linebuf = xtrystrdup (orig_line); + if (!line) + return out_of_core (); + + keyword = line; + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + if (*line) + *line++ = 0; + while (spacep (line)) + line++; + nbytes = percent_plus_unescape_inplace (line, 0); + + rc = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, + (const unsigned char*)line, nbytes); + xfree (linebuf); + + return rc; +} + + +static const char hlp_writecert[] = + "WRITECERT <hexified_certid>\n" + "\n" + "This command is used to store a certifciate on a smartcard. The\n" + "allowed certids depend on the currently selected smartcard\n" + "application. The actual certifciate is requested using the inquiry\n" + "\"CERTDATA\" and needs to be provided in its raw (e.g. DER) form.\n" + "\n" + "In almost all cases a PIN will be requested. See the related\n" + "writecert function of the actually used application (app-*.c) for\n" + "details."; +static gpg_error_t +cmd_writecert (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + char *certid; + unsigned char *certdata; + size_t certdatalen; + + line = skip_options (line); + + if (!*line) + return set_error (GPG_ERR_ASS_PARAMETER, "no certid given"); + certid = line; + while (*line && !spacep (line)) + line++; + *line = 0; + + if ((rc = open_card (ctrl))) + return rc; + + if (!ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + certid = xtrystrdup (certid); + if (!certid) + return out_of_core (); + + /* Now get the actual keydata. */ + rc = assuan_inquire (ctx, "CERTDATA", + &certdata, &certdatalen, MAXLEN_CERTDATA); + if (rc) + { + xfree (certid); + return rc; + } + + /* Write the certificate to the card. */ + rc = app_writecert (ctrl->app_ctx, ctrl, certid, + pin_cb, ctx, certdata, certdatalen); + xfree (certid); + xfree (certdata); + + return rc; +} + + +static const char hlp_writekey[] = + "WRITEKEY [--force] <keyid> \n" + "\n" + "This command is used to store a secret key on a smartcard. The\n" + "allowed keyids depend on the currently selected smartcard\n" + "application. The actual keydata is requested using the inquiry\n" + "\"KEYDATA\" and need to be provided without any protection. With\n" + "--force set an existing key under this KEYID will get overwritten.\n" + "The keydata is expected to be the usual canonical encoded\n" + "S-expression.\n" + "\n" + "A PIN will be requested for most NAMEs. See the corresponding\n" + "writekey function of the actually used application (app-*.c) for\n" + "details."; +static gpg_error_t +cmd_writekey (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + char *keyid; + int force = has_option (line, "--force"); + unsigned char *keydata; + size_t keydatalen; + + line = skip_options (line); + + if (!*line) + return set_error (GPG_ERR_ASS_PARAMETER, "no keyid given"); + keyid = line; + while (*line && !spacep (line)) + line++; + *line = 0; + + if ((rc = open_card (ctrl))) + return rc; + + if (!ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + keyid = xtrystrdup (keyid); + if (!keyid) + return out_of_core (); + + /* Now get the actual keydata. */ + assuan_begin_confidential (ctx); + rc = assuan_inquire (ctx, "KEYDATA", &keydata, &keydatalen, MAXLEN_KEYDATA); + assuan_end_confidential (ctx); + if (rc) + { + xfree (keyid); + return rc; + } + + /* Write the key to the card. */ + rc = app_writekey (ctrl->app_ctx, ctrl, keyid, force? 1:0, + pin_cb, ctx, keydata, keydatalen); + xfree (keyid); + xfree (keydata); + + return rc; +} + + +static const char hlp_genkey[] = + "GENKEY [--force] [--timestamp=<isodate>] <no>\n" + "\n" + "Generate a key on-card identified by NO, which is application\n" + "specific. Return values are application specific. For OpenPGP\n" + "cards 3 status lines are returned:\n" + "\n" + " S KEY-FPR <hexstring>\n" + " S KEY-CREATED-AT <seconds_since_epoch>\n" + " S KEY-DATA [-|p|n] <hexdata>\n" + "\n" + " 'p' and 'n' are the names of the RSA parameters; '-' is used to\n" + " indicate that HEXDATA is the first chunk of a parameter given\n" + " by the next KEY-DATA.\n" + "\n" + "--force is required to overwrite an already existing key. The\n" + "KEY-CREATED-AT is required for further processing because it is\n" + "part of the hashed key material for the fingerprint.\n" + "\n" + "If --timestamp is given an OpenPGP key will be created using this\n" + "value. The value needs to be in ISO Format; e.g.\n" + "\"--timestamp=20030316T120000\" and after 1970-01-01 00:00:00.\n" + "\n" + "The public part of the key can also later be retrieved using the\n" + "READKEY command."; +static gpg_error_t +cmd_genkey (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + char *save_line; + int force; + const char *s; + time_t timestamp; + + force = has_option (line, "--force"); + + if ((s=has_option_name (line, "--timestamp"))) + { + if (*s != '=') + return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); + timestamp = isotime2epoch (s+1); + if (timestamp < 1) + return set_error (GPG_ERR_ASS_PARAMETER, "invalid time value"); + } + else + timestamp = 0; + + + line = skip_options (line); + if (!*line) + { + rc = set_error (GPG_ERR_ASS_PARAMETER, "no key number given"); + goto leave; + } + save_line = line; + while (*line && !spacep (line)) + line++; + *line = 0; + + if ((rc = open_card (ctrl))) + goto leave; + + if (!ctrl->app_ctx) + { + rc = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + goto leave; + } + + { + char *tmp = xtrystrdup (save_line); + if (!tmp) + return gpg_error_from_syserror (); + rc = app_genkey (ctrl->app_ctx, ctrl, tmp, NULL, + force? APP_GENKEY_FLAG_FORCE : 0, + timestamp, pin_cb, ctx); + xfree (tmp); + } + + leave: + return rc; +} + + +static const char hlp_random[] = + "RANDOM <nbytes>\n" + "\n" + "Get NBYTES of random from the card and send them back as data.\n" + "This usually involves EEPROM write on the card and thus excessive\n" + "use of this command may destroy the card.\n" + "\n" + "Note, that this function may be even be used on a locked card."; +static gpg_error_t +cmd_random (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + size_t nbytes; + unsigned char *buffer; + + if (!*line) + return set_error (GPG_ERR_ASS_PARAMETER, + "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 out_of_core (); + + rc = app_get_challenge (ctrl->app_ctx, ctrl, 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 rc; +} + + + +static const char hlp_passwd[] = + "PASSWD [--reset] [--nullpin] [--clear] <chvno>\n" + "\n" + "Change the PIN or, if --reset is given, reset the retry counter of\n" + "the card holder verification vector CHVNO. The option --nullpin is\n" + "used for TCOS cards to set the initial PIN. The option --clear clears\n" + "the security status associated with the PIN so that the PIN needs to\n" + "be presented again. The format of CHVNO depends on the card application."; +static gpg_error_t +cmd_passwd (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + char *chvnostr; + unsigned int flags = 0; + + if (has_option (line, "--reset")) + flags |= APP_CHANGE_FLAG_RESET; + if (has_option (line, "--nullpin")) + flags |= APP_CHANGE_FLAG_NULLPIN; + if (has_option (line, "--clear")) + flags |= APP_CHANGE_FLAG_CLEAR; + + line = skip_options (line); + + if (!*line) + return set_error (GPG_ERR_ASS_PARAMETER, "no CHV number given"); + chvnostr = line; + while (*line && !spacep (line)) + line++; + *line = 0; + + /* Do not allow other flags aside of --clear. */ + if ((flags & APP_CHANGE_FLAG_CLEAR) && (flags & ~APP_CHANGE_FLAG_CLEAR)) + return set_error (GPG_ERR_UNSUPPORTED_OPERATION, + "--clear used with other options"); + + if ((rc = open_card (ctrl))) + return rc; + + if (!ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + chvnostr = xtrystrdup (chvnostr); + if (!chvnostr) + return out_of_core (); + rc = app_change_pin (ctrl->app_ctx, ctrl, chvnostr, flags, pin_cb, ctx); + if (rc) + log_error ("command passwd failed: %s\n", gpg_strerror (rc)); + xfree (chvnostr); + + return rc; +} + + +static const char hlp_checkpin[] = + "CHECKPIN <idstr>\n" + "\n" + "Perform a VERIFY operation without doing anything else. This may\n" + "be used to initialize a the PIN cache earlier to long lasting\n" + "operations. Its use is highly application dependent.\n" + "\n" + "For OpenPGP:\n" + "\n" + " Perform a simple verify operation for CHV1 and CHV2, so that\n" + " further operations won't ask for CHV2 and it is possible to do a\n" + " cheap check on the PIN: If there is something wrong with the PIN\n" + " entry system, only the regular CHV will get blocked and not the\n" + " dangerous CHV3. IDSTR is the usual card's serial number in hex\n" + " notation; an optional fingerprint part will get ignored. There\n" + " is however a special mode if the IDSTR is suffixed with the\n" + " literal string \"[CHV3]\": In this case the Admin PIN is checked\n" + " if and only if the retry counter is still at 3.\n" + "\n" + "For Netkey:\n" + "\n" + " Any of the valid PIN Ids may be used. These are the strings:\n" + "\n" + " PW1.CH - Global password 1\n" + " PW2.CH - Global password 2\n" + " PW1.CH.SIG - SigG password 1\n" + " PW2.CH.SIG - SigG password 2\n" + "\n" + " For a definitive list, see the implementation in app-nks.c.\n" + " Note that we call a PW2.* PIN a \"PUK\" despite that since TCOS\n" + " 3.0 they are technically alternative PINs used to mutally\n" + " unblock each other."; +static gpg_error_t +cmd_checkpin (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + char *idstr; + + 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. */ + idstr = xtrystrdup (line); + if (!idstr) + return out_of_core (); + + rc = app_check_pin (ctrl->app_ctx, ctrl, idstr, pin_cb, ctx); + xfree (idstr); + if (rc) + log_error ("app_check_pin failed: %s\n", gpg_strerror (rc)); + + return rc; +} + + +static const char hlp_lock[] = + "LOCK [--wait]\n" + "\n" + "Grant exclusive card access to this session. Note that there is\n" + "no lock counter used and a second lock from the same session will\n" + "be ignored. A single unlock (or RESET) unlocks the session.\n" + "Return GPG_ERR_LOCKED if another session has locked the reader.\n" + "\n" + "If the option --wait is given the command will wait until a\n" + "lock has been released."; +static gpg_error_t +cmd_lock (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc = 0; + + retry: + if (locked_session) + { + if (locked_session != ctrl->server_local) + rc = gpg_error (GPG_ERR_LOCKED); + } + else + locked_session = ctrl->server_local; + +#ifdef USE_NPTH + if (rc && has_option (line, "--wait")) + { + rc = 0; + npth_sleep (1); /* Better implement an event mechanism. However, + for card operations this should be + sufficient. */ + /* Send a progress so that we can detect a connection loss. */ + rc = send_status_printf (ctrl, "PROGRESS", "scd_locked . 0 0"); + if (!rc) + goto retry; + } +#endif /*USE_NPTH*/ + + if (rc) + log_error ("cmd_lock failed: %s\n", gpg_strerror (rc)); + return rc; +} + + +static const char hlp_unlock[] = + "UNLOCK\n" + "\n" + "Release exclusive card access."; +static gpg_error_t +cmd_unlock (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc = 0; + + (void)line; + + if (locked_session) + { + if (locked_session != ctrl->server_local) + rc = gpg_error (GPG_ERR_LOCKED); + else + locked_session = NULL; + } + else + rc = gpg_error (GPG_ERR_NOT_LOCKED); + + if (rc) + log_error ("cmd_unlock failed: %s\n", gpg_strerror (rc)); + return rc; +} + + +/* Ease reading of Assuan data ;ines by sending a physical line after + * each LF. */ +static gpg_error_t +pretty_assuan_send_data (assuan_context_t ctx, + const void *buffer_arg, size_t size) +{ + const char *buffer = buffer_arg; + const char *p; + size_t n, nbytes; + gpg_error_t err; + + nbytes = size; + do + { + p = memchr (buffer, '\n', nbytes); + n = p ? (p - buffer) + 1 : nbytes; + err = assuan_send_data (ctx, buffer, n); + if (err) + { + /* We also set ERRNO in case this function is used by a + * custom estream I/O handler. */ + gpg_err_set_errno (EIO); + goto leave; + } + buffer += n; + nbytes -= n; + if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */ + { + gpg_err_set_errno (EIO); + goto leave; + } + } + while (nbytes); + + leave: + return err; +} + + +static const char hlp_getinfo[] = + "GETINFO <what>\n" + "\n" + "Multi purpose command to return certain information. \n" + "Supported values of WHAT are:\n" + "\n" + " version - Return the version of the program.\n" + " pid - Return the process id of the server.\n" + " socket_name - Return the name of the socket.\n" + " connections - Return number of active connections.\n" + " status - Return the status of the current reader (in the future,\n" + " may also return the status of all readers). The status\n" + " is a list of one-character flags. The following flags\n" + " are currently defined:\n" + " 'u' Usable card present.\n" + " 'r' Card removed. A reset is necessary.\n" + " These flags are exclusive.\n" + " reader_list - Return a list of detected card readers.\n" + " deny_admin - Returns OK if admin commands are not allowed or\n" + " GPG_ERR_GENERAL if admin commands are allowed.\n" + " app_list - Return a list of supported applications. One\n" + " application per line, fields delimited by colons,\n" + " first field is the name.\n" + " card_list - Return a list of serial numbers of active cards,\n" + " using a status response."; +static gpg_error_t +cmd_getinfo (assuan_context_t ctx, char *line) +{ + int rc = 0; + + if (!strcmp (line, "version")) + { + const char *s = VERSION; + rc = assuan_send_data (ctx, s, strlen (s)); + } + else if (!strcmp (line, "pid")) + { + char numbuf[50]; + + snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); + rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } + else if (!strcmp (line, "socket_name")) + { + const char *s = scd_get_socket_name (); + + if (s) + rc = assuan_send_data (ctx, s, strlen (s)); + else + rc = gpg_error (GPG_ERR_NO_DATA); + } + else if (!strcmp (line, "connections")) + { + char numbuf[20]; + + snprintf (numbuf, sizeof numbuf, "%d", get_active_connection_count ()); + rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } + else if (!strcmp (line, "status")) + { + ctrl_t ctrl = assuan_get_pointer (ctx); + char flag; + + if (open_card (ctrl)) + flag = 'r'; + else + flag = 'u'; + + rc = assuan_send_data (ctx, &flag, 1); + } + else if (!strcmp (line, "reader_list")) + { + char *s = apdu_get_reader_list (); + if (s) + rc = pretty_assuan_send_data (ctx, s, strlen (s)); + else + rc = gpg_error (GPG_ERR_NO_DATA); + xfree (s); + } + else if (!strcmp (line, "deny_admin")) + rc = opt.allow_admin? gpg_error (GPG_ERR_GENERAL) : 0; + else if (!strcmp (line, "app_list")) + { + char *s = get_supported_applications (); + if (s) + rc = assuan_send_data (ctx, s, strlen (s)); + else + rc = 0; + xfree (s); + } + else if (!strcmp (line, "card_list")) + { + ctrl_t ctrl = assuan_get_pointer (ctx); + + app_send_card_list (ctrl); + } + else + rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); + return rc; +} + + +static const char hlp_restart[] = + "RESTART\n" + "\n" + "Restart the current connection; this is a kind of warm reset. It\n" + "deletes the context used by this connection but does not send a\n" + "RESET to the card. Thus the card itself won't get reset. \n" + "\n" + "This is used by gpg-agent to reuse a primary pipe connection and\n" + "may be used by clients to backup from a conflict in the serial\n" + "command; i.e. to select another application."; +static gpg_error_t +cmd_restart (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + app_t app = ctrl->app_ctx; + + (void)line; + + if (app) + { + ctrl->app_ctx = NULL; + release_application (app, 0); + } + if (locked_session && ctrl->server_local == locked_session) + { + locked_session = NULL; + log_info ("implicitly unlocking due to RESTART\n"); + } + return 0; +} + + +static const char hlp_disconnect[] = + "DISCONNECT\n" + "\n" + "Disconnect the card if the backend supports a disconnect operation."; +static gpg_error_t +cmd_disconnect (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + (void)line; + + if (!ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + apdu_disconnect (ctrl->app_ctx->slot); + return 0; +} + + + +static const char hlp_apdu[] = + "APDU [--[dump-]atr] [--more] [--exlen[=N]] [hexstring]\n" + "\n" + "Send an APDU to the current reader. This command bypasses the high\n" + "level functions and sends the data directly to the card. HEXSTRING\n" + "is expected to be a proper APDU. If HEXSTRING is not given no\n" + "commands are set to the card but the command will implictly check\n" + "whether the card is ready for use. \n" + "\n" + "Using the option \"--atr\" returns the ATR of the card as a status\n" + "message before any data like this:\n" + " S CARD-ATR 3BFA1300FF813180450031C173C00100009000B1\n" + "\n" + "Using the option --more handles the card status word MORE_DATA\n" + "(61xx) and concatenates all responses to one block.\n" + "\n" + "Using the option \"--exlen\" the returned APDU may use extended\n" + "length up to N bytes. If N is not given a default value is used\n" + "(currently 4096)."; +static gpg_error_t +cmd_apdu (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + app_t app; + int rc; + unsigned char *apdu; + size_t apdulen; + int with_atr; + int handle_more; + const char *s; + size_t exlen; + + if (has_option (line, "--dump-atr")) + with_atr = 2; + else + with_atr = has_option (line, "--atr"); + handle_more = has_option (line, "--more"); + + if ((s=has_option_name (line, "--exlen"))) + { + if (*s == '=') + exlen = strtoul (s+1, NULL, 0); + else + exlen = 4096; + } + else + exlen = 0; + + line = skip_options (line); + + if ((rc = open_card (ctrl))) + return rc; + + app = ctrl->app_ctx; + if (!app) + return gpg_error (GPG_ERR_CARD_NOT_PRESENT); + + if (with_atr) + { + unsigned char *atr; + size_t atrlen; + char hexbuf[400]; + + atr = apdu_get_atr (app->slot, &atrlen); + if (!atr || atrlen > sizeof hexbuf - 2 ) + { + rc = gpg_error (GPG_ERR_INV_CARD); + goto leave; + } + if (with_atr == 2) + { + char *string, *p, *pend; + + string = atr_dump (atr, atrlen); + if (string) + { + for (rc=0, p=string; !rc && (pend = strchr (p, '\n')); p = pend+1) + { + rc = assuan_send_data (ctx, p, pend - p + 1); + if (!rc) + rc = assuan_send_data (ctx, NULL, 0); + } + if (!rc && *p) + rc = assuan_send_data (ctx, p, strlen (p)); + es_free (string); + if (rc) + goto leave; + } + } + else + { + bin2hex (atr, atrlen, hexbuf); + send_status_info (ctrl, "CARD-ATR", hexbuf, strlen (hexbuf), NULL, 0); + } + xfree (atr); + } + + apdu = hex_to_buffer (line, &apdulen); + if (!apdu) + { + rc = gpg_error_from_syserror (); + goto leave; + } + if (apdulen) + { + unsigned char *result = NULL; + size_t resultlen; + + rc = apdu_send_direct (app->slot, exlen, + apdu, apdulen, handle_more, + NULL, &result, &resultlen); + if (rc) + log_error ("apdu_send_direct failed: %s\n", gpg_strerror (rc)); + else + { + rc = assuan_send_data (ctx, result, resultlen); + xfree (result); + } + } + xfree (apdu); + + leave: + return rc; +} + + +static const char hlp_killscd[] = + "KILLSCD\n" + "\n" + "Commit suicide."; +static gpg_error_t +cmd_killscd (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + (void)line; + + ctrl->server_local->stopme = 1; + assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); + return 0; +} + + + +/* Tell the assuan library about our commands */ +static int +register_commands (assuan_context_t ctx) +{ + static struct { + const char *name; + assuan_handler_t handler; + const char * const help; + } table[] = { + { "SERIALNO", cmd_serialno, hlp_serialno }, + { "LEARN", cmd_learn, hlp_learn }, + { "READCERT", cmd_readcert, hlp_readcert }, + { "READKEY", cmd_readkey, hlp_readkey }, + { "SETDATA", cmd_setdata, hlp_setdata }, + { "PKSIGN", cmd_pksign, hlp_pksign }, + { "PKAUTH", cmd_pkauth, hlp_pkauth }, + { "PKDECRYPT", cmd_pkdecrypt,hlp_pkdecrypt }, + { "INPUT", NULL }, + { "OUTPUT", NULL }, + { "GETATTR", cmd_getattr, hlp_getattr }, + { "SETATTR", cmd_setattr, hlp_setattr }, + { "WRITECERT", cmd_writecert,hlp_writecert }, + { "WRITEKEY", cmd_writekey, hlp_writekey }, + { "GENKEY", cmd_genkey, hlp_genkey }, + { "RANDOM", cmd_random, hlp_random }, + { "PASSWD", cmd_passwd, hlp_passwd }, + { "CHECKPIN", cmd_checkpin, hlp_checkpin }, + { "LOCK", cmd_lock, hlp_lock }, + { "UNLOCK", cmd_unlock, hlp_unlock }, + { "GETINFO", cmd_getinfo, hlp_getinfo }, + { "RESTART", cmd_restart, hlp_restart }, + { "DISCONNECT", cmd_disconnect,hlp_disconnect }, + { "APDU", cmd_apdu, hlp_apdu }, + { "KILLSCD", cmd_killscd, hlp_killscd }, + { NULL } + }; + int i, rc; + + for (i=0; table[i].name; i++) + { + rc = assuan_register_command (ctx, table[i].name, table[i].handler, + table[i].help); + if (rc) + return rc; + } + assuan_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 FD is given as -1 this is simple pipe + server, otherwise it is a regular server. Returns true if there + are no more active asessions. */ +int +scd_command_handler (ctrl_t ctrl, int fd) +{ + int rc; + assuan_context_t ctx = NULL; + int stopme; + + rc = assuan_new (&ctx); + if (rc) + { + log_error ("failed to allocate assuan context: %s\n", + gpg_strerror (rc)); + scd_exit (2); + } + + if (fd == -1) + { + assuan_fd_t filedes[2]; + + filedes[0] = assuan_fdopen (0); + filedes[1] = assuan_fdopen (1); + rc = assuan_init_pipe_server (ctx, filedes); + } + else + { + rc = assuan_init_socket_server (ctx, INT2FD(fd), + ASSUAN_SOCKET_SERVER_ACCEPTED); + } + if (rc) + { + log_error ("failed to initialize the server: %s\n", + gpg_strerror(rc)); + scd_exit (2); + } + rc = register_commands (ctx); + if (rc) + { + log_error ("failed to register commands with Assuan: %s\n", + gpg_strerror(rc)); + scd_exit (2); + } + assuan_set_pointer (ctx, ctrl); + + /* Allocate and initialize the server object. Put it into the list + of active sessions. */ + ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local); + ctrl->server_local->next_session = session_list; + session_list = ctrl->server_local; + ctrl->server_local->ctrl_backlink = ctrl; + ctrl->server_local->assuan_ctx = ctx; + + /* Command processing loop. */ + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + { + break; + } + else if (rc) + { + log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); + break; + } + + rc = assuan_process (ctx); + if (rc) + { + log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); + continue; + } + } + + /* Cleanup. We don't send an explicit reset to the card. */ + do_reset (ctrl, 0, 0); + + /* Release the server object. */ + if (session_list == ctrl->server_local) + session_list = ctrl->server_local->next_session; + else + { + struct server_local_s *sl; + + for (sl=session_list; sl->next_session; sl = sl->next_session) + if (sl->next_session == ctrl->server_local) + break; + if (!sl->next_session) + BUG (); + sl->next_session = ctrl->server_local->next_session; + } + stopme = ctrl->server_local->stopme; + xfree (ctrl->server_local); + ctrl->server_local = NULL; + + /* Release the Assuan context. */ + assuan_release (ctx); + + if (stopme) + scd_exit (0); + + /* If there are no more sessions return true. */ + return !session_list; +} + + + +/* Send a keyinfo string. If DATA is true the string is emitted as a + * data line, else as a status line. */ +void +send_keyinfo (ctrl_t ctrl, int data, const char *keygrip_str, + const char *serialno, const char *idstr) +{ + char *string; + assuan_context_t ctx = ctrl->server_local->assuan_ctx; + + string = xtryasprintf ("%s T %s %s%s", keygrip_str, + serialno? serialno : "-", + idstr? idstr : "-", + data? "\n" : ""); + + if (!string) + return; + + if (!data) + assuan_write_status (ctx, "KEYINFO", string); + else + assuan_send_data (ctx, string, strlen (string)); + + xfree (string); + return; +} + + +/* 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_t ctrl, const char *keyword, ...) +{ + va_list arg_ptr; + const unsigned char *value; + size_t valuelen; + char buf[950], *p; + size_t n; + assuan_context_t ctx = ctrl->server_local->assuan_ctx; + + va_start (arg_ptr, keyword); + + p = buf; + n = 0; + while ( (value = va_arg (arg_ptr, const unsigned char *)) + && n < DIM (buf)-2 ) + { + 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 == '\"' || *value == '%' + || *value < ' ') + { + sprintf (p, "%%%02X", *value); + p += 3; + n += 2; + } + else if (*value == ' ') + *p++ = '+'; + else + *p++ = *value; + } + } + *p = 0; + assuan_write_status (ctx, keyword, buf); + + va_end (arg_ptr); +} + + +/* Send a ready formatted status line via assuan. */ +gpg_error_t +send_status_direct (ctrl_t ctrl, const char *keyword, const char *args) +{ + assuan_context_t ctx = ctrl->server_local->assuan_ctx; + + if (strchr (args, '\n')) + { + log_error ("error: LF detected in status line - not sending\n"); + return gpg_error (GPG_ERR_INTERNAL); + } + return assuan_write_status (ctx, keyword, args); +} + + +/* This status functions expects a printf style format string. No + * filtering of the data is done instead the orintf formatted data is + * send using assuan_send_status. */ +gpg_error_t +send_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...) +{ + gpg_error_t err; + va_list arg_ptr; + assuan_context_t ctx; + + if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx)) + return 0; + + va_start (arg_ptr, format); + err = vprint_assuan_status (ctx, keyword, format, arg_ptr); + va_end (arg_ptr); + return err; +} + + +void +popup_prompt (void *opaque, int on) +{ + ctrl_t ctrl = opaque; + + if (ctrl) + { + assuan_context_t ctx = ctrl->server_local->assuan_ctx; + + if (ctx) + { + const char *cmd; + gpg_error_t err; + unsigned char *value; + size_t valuelen; + + if (on) + cmd = "POPUPPINPADPROMPT --ack"; + else + cmd = "DISMISSPINPADPROMPT"; + err = assuan_inquire (ctx, cmd, &value, &valuelen, 100); + if (!err) + xfree (value); + } + } +} + + +/* Helper to send the clients a status change notification. */ +void +send_client_notifications (app_t app, int removal) +{ + struct { + pid_t pid; +#ifdef HAVE_W32_SYSTEM + HANDLE handle; +#else + int signo; +#endif + } killed[50]; + int killidx = 0; + int kidx; + struct server_local_s *sl; + + for (sl=session_list; sl; sl = sl->next_session) + if (sl->ctrl_backlink && sl->ctrl_backlink->app_ctx == app) + { + pid_t pid; +#ifdef HAVE_W32_SYSTEM + HANDLE handle; +#else + int signo; +#endif + + if (removal) + { + sl->ctrl_backlink->app_ctx = NULL; + sl->card_removed = 1; + release_application (app, 1); + } + + if (!sl->event_signal || !sl->assuan_ctx) + continue; + + pid = assuan_get_pid (sl->assuan_ctx); + +#ifdef HAVE_W32_SYSTEM + handle = sl->event_signal; + for (kidx=0; kidx < killidx; kidx++) + if (killed[kidx].pid == pid + && killed[kidx].handle == handle) + break; + if (kidx < killidx) + log_info ("event %p (%p) already triggered for client %d\n", + sl->event_signal, handle, (int)pid); + else + { + log_info ("triggering event %p (%p) for client %d\n", + sl->event_signal, handle, (int)pid); + if (!SetEvent (handle)) + log_error ("SetEvent(%p) failed: %s\n", + sl->event_signal, w32_strerror (-1)); + if (killidx < DIM (killed)) + { + killed[killidx].pid = pid; + killed[killidx].handle = handle; + killidx++; + } + } +#else /*!HAVE_W32_SYSTEM*/ + signo = sl->event_signal; + + if (pid != (pid_t)(-1) && pid && signo > 0) + { + for (kidx=0; kidx < killidx; kidx++) + if (killed[kidx].pid == pid + && killed[kidx].signo == signo) + break; + if (kidx < killidx) + log_info ("signal %d already sent to client %d\n", + signo, (int)pid); + else + { + log_info ("sending signal %d to client %d\n", + signo, (int)pid); + kill (pid, signo); + if (killidx < DIM (killed)) + { + killed[killidx].pid = pid; + killed[killidx].signo = signo; + killidx++; + } + } + } +#endif /*!HAVE_W32_SYSTEM*/ + } +} diff --git a/scd/iso7816.c b/scd/iso7816.c new file mode 100644 index 0000000..c878a03 --- /dev/null +++ b/scd/iso7816.c @@ -0,0 +1,1034 @@ +/* iso7816.c - ISO 7816 commands + * Copyright (C) 2003, 2004, 2008, 2009 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if defined(GNUPG_SCD_MAIN_HEADER) +#include GNUPG_SCD_MAIN_HEADER +#elif GNUPG_MAJOR_VERSION == 1 +/* This is used with GnuPG version < 1.9. The code has been source + copied from the current GnuPG >= 1.9 and is maintained over + there. */ +#include "options.h" +#include "errors.h" +#include "memory.h" +#include "../common/util.h" +#include "../common/i18n.h" +#else /* GNUPG_MAJOR_VERSION != 1 */ +#include "scdaemon.h" +#endif /* GNUPG_MAJOR_VERSION != 1 */ + +#include "iso7816.h" +#include "apdu.h" + + +#define CMD_SELECT_FILE 0xA4 +#define CMD_VERIFY ISO7816_VERIFY +#define CMD_CHANGE_REFERENCE_DATA ISO7816_CHANGE_REFERENCE_DATA +#define CMD_RESET_RETRY_COUNTER ISO7816_RESET_RETRY_COUNTER +#define CMD_GET_DATA 0xCA +#define CMD_PUT_DATA 0xDA +#define CMD_MSE 0x22 +#define CMD_PSO 0x2A +#define CMD_GENERAL_AUTHENTICATE 0x87 +#define CMD_INTERNAL_AUTHENTICATE 0x88 +#define CMD_GENERATE_KEYPAIR 0x47 +#define CMD_GET_CHALLENGE 0x84 +#define CMD_READ_BINARY 0xB0 +#define CMD_READ_RECORD 0xB2 + +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_TERM_STATE: ec = GPG_ERR_OBJ_TERM_STATE; break; + case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break; + case SW_SM_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; + case SW_CC_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; + case SW_FILE_STRUCT: ec = GPG_ERR_CARD; 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_NO_CURRENT_EF: ec = GPG_ERR_ENOENT; break; + case SW_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break; + case SW_BAD_PARAMETER: ec = GPG_ERR_INV_VALUE; break; + case SW_FILE_NOT_FOUND: ec = GPG_ERR_ENOENT; break; + case SW_RECORD_NOT_FOUND:ec= GPG_ERR_NOT_FOUND; break; + case SW_REF_NOT_FOUND: ec = GPG_ERR_NO_OBJ; break; + case SW_INCORRECT_P0_P1:ec = GPG_ERR_INV_VALUE; break; + case SW_BAD_P0_P1: ec = GPG_ERR_INV_VALUE; break; + case SW_EXACT_LENGTH: 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; + case SW_HOST_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break; + case SW_HOST_LOCKING_FAILED: ec = GPG_ERR_BUG; break; + case SW_HOST_BUSY: ec = GPG_ERR_EBUSY; break; + case SW_HOST_NO_CARD: ec = GPG_ERR_CARD_NOT_PRESENT; break; + case SW_HOST_CARD_INACTIVE: ec = GPG_ERR_CARD_RESET; break; + case SW_HOST_CARD_IO_ERROR: ec = GPG_ERR_EIO; break; + case SW_HOST_GENERAL_ERROR: ec = GPG_ERR_GENERAL; break; + case SW_HOST_NO_READER: ec = GPG_ERR_ENODEV; break; + case SW_HOST_ABORTED: ec = GPG_ERR_INV_RESPONSE; break; + case SW_HOST_NO_PINPAD: ec = GPG_ERR_NOT_SUPPORTED; break; + case SW_HOST_CANCELLED: ec = GPG_ERR_CANCELED; break; + case SW_HOST_USB_OTHER: ec = GPG_ERR_EIO; break; + case SW_HOST_USB_IO: ec = GPG_ERR_EIO; break; + case SW_HOST_USB_ACCESS: ec = GPG_ERR_EACCES; break; + case SW_HOST_USB_NO_DEVICE: ec = GPG_ERR_ENODEV; break; + case SW_HOST_USB_BUSY: ec = GPG_ERR_EBUSY; break; + case SW_HOST_USB_TIMEOUT: ec = GPG_ERR_TIMEOUT; break; + case SW_HOST_USB_OVERFLOW: ec = GPG_ERR_EOVERFLOW; 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 if ((sw & 0xfff0) == 0x63C0) + ec = GPG_ERR_BAD_PIN; + else + ec = GPG_ERR_CARD; + } + return gpg_error (ec); +} + +/* Map a status word from the APDU layer to a gpg-error code. */ +gpg_error_t +iso7816_map_sw (int sw) +{ + /* All APDU functions should return 0x9000 on success but for + historical reasons of the implementation some return 0 to + indicate success. We allow for that here. */ + return sw? map_sw (sw) : 0; +} + + +/* 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 a GPG error code. Note that ISO error codes are + internally mapped. Bit 0 of FLAGS should be set if the card does + not understand P2=0xC0. */ +gpg_error_t +iso7816_select_application (int slot, const char *aid, size_t aidlen, + unsigned int flags) +{ + int sw; + sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, 4, + (flags&1)? 0 :0x0c, aidlen, aid); + return map_sw (sw); +} + + +/* This is the same as iso7816_select_application but may return data + * at RESULT,RESULTLEN). */ +gpg_error_t +iso7816_select_application_ext (int slot, const char *aid, size_t aidlen, + unsigned int flags, + unsigned char **result, size_t *resultlen) +{ + int sw; + sw = apdu_send (slot, 0, 0x00, CMD_SELECT_FILE, 4, + (flags&1)? 0:0x0c, aidlen, aid, + result, resultlen); + return map_sw (sw); +} + + +/* Simple MF selection as supported by some cards. */ +gpg_error_t +iso7816_select_mf (int slot) +{ + int sw; + + sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, 0x000, 0x0c, -1, NULL); + return map_sw (sw); +} + + +gpg_error_t +iso7816_select_file (int slot, int tag, int is_dir) +{ + int sw, p0, p1; + unsigned char tagbuf[2]; + + tagbuf[0] = (tag >> 8) & 0xff; + tagbuf[1] = tag & 0xff; + + p0 = (tag == 0x3F00)? 0: is_dir? 1:2; + p1 = 0x0c; /* No FC return. */ + sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, + p0, p1, 2, (char*)tagbuf ); + return map_sw (sw); +} + + +/* Do a select file command with a direct path. If TOPDF is set, the + * actual used path is 3f00/<topdf>/<path>. */ +gpg_error_t +iso7816_select_path (int slot, const unsigned short *path, size_t pathlen, + unsigned short topdf) +{ + int sw, p0, p1; + unsigned char buffer[100]; + int buflen = 0; + + if (pathlen*2 + 2 >= sizeof buffer) + return gpg_error (GPG_ERR_TOO_LARGE); + + if (topdf) + { + buffer[buflen++] = topdf >> 8; + buffer[buflen++] = topdf; + } + + for (; pathlen; pathlen--, path++) + { + buffer[buflen++] = (*path >> 8); + buffer[buflen++] = *path; + } + + p0 = 0x08; + p1 = 0x0c; /* No FC return. */ + sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, + p0, p1, buflen, (char*)buffer ); + return map_sw (sw); +} + + +/* This is a private command currently only working for TCOS cards. */ +gpg_error_t +iso7816_list_directory (int slot, int list_dirs, + 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, 0, 0x80, 0xAA, list_dirs? 1:2, 0, -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); +} + + +/* This function sends an already formatted APDU to the card. With + HANDLE_MORE set to true a MORE DATA status will be handled + internally. The return value is a gpg error code (i.e. a mapped + status word). This is basically the same as apdu_send_direct but + it maps the status word and does not return it in the result + buffer. However, it R_SW is not NULL the status word is stored + R_SW for closer inspection. */ +gpg_error_t +iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, + int handle_more, unsigned int *r_sw, + unsigned char **result, size_t *resultlen) +{ + int sw, sw2; + + if (result) + { + *result = NULL; + *resultlen = 0; + } + + sw = apdu_send_direct (slot, 0, apdudata, apdudatalen, handle_more, + &sw2, result, resultlen); + if (!sw) + { + if (!result) + sw = sw2; + else if (*resultlen < 2) + sw = SW_HOST_GENERAL_ERROR; + else + { + sw = ((*result)[*resultlen-2] << 8) | (*result)[*resultlen-1]; + (*resultlen)--; + (*resultlen)--; + } + } + if (sw != SW_SUCCESS && result) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + } + if (r_sw) + *r_sw = sw; + return map_sw (sw); +} + + +/* Check whether the reader supports the ISO command code COMMAND on + the pinpad. Returns 0 on success. */ +gpg_error_t +iso7816_check_pinpad (int slot, int command, pininfo_t *pininfo) +{ + int sw; + + sw = apdu_check_pinpad (slot, command, pininfo); + return iso7816_map_sw (sw); +} + + +/* Perform a VERIFY command on SLOT using the card holder verification + vector CHVNO. With PININFO non-NULL the pinpad of the reader will + be used. Returns 0 on success. */ +gpg_error_t +iso7816_verify_kp (int slot, int chvno, pininfo_t *pininfo) +{ + int sw; + + sw = apdu_pinpad_verify (slot, 0x00, CMD_VERIFY, 0, chvno, pininfo); + return map_sw (sw); +} + +/* Perform a VERIFY command on SLOT using the card holder verification + vector CHVNO with a CHV of length 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, 0, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv); + return map_sw (sw); +} + + +/* Some cards support a VERIFY command variant to check the status of + * the the CHV without a need to try a CHV. In contrast to the other + * functions this function returns the special codes ISO7816_VERIFY_* + * or a non-negative number with the left attempts. */ +int +iso7816_verify_status (int slot, int chvno) +{ + unsigned char apdu[4]; + unsigned int sw; + int result; + + apdu[0] = 0x00; + apdu[1] = ISO7816_VERIFY; + apdu[2] = 0x00; + apdu[3] = chvno; + if (!iso7816_apdu_direct (slot, apdu, 4, 0, &sw, NULL, NULL)) + result = ISO7816_VERIFY_NOT_NEEDED; /* Not returned by all cards. */ + else if (sw == 0x6a88 || sw == 0x6a80) + result = ISO7816_VERIFY_NO_PIN; + else if (sw == 0x6983) + result = ISO7816_VERIFY_BLOCKED; + else if (sw == 0x6985) + result = ISO7816_VERIFY_NULLPIN; /* TCOS card */ + else if ((sw & 0xfff0) == 0x63C0) + result = (sw & 0x000f); + else + result = ISO7816_VERIFY_ERROR; + + return result; +} + + +/* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder + verification vector CHVNO. With PININFO non-NULL the pinpad of the + reader will be used. If IS_EXCHANGE is 0, a "change reference + data" is done, otherwise an "exchange reference data". */ +gpg_error_t +iso7816_change_reference_data_kp (int slot, int chvno, int is_exchange, + pininfo_t *pininfo) +{ + int sw; + + sw = apdu_pinpad_modify (slot, 0x00, CMD_CHANGE_REFERENCE_DATA, + is_exchange ? 1 : 0, chvno, pininfo); + 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 gpg_error (gpg_err_code_from_errno (errno)); + if (oldchvlen) + memcpy (buf, oldchv, oldchvlen); + memcpy (buf+oldchvlen, newchv, newchvlen); + + sw = apdu_send_simple (slot, 0, 0x00, CMD_CHANGE_REFERENCE_DATA, + oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); + wipememory (buf, oldchvlen+newchvlen); + xfree (buf); + return map_sw (sw); + +} + + +gpg_error_t +iso7816_reset_retry_counter_with_rc (int slot, int chvno, + const char *data, size_t datalen) +{ + int sw; + + if (!data || !datalen ) + return gpg_error (GPG_ERR_INV_VALUE); + + sw = apdu_send_simple (slot, 0, 0x00, CMD_RESET_RETRY_COUNTER, + 0, chvno, datalen, data); + return map_sw (sw); +} + + +gpg_error_t +iso7816_reset_retry_counter (int slot, int chvno, + const char *newchv, size_t newchvlen) +{ + int sw; + + sw = apdu_send_simple (slot, 0, 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 extended_mode, int tag, + unsigned char **result, size_t *resultlen) +{ + int sw; + int le; + + if (!result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + if (extended_mode > 0 && extended_mode < 256) + le = 65534; /* Not 65535 in case it is used as some special flag. */ + else if (extended_mode > 0) + le = extended_mode; + else + le = 256; + + sw = apdu_send_le (slot, extended_mode, 0x00, CMD_GET_DATA, + ((tag >> 8) & 0xff), (tag & 0xff), -1, NULL, le, + 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. EXTENDED_MODE controls whether extended length + headers or command chaining is used instead of single length + bytes. */ +gpg_error_t +iso7816_put_data (int slot, int extended_mode, int tag, + const void *data, size_t datalen) +{ + int sw; + + sw = apdu_send_simple (slot, extended_mode, 0x00, CMD_PUT_DATA, + ((tag >> 8) & 0xff), (tag & 0xff), + datalen, (const char*)data); + return map_sw (sw); +} + +/* Same as iso7816_put_data but uses an odd instruction byte. */ +gpg_error_t +iso7816_put_data_odd (int slot, int extended_mode, int tag, + const void *data, size_t datalen) +{ + int sw; + + sw = apdu_send_simple (slot, extended_mode, 0x00, CMD_PUT_DATA+1, + ((tag >> 8) & 0xff), (tag & 0xff), + datalen, (const char*)data); + return map_sw (sw); +} + +/* Manage Security Environment. This is a weird operation and there + is no easy abstraction for it. Furthermore, some card seem to have + a different interpreation of 7816-8 and thus we resort to let the + caller decide what to do. */ +gpg_error_t +iso7816_manage_security_env (int slot, int p1, int p2, + const unsigned char *data, size_t datalen) +{ + int sw; + + if (p1 < 0 || p1 > 255 || p2 < 0 || p2 > 255 ) + return gpg_error (GPG_ERR_INV_VALUE); + + sw = apdu_send_simple (slot, 0, 0x00, CMD_MSE, p1, p2, + data? datalen : -1, (const char*)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. For LE see do_generate_keypair. */ +gpg_error_t +iso7816_compute_ds (int slot, int extended_mode, + const unsigned char *data, size_t datalen, int le, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + if (!extended_mode) + le = 256; /* Ignore provided Le and use what apdu_send uses. */ + else if (le >= 0 && le < 256) + le = 256; + + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_PSO, 0x9E, 0x9A, + datalen, (const char*)data, + le, + 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. PADIND is the padding + indicator to be used. It should be 0 if no padding is required, a + value of -1 suppresses the padding byte. On success 0 is returned + and the plaintext is available in a newly allocated buffer stored + at RESULT with its length stored at RESULTLEN. For LE see + do_generate_keypair. */ +gpg_error_t +iso7816_decipher (int slot, int extended_mode, + const unsigned char *data, size_t datalen, int le, + int padind, 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; + + if (!extended_mode) + le = 256; /* Ignore provided Le and use what apdu_send uses. */ + else if (le >= 0 && le < 256) + le = 256; + + if (padind >= 0) + { + /* We need to prepend the padding indicator. */ + buf = xtrymalloc (datalen + 1); + if (!buf) + return gpg_error (gpg_err_code_from_errno (errno)); + + *buf = padind; /* Padding indicator. */ + memcpy (buf+1, data, datalen); + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_PSO, 0x80, 0x86, + datalen+1, (char*)buf, le, + result, resultlen); + xfree (buf); + } + else + { + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_PSO, 0x80, 0x86, + datalen, (const char *)data, le, + 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 COMPUTE SHARED SECRET. On success 0 + is returned and the shared secret is available in a newly allocated + buffer stored at RESULT with its length stored at RESULTLEN. For + LE see do_generate_keypair. */ +gpg_error_t +iso7816_pso_csv (int slot, int extended_mode, + const unsigned char *data, size_t datalen, int le, + unsigned char **result, size_t *resultlen) +{ + int sw; + unsigned char *buf; + unsigned int nbuf; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + if (!extended_mode) + le = 256; /* Ignore provided Le and use what apdu_send uses. */ + else if (le >= 0 && le < 256) + le = 256; + + /* Data needs to be in BER-TLV format. */ + buf = xtrymalloc (datalen + 4); + if (!buf) + return gpg_error_from_syserror (); + nbuf = 0; + buf[nbuf++] = 0x9c; + if (datalen < 128) + buf[nbuf++] = datalen; + else if (datalen < 256) + { + buf[nbuf++] = 0x81; + buf[nbuf++] = datalen; + } + else + { + buf[nbuf++] = 0x82; + buf[nbuf++] = datalen << 8; + buf[nbuf++] = datalen; + } + memcpy (buf+nbuf, data, datalen); + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_PSO, 0x80, 0xa6, + datalen+nbuf, (const char *)buf, le, + 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; +} + + +/* For LE see do_generate_keypair. */ +gpg_error_t +iso7816_internal_authenticate (int slot, int extended_mode, + const unsigned char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + if (!extended_mode) + le = 256; /* Ignore provided Le and use what apdu_send uses. */ + else if (le >= 0 && le < 256) + le = 256; + + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0, + datalen, (const char*)data, + le, + 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; +} + + +/* For LE see do_generate_keypair. */ +gpg_error_t +iso7816_general_authenticate (int slot, int extended_mode, + int algoref, int keyref, + const unsigned char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + if (!extended_mode) + le = 256; /* Ignore provided Le and use what apdu_send uses. */ + else if (le >= 0 && le < 256) + le = 256; + + sw = apdu_send_le (slot, extended_mode, + 0x00, CMD_GENERAL_AUTHENTICATE, algoref, keyref, + datalen, (const char*)data, + le, + 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; +} + + +/* LE is the expected return length. This is usually 0 except if + extended length mode is used and more than 256 byte will be + returned. In that case a value of -1 uses a large default + (e.g. 4096 bytes), a value larger 256 used that value. */ +static gpg_error_t +do_generate_keypair (int slot, int extended_mode, int p1, int p2, + const char *data, size_t datalen, int le, + 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_le (slot, extended_mode, + 0x00, CMD_GENERATE_KEYPAIR, p1, p2, + datalen, data, + le >= 0 && le < 256? 256:le, + 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, int extended_mode, int p1, int p2, + const char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen) +{ + return do_generate_keypair (slot, extended_mode, p1, p2, + data, datalen, le, result, resultlen); +} + + +gpg_error_t +iso7816_read_public_key (int slot, int extended_mode, + const char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen) +{ + return do_generate_keypair (slot, extended_mode, 0x81, 0, + data, datalen, le, 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, 0, + 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; +} + +/* Perform a READ BINARY command requesting a maximum of NMAX bytes + * from OFFSET. With NMAX = 0 the entire file is read. The result is + * stored in a newly allocated buffer at the address passed by RESULT. + * Returns the length of this data at the address of RESULTLEN. If + * R_SW is not NULL the last status word is stored there. */ +gpg_error_t +iso7816_read_binary_ext (int slot, int extended_mode, + size_t offset, size_t nmax, + unsigned char **result, size_t *resultlen, + int *r_sw) +{ + int sw; + unsigned char *buffer; + size_t bufferlen; + int read_all = !nmax; + size_t n; + + if (r_sw) + *r_sw = 0; + + if (!result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + /* We can only encode 15 bits in p0,p1 to indicate an offset. Thus + we check for this limit. */ + if (offset > 32767) + return gpg_error (GPG_ERR_INV_VALUE); + + do + { + buffer = NULL; + bufferlen = 0; + n = read_all? 0 : nmax; + sw = apdu_send_le (slot, extended_mode, 0x00, CMD_READ_BINARY, + ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL, + n, &buffer, &bufferlen); + if ( SW_EXACT_LENGTH_P(sw) ) + { + n = (sw & 0x00ff); + sw = apdu_send_le (slot, extended_mode, 0x00, CMD_READ_BINARY, + ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL, + n, &buffer, &bufferlen); + } + if (r_sw) + *r_sw = sw; + + if (*result && sw == SW_BAD_P0_P1) + { + /* Bad Parameter means that the offset is outside of the + EF. When reading all data we take this as an indication + for EOF. */ + break; + } + + if (sw != SW_SUCCESS && sw != SW_EOF_REACHED) + { + /* Make sure that pending buffers are released. */ + xfree (buffer); + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + if (*result) /* Need to extend the buffer. */ + { + unsigned char *p = xtryrealloc (*result, *resultlen + bufferlen); + if (!p) + { + gpg_error_t err = gpg_error_from_syserror (); + xfree (buffer); + xfree (*result); + *result = NULL; + *resultlen = 0; + return err; + } + *result = p; + memcpy (*result + *resultlen, buffer, bufferlen); + *resultlen += bufferlen; + xfree (buffer); + buffer = NULL; + } + else /* Transfer the buffer into our result. */ + { + *result = buffer; + *resultlen = bufferlen; + } + offset += bufferlen; + if (offset > 32767) + break; /* We simply truncate the result for too large + files. */ + if (nmax > bufferlen) + nmax -= bufferlen; + else + nmax = 0; + } + while ((read_all && sw != SW_EOF_REACHED) || (!read_all && nmax)); + + return 0; +} + + +gpg_error_t +iso7816_read_binary (int slot, size_t offset, size_t nmax, + unsigned char **result, size_t *resultlen) +{ + return iso7816_read_binary_ext (slot, 0, offset, nmax, + result, resultlen, NULL); +} + + +/* Perform a READ RECORD command. RECNO gives the record number to + read with 0 indicating the current record. RECCOUNT must be 1 (not + all cards support reading of more than one record). SHORT_EF + should be 0 to read the current EF or contain a short EF. The + result is stored in a newly allocated buffer at the address passed + by RESULT. Returns the length of this data at the address of + RESULTLEN. If R_SW is not NULL the last status word is stored + there. */ +gpg_error_t +iso7816_read_record_ext (int slot, int recno, int reccount, int short_ef, + unsigned char **result, size_t *resultlen, + int *r_sw) +{ + int sw; + unsigned char *buffer; + size_t bufferlen; + + if (r_sw) + *r_sw = 0; + + if (!result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + /* We can only encode 15 bits in p0,p1 to indicate an offset. Thus + we check for this limit. */ + if (recno < 0 || recno > 255 || reccount != 1 + || short_ef < 0 || short_ef > 254 ) + return gpg_error (GPG_ERR_INV_VALUE); + + buffer = NULL; + bufferlen = 0; + sw = apdu_send_le (slot, 0, 0x00, CMD_READ_RECORD, + recno, + short_ef? short_ef : 0x04, + -1, NULL, + 0, &buffer, &bufferlen); + if (r_sw) + *r_sw = sw; + + if (sw != SW_SUCCESS && sw != SW_EOF_REACHED) + { + /* Make sure that pending buffers are released. */ + xfree (buffer); + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + *result = buffer; + *resultlen = bufferlen; + + return 0; +} + +gpg_error_t +iso7816_read_record (int slot, int recno, int reccount, int short_ef, + unsigned char **result, size_t *resultlen) +{ + return iso7816_read_record_ext (slot, recno, reccount, short_ef, + result, resultlen, NULL); +} diff --git a/scd/iso7816.h b/scd/iso7816.h new file mode 100644 index 0000000..d22f0be --- /dev/null +++ b/scd/iso7816.h @@ -0,0 +1,154 @@ +/* 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef ISO7816_H +#define ISO7816_H + +#if GNUPG_MAJOR_VERSION == 1 +#include "cardglue.h" +#endif + +/* Command codes used by iso7816_check_pinpad. */ +#define ISO7816_VERIFY 0x20 +#define ISO7816_CHANGE_REFERENCE_DATA 0x24 +#define ISO7816_RESET_RETRY_COUNTER 0x2C + +/* Error codes returned by iso7816_verify_status. A non-negative + * number gives the number of left tries. + * NB: The values are also used by the CHV-STATUS lines and thus are + * part of the public interface. Do not change them. */ +#define ISO7816_VERIFY_ERROR (-1) +#define ISO7816_VERIFY_NO_PIN (-2) +#define ISO7816_VERIFY_BLOCKED (-3) +#define ISO7816_VERIFY_NULLPIN (-4) +#define ISO7816_VERIFY_NOT_NEEDED (-5) + +/* Information to be passed to pinpad equipped readers. See + ccid-driver.c for details. */ +struct pininfo_s +{ + int fixedlen; /* + * -1: Variable length input is not supported, + * no information of fixed length yet. + * 0: Use variable length input. + * >0: Fixed length of PIN. + */ + int minlen; + int maxlen; +}; +typedef struct pininfo_s pininfo_t; + + +gpg_error_t iso7816_map_sw (int sw); + +gpg_error_t iso7816_select_application (int slot, + const char *aid, size_t aidlen, + unsigned int flags); +gpg_error_t iso7816_select_application_ext (int slot, + const char *aid, size_t aidlen, + unsigned int flags, + unsigned char **result, + size_t *resultlen); +gpg_error_t iso7816_select_mf (int slot); +gpg_error_t iso7816_select_file (int slot, int tag, int is_dir); +gpg_error_t iso7816_select_path (int slot, + const unsigned short *path, size_t pathlen, + unsigned short top_df); +gpg_error_t iso7816_list_directory (int slot, int list_dirs, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_apdu_direct (int slot, + const void *apdudata, size_t apdudatalen, + int handle_more, unsigned int *r_sw, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_check_pinpad (int slot, int command, + pininfo_t *pininfo); +gpg_error_t iso7816_verify (int slot, + int chvno, const char *chv, size_t chvlen); +gpg_error_t iso7816_verify_kp (int slot, int chvno, pininfo_t *pininfo); +int iso7816_verify_status (int slot, int chvno); +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_change_reference_data_kp (int slot, int chvno, + int is_exchange, + pininfo_t *pininfo); +gpg_error_t iso7816_reset_retry_counter (int slot, int chvno, + const char *newchv, size_t newchvlen); +gpg_error_t iso7816_reset_retry_counter_with_rc (int slot, int chvno, + const char *data, + size_t datalen); +gpg_error_t iso7816_get_data (int slot, int extended_mode, int tag, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_put_data (int slot, int extended_mode, int tag, + const void *data, size_t datalen); +gpg_error_t iso7816_put_data_odd (int slot, int extended_mode, int tag, + const void *data, size_t datalen); +gpg_error_t iso7816_manage_security_env (int slot, int p1, int p2, + const unsigned char *data, + size_t datalen); +gpg_error_t iso7816_compute_ds (int slot, int extended_mode, + const unsigned char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_decipher (int slot, int extended_mode, + const unsigned char *data, size_t datalen, + int le, int padind, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_pso_csv (int slot, int extended_mode, + const unsigned char *data, size_t datalen, int le, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_internal_authenticate (int slot, int extended_mode, + const unsigned char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_general_authenticate (int slot, int extended_mode, + int algoref, int keyref, + const unsigned char *data, + size_t datalen, + int le, + unsigned char **result, + size_t *resultlen); +gpg_error_t iso7816_generate_keypair (int slot, int extended_mode, + int p1, int p2, + const char *data, size_t datalen, + int le, + unsigned char **result, + size_t *resultlen); +gpg_error_t iso7816_read_public_key (int slot, int extended_mode, + const char *data, size_t datalen, + int le, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_get_challenge (int slot, + int length, unsigned char *buffer); + +gpg_error_t iso7816_read_binary_ext (int slot, int extended_mode, + size_t offset, size_t nmax, + unsigned char **result, size_t *resultlen, + int *r_sw); +gpg_error_t iso7816_read_binary (int slot, size_t offset, size_t nmax, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_read_record_ext (int slot, int recno, int reccount, + int short_ef, + unsigned char **result, size_t *resultlen, + int *r_sw); +gpg_error_t iso7816_read_record (int slot, int recno, int reccount, + int short_ef, + unsigned char **result, size_t *resultlen); + +#endif /*ISO7816_H*/ diff --git a/scd/scdaemon-w32info.rc b/scd/scdaemon-w32info.rc new file mode 100644 index 0000000..c1dae54 --- /dev/null +++ b/scd/scdaemon-w32info.rc @@ -0,0 +1,52 @@ +/* scdaemon-w32info.rc -*- c -*- + * Copyright (C) 2013 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s smartcard daemon\0" + VALUE "InternalName", "scdaemon\0" + VALUE "OriginalFilename", "scdaemon.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "scdaemon.w32-manifest" diff --git a/scd/scdaemon.c b/scd/scdaemon.c new file mode 100644 index 0000000..b62f5b6 --- /dev/null +++ b/scd/scdaemon.c @@ -0,0 +1,1454 @@ +/* scdaemon.c - The GnuPG Smartcard Daemon + * Copyright (C) 2001-2002, 2004-2005, 2007-2020 Free Software Foundation, Inc. + * Copyright (C) 2001-2002, 2004-2005, 2007-2019 Werner Koch + * Copyright (C) 2020 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <time.h> +#include <fcntl.h> +#ifndef HAVE_W32_SYSTEM +#include <sys/socket.h> +#include <sys/un.h> +#endif /*HAVE_W32_SYSTEM*/ +#include <unistd.h> +#include <signal.h> +#include <npth.h> + +#define INCLUDED_BY_MAIN_MODULE 1 +#define GNUPG_COMMON_NEED_AFLOCAL +#include "scdaemon.h" +#include <ksba.h> +#include <gcrypt.h> + +#include <assuan.h> /* malloc hooks */ + +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "iso7816.h" +#include "apdu.h" +#include "ccid-driver.h" +#include "../common/gc-opt-flags.h" +#include "../common/asshelp.h" +#include "../common/exechelp.h" +#include "../common/init.h" + +#ifndef ENAMETOOLONG +# define ENAMETOOLONG EINVAL +#endif + +enum cmd_and_opt_values +{ aNull = 0, + oCsh = 'c', + oQuiet = 'q', + oSh = 's', + oVerbose = 'v', + + oNoVerbose = 500, + aGPGConfList, + aGPGConfTest, + oOptions, + oDebug, + oDebugAll, + oDebugLevel, + oDebugWait, + oDebugAllowCoreDump, + oDebugCCIDDriver, + oDebugLogTid, + oDebugAssuanLogCats, + oNoGreeting, + oNoOptions, + oHomedir, + oNoDetach, + oNoGrab, + oLogFile, + oServer, + oMultiServer, + oDaemon, + oBatch, + oReaderPort, + oCardTimeout, + octapiDriver, + opcscDriver, + opcscShared, + oDisableCCID, + oDisableOpenSC, + oDisablePinpad, + oAllowAdmin, + oDenyAdmin, + oDisableApplication, + oEnablePinpadVarlen, + oListenBacklog, + + oNoop +}; + + + +static ARGPARSE_OPTS opts[] = { + ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), + ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), + + ARGPARSE_header (NULL, N_("Options used for startup")), + + ARGPARSE_s_n (oServer,"server", N_("run in server mode (foreground)")), + ARGPARSE_s_n (oMultiServer, "multi-server", + N_("run in multi server mode (foreground)")), + ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")), + ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), + ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), + ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")), + ARGPARSE_s_s (oHomedir, "homedir", "@"), + ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")), + ARGPARSE_noconffile (oNoOptions, "no-options", "@"), + + + ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")), + + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_n (oDebugAll, "debug-all", "@"), + ARGPARSE_s_s (oDebugLevel, "debug-level" , + N_("|LEVEL|set the debugging level to LEVEL")), + ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), + ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"), + ARGPARSE_s_n (oDebugCCIDDriver, "debug-ccid-driver", "@"), + ARGPARSE_s_n (oDebugLogTid, "debug-log-tid", "@"), + ARGPARSE_p_u (oDebugAssuanLogCats, "debug-assuan-log-cats", "@"), + ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write a log to FILE")), + + + ARGPARSE_header ("Configuration", + N_("Options controlling the configuration")), + + ARGPARSE_s_s (oReaderPort, "reader-port", + N_("|N|connect to reader at port N")), + ARGPARSE_s_s (octapiDriver, "ctapi-driver", + N_("|NAME|use NAME as ct-API driver")), + ARGPARSE_s_s (opcscDriver, "pcsc-driver", + N_("|NAME|use NAME as PC/SC driver")), + ARGPARSE_s_n (opcscShared, "pcsc-shared", "@"), + ARGPARSE_s_n (oDisableCCID, "disable-ccid", +#ifdef HAVE_LIBUSB + N_("do not use the internal CCID driver") +#else + "@" +#endif + /* end --disable-ccid */), + ARGPARSE_s_u (oCardTimeout, "card-timeout", + N_("|N|disconnect the card after N seconds of inactivity")), + + ARGPARSE_s_n (oDisablePinpad, "disable-pinpad", + N_("do not use a reader's pinpad")), + ARGPARSE_ignore (300, "disable-keypad"), + ARGPARSE_s_n (oEnablePinpadVarlen, "enable-pinpad-varlen", + N_("use variable length input for pinpad")), + ARGPARSE_s_s (oDisableApplication, "disable-application", "@"), + ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), + + + ARGPARSE_header("Security", N_("Options controlling the security")), + + ARGPARSE_s_n (oAllowAdmin, "allow-admin", "@"), + ARGPARSE_s_n (oDenyAdmin, "deny-admin", + N_("deny the use of admin card commands")), + + /* Stubs for options which are implemented by 2.3 or later. */ + ARGPARSE_s_s (oNoop, "application-priority", "@"), + + ARGPARSE_end () +}; + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_MPI_VALUE , "mpi" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_MEMORY_VALUE , "memory" }, + { DBG_CACHE_VALUE , "cache" }, + { DBG_MEMSTAT_VALUE, "memstat" }, + { DBG_HASHING_VALUE, "hashing" }, + { DBG_IPC_VALUE , "ipc" }, + { DBG_CARD_VALUE , "card" }, + { DBG_CARD_IO_VALUE, "cardio" }, + { DBG_READER_VALUE , "reader" }, + { 0, NULL } + }; + + +/* The card driver we use by default for PC/SC. */ +#if defined(HAVE_W32_SYSTEM) || defined(__CYGWIN__) +#define DEFAULT_PCSC_DRIVER "winscard.dll" +#elif defined(__APPLE__) +#define DEFAULT_PCSC_DRIVER "/System/Library/Frameworks/PCSC.framework/PCSC" +#elif defined(__GLIBC__) +#define DEFAULT_PCSC_DRIVER "libpcsclite.so.1" +#else +#define DEFAULT_PCSC_DRIVER "libpcsclite.so" +#endif + +/* The timer tick used to check card removal. + + We poll every 500ms to let the user immediately know a status + change. + + For a card reader with an interrupt endpoint, this timer is not + used with the internal CCID driver. + + This is not too good for power saving but given that there is no + easy way to block on card status changes it is the best we can do. + For PC/SC we could in theory use an extra thread to wait for status + changes but that requires a native thread because there is no way + to make the underlying PC/SC card change function block using a Npth + mechanism. Given that a native thread could only be used under W32 + we don't do that at all. */ +#define TIMERTICK_INTERVAL_SEC (0) +#define TIMERTICK_INTERVAL_USEC (500000) + +/* 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; + +/* Flag telling whether we are running as a pipe server. */ +static int pipe_server; + +/* Name of the communication socket */ +static char *socket_name; +/* Name of the redirected socket or NULL. */ +static char *redir_socket_name; + +/* We need to keep track of the server's nonces (these are dummies for + POSIX systems). */ +static assuan_sock_nonce_t socket_nonce; + +/* Value for the listen() backlog argument. Change at runtime with + * --listen-backlog. */ +static int listen_backlog = 64; + +#ifdef HAVE_W32_SYSTEM +static HANDLE the_event; +#else +/* PID to notify update of usb devices. */ +static pid_t main_thread_pid; +#endif +#ifdef HAVE_PSELECT_NO_EINTR +/* FD to notify changes. */ +static int notify_fd; +#endif + +static char *create_socket_name (char *standard_name); +static gnupg_fd_t create_server_socket (const char *name, + char **r_redir_name, + assuan_sock_nonce_t *nonce); + +static void *start_connection_thread (void *arg); +static void handle_connections (int listen_fd); + +/* Pth wrapper function definitions. */ +ASSUAN_SYSTEM_NPTH_IMPL; + +static int active_connections; + + +static char * +make_libversion (const char *libname, const char *(*getfnc)(const char*)) +{ + const char *s; + char *result; + + if (maybe_setuid) + { + gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ + maybe_setuid = 0; + } + s = getfnc (NULL); + result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); + strcpy (stpcpy (stpcpy (result, libname), " "), s); + return result; +} + + +static const char * +my_strusage (int level) +{ + static char *ver_gcry, *ver_ksba; + const char *p; + + switch (level) + { + case 9: p = "GPL-3.0-or-later"; break; + case 11: p = "@SCDAEMON@ (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 20: + if (!ver_gcry) + ver_gcry = make_libversion ("libgcrypt", gcry_check_version); + p = ver_gcry; + break; + case 21: + if (!ver_ksba) + ver_ksba = make_libversion ("libksba", ksba_check_version); + p = ver_ksba; + break; + case 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 int +tid_log_callback (unsigned long *rvalue) +{ + int len = sizeof (*rvalue); + npth_t thread; + + thread = npth_self (); + if (sizeof (thread) < len) + len = sizeof (thread); + memcpy (rvalue, &thread, len); + + return 2; /* Use use hex representation. */ +} + + +/* Setup the debugging. With a LEVEL of NULL only the active debug + flags are propagated to the subsystems. With LEVEL set, a specific + set of debug flags is set; thus overriding all flags already + set. */ +static void +set_debug (const char *level) +{ + int numok = (level && digitp (level)); + int numlvl = numok? atoi (level) : 0; + + if (!level) + ; + else if (!strcmp (level, "none") || (numok && numlvl < 1)) + opt.debug = 0; + else if (!strcmp (level, "basic") || (numok && numlvl <= 2)) + opt.debug = DBG_IPC_VALUE; + else if (!strcmp (level, "advanced") || (numok && numlvl <= 5)) + opt.debug = DBG_IPC_VALUE; + else if (!strcmp (level, "expert") || (numok && numlvl <= 8)) + opt.debug = (DBG_IPC_VALUE|DBG_CACHE_VALUE|DBG_CARD_IO_VALUE); + else if (!strcmp (level, "guru") || numok) + { + opt.debug = ~0; + /* Unless the "guru" string has been used we don't want to allow + hashing debugging. The rationale is that people tend to + select the highest debug value and would then clutter their + disk with debug files which may reveal confidential data. */ + if (numok) + opt.debug &= ~(DBG_HASHING_VALUE); + } + else + { + log_error (_("invalid debug-level '%s' given\n"), level); + scd_exit(2); + } + + + if (opt.debug && !opt.verbose) + opt.verbose = 1; + if (opt.debug && opt.quiet) + opt.quiet = 0; + + if (opt.debug & DBG_MPI_VALUE) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); + if (opt.debug & DBG_CRYPTO_VALUE ) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + + if (opt.debug) + parse_debug_flag (NULL, &opt.debug, debug_flags); +} + + + +static void +cleanup (void) +{ + if (socket_name && *socket_name) + { + char *name; + + name = redir_socket_name? redir_socket_name : socket_name; + + gnupg_remove (name); + *socket_name = 0; + } +} + +static void +setup_signal_mask (void) +{ +#ifndef HAVE_W32_SYSTEM + npth_sigev_init (); + npth_sigev_add (SIGHUP); + npth_sigev_add (SIGUSR1); + npth_sigev_add (SIGUSR2); + npth_sigev_add (SIGINT); + npth_sigev_add (SIGCONT); + npth_sigev_add (SIGTERM); + npth_sigev_fini (); + main_thread_pid = getpid (); +#endif +} + +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int orig_argc; + char **orig_argv; + char *last_configname = NULL; + const char *configname = NULL; + const char *shell; + int debug_argparser = 0; + const char *debug_level = NULL; + int greeting = 0; + int nogreeting = 0; + int multi_server = 0; + int is_daemon = 0; + int nodetach = 0; + int csh_style = 0; + char *logfile = NULL; + int debug_wait = 0; + int gpgconf_list = 0; + char *config_filename = NULL; + int allow_coredump = 0; + struct assuan_malloc_hooks malloc_hooks; + int res; + npth_t pipecon_handler; + + early_system_init (); + 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", GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID); + + /* Make sure that our subsystems are ready. */ + i18n_init (); + init_common_subsystems (&argc, &argv); + + ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); + + malloc_hooks.malloc = gcry_malloc; + malloc_hooks.realloc = gcry_realloc; + malloc_hooks.free = gcry_free; + assuan_set_malloc_hooks (&malloc_hooks); + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); + assuan_sock_init (); + setup_libassuan_logging (&opt.debug, NULL); + + setup_libgcrypt_logging (); + gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); + + disable_core_dumps (); + + /* Set default options. */ + opt.allow_admin = 1; + opt.pcsc_driver = DEFAULT_PCSC_DRIVER; + + shell = getenv ("SHELL"); + if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) + csh_style = 1; + + /* Check whether we have a config file on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + while (gnupg_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oDebug: + case oDebugAll: + debug_argparser++; + break; + case oHomedir: + gnupg_set_homedir (pargs.r.ret_str); + break; + } + } + /* Reset the flags. */ + pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + + /* Initialize the secure memory. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + maybe_setuid = 0; + + /* + * Now we are working under our real uid + */ + + /* The configuraton directories for use by gpgrt_argparser. */ + gnupg_set_confdir (GNUPG_CONFDIR_SYS, gnupg_sysconfdir ()); + gnupg_set_confdir (GNUPG_CONFDIR_USER, gnupg_homedir ()); + + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags |= (ARGPARSE_FLAG_RESET + | ARGPARSE_FLAG_KEEP + | ARGPARSE_FLAG_SYS + | ARGPARSE_FLAG_USER); + while (gnupg_argparser (&pargs, opts, SCDAEMON_NAME EXTSEP_S "conf")) + { + switch (pargs.r_opt) + { + case ARGPARSE_CONFFILE: + if (debug_argparser) + log_info (_("reading options from '%s'\n"), + pargs.r_type? pargs.r.ret_str: "[cmdline]"); + if (pargs.r_type) + { + xfree (last_configname); + last_configname = xstrdup (pargs.r.ret_str); + configname = last_configname; + } + else + configname = NULL; + break; + + case aGPGConfList: gpgconf_list = 1; break; + case aGPGConfTest: gpgconf_list = 2; break; + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oBatch: opt.batch=1; break; + + case oDebug: + if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) + { + pargs.r_opt = ARGPARSE_INVALID_ARG; + pargs.err = ARGPARSE_PRINT_ERROR; + } + break; + case oDebugAll: opt.debug = ~0; break; + case oDebugLevel: debug_level = pargs.r.ret_str; break; + case oDebugWait: debug_wait = pargs.r.ret_int; break; + case oDebugAllowCoreDump: + enable_core_dumps (); + allow_coredump = 1; + break; + case oDebugCCIDDriver: +#ifdef HAVE_LIBUSB + ccid_set_debug_level (ccid_set_debug_level (-1)+1); +#endif /*HAVE_LIBUSB*/ + break; + case oDebugLogTid: + log_set_pid_suffix_cb (tid_log_callback); + break; + case oDebugAssuanLogCats: + set_libassuan_log_cats (pargs.r.ret_ulong); + break; + + case oNoGreeting: nogreeting = 1; break; + case oNoVerbose: opt.verbose = 0; break; + case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; + case oNoDetach: nodetach = 1; break; + case oLogFile: logfile = pargs.r.ret_str; break; + case oCsh: csh_style = 1; break; + case oSh: csh_style = 0; break; + case oServer: pipe_server = 1; break; + case oMultiServer: pipe_server = 1; multi_server = 1; break; + case oDaemon: is_daemon = 1; break; + + case oReaderPort: opt.reader_port = pargs.r.ret_str; break; + case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break; + case opcscDriver: opt.pcsc_driver = pargs.r.ret_str; break; + case opcscShared: opt.pcsc_shared = 1; break; + case oDisableCCID: opt.disable_ccid = 1; break; + case oDisableOpenSC: break; + + case oDisablePinpad: opt.disable_pinpad = 1; break; + + case oAllowAdmin: /* Dummy because allow is now the default. */ + break; + case oDenyAdmin: opt.allow_admin = 0; break; + + case oCardTimeout: opt.card_timeout = pargs.r.ret_ulong; break; + + case oDisableApplication: + add_to_strlist (&opt.disabled_applications, pargs.r.ret_str); + break; + + case oEnablePinpadVarlen: opt.enable_pinpad_varlen = 1; break; + + case oListenBacklog: + listen_backlog = pargs.r.ret_int; + break; + + case oNoop: break; + + default: + if (configname) + pargs.err = ARGPARSE_PRINT_WARNING; + else + pargs.err = ARGPARSE_PRINT_ERROR; + break; + } + } + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (!last_configname) + config_filename = make_filename (gnupg_homedir (), + SCDAEMON_NAME EXTSEP_S "conf", + NULL); + else + { + config_filename = last_configname; + last_configname = NULL; + } + + if (log_get_errorcount(0)) + exit(2); + if (nogreeting ) + greeting = 0; + + if (greeting) + { + es_fprintf (es_stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + es_fprintf (es_stderr, "%s\n", strusage(15) ); + } +#ifdef IS_DEVELOPMENT_VERSION + log_info ("NOTE: this is a development version!\n"); +#endif + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (_("Note: '%s' is not considered an option\n"), argv[i]); + } + + if (atexit (cleanup)) + { + log_error ("atexit failed\n"); + cleanup (); + exit (1); + } + + set_debug (debug_level); + + if (initialize_module_command ()) + { + log_error ("initialization failed\n"); + cleanup (); + exit (1); + } + + if (gpgconf_list == 2) + scd_exit (0); + if (gpgconf_list) + { + /* List options and default values in the GPG Conf format. */ + char *filename_esc; + + filename_esc = percent_escape (config_filename, NULL); + es_printf ("%s-%s.conf:%lu:\"%s\n", + GPGCONF_NAME, SCDAEMON_NAME, + GC_OPT_FLAG_DEFAULT, filename_esc); + xfree (filename_esc); + + es_printf ("verbose:%lu:\n" + "quiet:%lu:\n" + "debug-level:%lu:\"none:\n" + "log-file:%lu:\n", + GC_OPT_FLAG_NONE, + GC_OPT_FLAG_NONE, + GC_OPT_FLAG_DEFAULT, + GC_OPT_FLAG_NONE ); + + es_printf ("reader-port:%lu:\n", GC_OPT_FLAG_NONE ); + es_printf ("ctapi-driver:%lu:\n", GC_OPT_FLAG_NONE ); + es_printf ("pcsc-driver:%lu:\"%s:\n", + GC_OPT_FLAG_DEFAULT, DEFAULT_PCSC_DRIVER ); +#ifdef HAVE_LIBUSB + es_printf ("disable-ccid:%lu:\n", GC_OPT_FLAG_NONE ); +#endif + es_printf ("deny-admin:%lu:\n", GC_OPT_FLAG_NONE ); + es_printf ("disable-pinpad:%lu:\n", GC_OPT_FLAG_NONE ); + es_printf ("card-timeout:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, 0); + es_printf ("enable-pinpad-varlen:%lu:\n", GC_OPT_FLAG_NONE ); + + scd_exit (0); + } + + /* Now start with logging to a file if this is desired. */ + if (logfile) + { + log_set_file (logfile); + log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); + } + + if (debug_wait && pipe_server) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + gnupg_sleep (debug_wait); + log_debug ("... okay\n"); + } + + if (pipe_server) + { + /* This is the simple pipe based server */ + ctrl_t ctrl; + npth_attr_t tattr; + int fd = -1; + +#ifndef HAVE_W32_SYSTEM + { + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + } +#endif + + npth_init (); + setup_signal_mask (); + gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); + + /* If --debug-allow-core-dump has been given we also need to + switch the working directory to a place where we can actually + write. */ + if (allow_coredump) + { + if (chdir("/tmp")) + log_debug ("chdir to '/tmp' failed: %s\n", strerror (errno)); + else + log_debug ("changed working directory to '/tmp'\n"); + } + + /* In multi server mode we need to listen on an additional + socket. Create that socket now before starting the handler + for the pipe connection. This allows that handler to send + back the name of that socket. */ + if (multi_server) + { + socket_name = create_socket_name (SCDAEMON_SOCK_NAME); + fd = FD2INT(create_server_socket (socket_name, + &redir_socket_name, &socket_nonce)); + } + + res = npth_attr_init (&tattr); + if (res) + { + log_error ("error allocating thread attributes: %s\n", + strerror (res)); + scd_exit (2); + } + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + + ctrl = xtrycalloc (1, sizeof *ctrl); + if ( !ctrl ) + { + log_error ("error allocating connection control data: %s\n", + strerror (errno) ); + scd_exit (2); + } + ctrl->thread_startup.fd = GNUPG_INVALID_FD; + res = npth_create (&pipecon_handler, &tattr, start_connection_thread, ctrl); + if (res) + { + log_error ("error spawning pipe connection handler: %s\n", + strerror (res) ); + xfree (ctrl); + scd_exit (2); + } + npth_setname_np (pipecon_handler, "pipe-connection"); + npth_attr_destroy (&tattr); + + /* We run handle_connection to wait for the shutdown signal and + to run the ticker stuff. */ + handle_connections (fd); + if (fd != -1) + close (fd); + } + 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; +#ifndef HAVE_W32_SYSTEM + pid_t pid; + int i; +#endif + + /* Create the socket. */ + socket_name = create_socket_name (SCDAEMON_SOCK_NAME); + fd = FD2INT (create_server_socket (socket_name, + &redir_socket_name, &socket_nonce)); + + + fflush (NULL); +#ifdef HAVE_W32_SYSTEM + (void)csh_style; + (void)nodetach; +#else + 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 (gpgrt_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, '=') = ' '; + es_printf ( "setenv %s;\n", infostr); + } + else + { + es_printf ( "%s; export SCDAEMON_INFO;\n", infostr); + } + xfree (infostr); + exit (0); + } + /* NOTREACHED */ + } /* end parent */ + + /* This is the child. */ + + npth_init (); + setup_signal_mask (); + gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); + + /* 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_test_fd (i) && i != fd ) + { + if ( !close (i) + && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1) + { + log_error ("failed to open '%s': %s\n", + "/dev/null", strerror (errno)); + cleanup (); + exit (1); + } + } + } + + if (setsid() == -1) + { + log_error ("setsid() failed: %s\n", strerror(errno) ); + cleanup (); + exit (1); + } + } + + { + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + } + +#endif /*!HAVE_W32_SYSTEM*/ + + if (gnupg_chdir (gnupg_daemon_rootdir ())) + { + log_error ("chdir to '%s' failed: %s\n", + gnupg_daemon_rootdir (), strerror (errno)); + exit (1); + } + + handle_connections (fd); + + close (fd); + } + + xfree (config_filename); + return 0; +} + + +void +scd_exit (int rc) +{ + apdu_prepare_exit (); +#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); +} + + +static void +scd_init_default_ctrl (ctrl_t ctrl) +{ + (void)ctrl; +} + +static void +scd_deinit_default_ctrl (ctrl_t ctrl) +{ + if (!ctrl) + return; + xfree (ctrl->in_data.value); + ctrl->in_data.value = NULL; + ctrl->in_data.valuelen = 0; +} + + +/* Return the name of the socket to be used to connect to this + process. If no socket is available, return NULL. */ +const char * +scd_get_socket_name () +{ + if (socket_name && *socket_name) + return socket_name; + return NULL; +} + + +#ifndef HAVE_W32_SYSTEM +static void +handle_signal (int signo) +{ + switch (signo) + { + case SIGHUP: + log_info ("SIGHUP received - " + "re-reading configuration and resetting cards\n"); +/* reread_configuration (); */ + break; + + case SIGUSR1: + log_info ("SIGUSR1 received - printing internal information:\n"); + /* Fixme: We need to see how to integrate pth dumping into our + logging system. */ + /* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */ + app_dump_state (); + break; + + case SIGUSR2: + log_info ("SIGUSR2 received - no action defined\n"); + break; + + case SIGCONT: + /* Nothing. */ + log_debug ("SIGCONT received - breaking select\n"); + break; + + case SIGTERM: + if (!shutdown_pending) + log_info ("SIGTERM received - shutting down ...\n"); + else + log_info ("SIGTERM received - still %i running threads\n", + active_connections); + shutdown_pending++; + if (shutdown_pending > 2) + { + log_info ("shutdown forced\n"); + log_info ("%s %s stopped\n", strusage(11), strusage(13) ); + cleanup (); + scd_exit (0); + } + break; + + case SIGINT: + log_info ("SIGINT received - immediate shutdown\n"); + log_info( "%s %s stopped\n", strusage(11), strusage(13)); + cleanup (); + scd_exit (0); + break; + + default: + log_info ("signal %d received - no action defined\n", signo); + } +} +#endif /*!HAVE_W32_SYSTEM*/ + + +/* Create a name for the socket. We check for valid characters as + well as against a maximum allowed length for a unix domain socket + is done. The function terminates the process in case of an error. + Retunrs: Pointer to an allcoated string with the absolute name of + the socket used. */ +static char * +create_socket_name (char *standard_name) +{ + char *name; + + name = make_filename (gnupg_socketdir (), standard_name, NULL); + if (strchr (name, PATHSEP_C)) + { + log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S); + scd_exit (2); + } + return name; +} + + + +/* Create a Unix domain socket with NAME. Returns the file descriptor + or terminates the process in case of an error. If the socket has + been redirected the name of the real socket is stored as a malloced + string at R_REDIR_NAME. */ +static gnupg_fd_t +create_server_socket (const char *name, char **r_redir_name, + assuan_sock_nonce_t *nonce) +{ + struct sockaddr *addr; + struct sockaddr_un *unaddr; + socklen_t len; + gnupg_fd_t fd; + int rc; + + xfree (*r_redir_name); + *r_redir_name = NULL; + + fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0); + if (fd == GNUPG_INVALID_FD) + { + log_error (_("can't create socket: %s\n"), strerror (errno)); + scd_exit (2); + } + + unaddr = xmalloc (sizeof (*unaddr)); + addr = (struct sockaddr*)unaddr; + + { + int redirected; + + if (assuan_sock_set_sockaddr_un (name, addr, &redirected)) + { + if (errno == ENAMETOOLONG) + log_error (_("socket name '%s' is too long\n"), name); + else + log_error ("error preparing socket '%s': %s\n", + name, gpg_strerror (gpg_error_from_syserror ())); + scd_exit (2); + } + if (redirected) + { + *r_redir_name = xstrdup (unaddr->sun_path); + if (opt.verbose) + log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name); + } + } + + len = SUN_LEN (unaddr); + + rc = assuan_sock_bind (fd, addr, len); + if (rc == -1 && errno == EADDRINUSE) + { + gnupg_remove (unaddr->sun_path); + rc = assuan_sock_bind (fd, addr, len); + } + if (rc != -1 + && (rc=assuan_sock_get_nonce (addr, len, nonce))) + log_error (_("error getting nonce for the socket\n")); + if (rc == -1) + { + log_error (_("error binding socket to '%s': %s\n"), + unaddr->sun_path, + gpg_strerror (gpg_error_from_syserror ())); + assuan_sock_close (fd); + scd_exit (2); + } + + if (gnupg_chmod (unaddr->sun_path, "-rwx")) + log_error (_("can't set permissions of '%s': %s\n"), + unaddr->sun_path, strerror (errno)); + + if (listen (FD2INT(fd), listen_backlog) == -1) + { + log_error ("listen(fd, %d) failed: %s\n", + listen_backlog, gpg_strerror (gpg_error_from_syserror ())); + assuan_sock_close (fd); + scd_exit (2); + } + + if (opt.verbose) + log_info (_("listening on socket '%s'\n"), unaddr->sun_path); + + return fd; +} + + + +/* This is the standard connection thread's main function. */ +static void * +start_connection_thread (void *arg) +{ + ctrl_t ctrl = arg; + + if (ctrl->thread_startup.fd != GNUPG_INVALID_FD + && assuan_sock_check_nonce (ctrl->thread_startup.fd, &socket_nonce)) + { + log_info (_("error reading nonce on fd %d: %s\n"), + FD2INT(ctrl->thread_startup.fd), strerror (errno)); + assuan_sock_close (ctrl->thread_startup.fd); + xfree (ctrl); + return NULL; + } + + active_connections++; + + scd_init_default_ctrl (ctrl); + if (opt.verbose) + log_info (_("handler for fd %d started\n"), + FD2INT(ctrl->thread_startup.fd)); + + /* If this is a pipe server, we request a shutdown if the command + handler asked for it. With the next ticker event and given that + no other connections are running the shutdown will then + happen. */ + if (scd_command_handler (ctrl, FD2INT(ctrl->thread_startup.fd)) + && pipe_server) + shutdown_pending = 1; + + if (opt.verbose) + log_info (_("handler for fd %d terminated\n"), + FD2INT (ctrl->thread_startup.fd)); + + scd_deinit_default_ctrl (ctrl); + xfree (ctrl); + + if (--active_connections == 0) + scd_kick_the_loop (); + + return NULL; +} + + +void +scd_kick_the_loop (void) +{ + /* Kick the select loop. */ +#ifdef HAVE_W32_SYSTEM + int ret = SetEvent (the_event); + if (ret == 0) + log_error ("SetEvent for scd_kick_the_loop failed: %s\n", + w32_strerror (-1)); +#elif defined(HAVE_PSELECT_NO_EINTR) + write (notify_fd, "", 1); +#else + int ret = kill (main_thread_pid, SIGCONT); + if (ret < 0) + log_error ("sending signal for scd_kick_the_loop failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); +#endif +} + +/* Connection handler loop. Wait for connection requests and spawn a + thread after accepting a connection. LISTEN_FD is allowed to be -1 + in which case this code will only do regular timeouts and handle + signals. */ +static void +handle_connections (int listen_fd) +{ + npth_attr_t tattr; + struct sockaddr_un paddr; + socklen_t plen; + fd_set fdset, read_fdset; + int nfd; + int ret; + int fd; + struct timespec timeout; + struct timespec *t; + int saved_errno; +#ifdef HAVE_W32_SYSTEM + HANDLE events[2]; + unsigned int events_set; +#else + int signo; +#endif +#ifdef HAVE_PSELECT_NO_EINTR + int pipe_fd[2]; + + ret = gnupg_create_pipe (pipe_fd); + if (ret) + { + log_error ("pipe creation failed: %s\n", gpg_strerror (ret)); + return; + } + notify_fd = pipe_fd[1]; +#endif + + ret = npth_attr_init(&tattr); + if (ret) + { + log_error ("npth_attr_init failed: %s\n", strerror (ret)); + return; + } + + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + +#ifdef HAVE_W32_SYSTEM + { + HANDLE h, h2; + SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; + + events[0] = the_event = INVALID_HANDLE_VALUE; + events[1] = INVALID_HANDLE_VALUE; + /* Create event for manual reset, initially non-signaled. Make it + * waitable and inheritable. */ + h = CreateEvent (&sa, TRUE, FALSE, NULL); + if (!h) + log_error ("can't create scd event: %s\n", w32_strerror (-1) ); + else if (!DuplicateHandle (GetCurrentProcess(), h, + GetCurrentProcess(), &h2, + EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) + { + log_error ("setting synchronize for scd_kick_the_loop failed: %s\n", + w32_strerror (-1) ); + CloseHandle (h); + } + else + { + CloseHandle (h); + events[0] = the_event = h2; + } + } +#endif + + FD_ZERO (&fdset); + nfd = 0; + if (listen_fd != -1) + { + FD_SET (listen_fd, &fdset); + nfd = listen_fd; + } + + for (;;) + { + int periodical_check; + int max_fd = nfd; + + if (shutdown_pending) + { + if (active_connections == 0) + break; /* ready */ + + /* Do not accept anymore connections but wait for existing + connections to terminate. We do this by clearing out all + file descriptors to wait for, so that the select will be + used to just wait on a signal or timeout event. */ + FD_ZERO (&fdset); + listen_fd = -1; + } + + periodical_check = scd_update_reader_status_file (); + + timeout.tv_sec = TIMERTICK_INTERVAL_SEC; + timeout.tv_nsec = TIMERTICK_INTERVAL_USEC * 1000; + + if (shutdown_pending || periodical_check) + t = &timeout; + else + t = NULL; + + /* POSIX says that fd_set should be implemented as a structure, + thus a simple assignment is fine to copy the entire set. */ + read_fdset = fdset; + +#ifdef HAVE_PSELECT_NO_EINTR + FD_SET (pipe_fd[0], &read_fdset); + if (max_fd < pipe_fd[0]) + max_fd = pipe_fd[0]; +#else + (void)max_fd; +#endif + +#ifndef HAVE_W32_SYSTEM + ret = npth_pselect (max_fd+1, &read_fdset, NULL, NULL, t, + npth_sigev_sigmask ()); + saved_errno = errno; + + while (npth_sigev_get_pending(&signo)) + handle_signal (signo); +#else + ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, t, + events, &events_set); + saved_errno = errno; + if (events_set & 1) + continue; +#endif + + if (ret == -1 && saved_errno != EINTR) + { + log_error (_("npth_pselect failed: %s - waiting 1s\n"), + strerror (saved_errno)); + npth_sleep (1); + continue; + } + + if (ret <= 0) + /* Timeout. Will be handled when calculating the next timeout. */ + continue; + +#ifdef HAVE_PSELECT_NO_EINTR + if (FD_ISSET (pipe_fd[0], &read_fdset)) + { + char buf[256]; + + read (pipe_fd[0], buf, sizeof buf); + } +#endif + + if (listen_fd != -1 && FD_ISSET (listen_fd, &read_fdset)) + { + ctrl_t ctrl; + + plen = sizeof paddr; + fd = npth_accept (listen_fd, (struct sockaddr *)&paddr, &plen); + if (fd == -1) + { + log_error ("accept failed: %s\n", strerror (errno)); + } + else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) ) + { + log_error ("error allocating connection control data: %s\n", + strerror (errno) ); + close (fd); + } + else + { + char threadname[50]; + npth_t thread; + + snprintf (threadname, sizeof threadname, "conn fd=%d", fd); + ctrl->thread_startup.fd = INT2FD (fd); + ret = npth_create (&thread, &tattr, start_connection_thread, ctrl); + if (ret) + { + log_error ("error spawning connection handler: %s\n", + strerror (ret)); + xfree (ctrl); + close (fd); + } + else + npth_setname_np (thread, threadname); + } + } + } + +#ifdef HAVE_W32_SYSTEM + if (the_event != INVALID_HANDLE_VALUE) + CloseHandle (the_event); +#endif +#ifdef HAVE_PSELECT_NO_EINTR + close (pipe_fd[0]); + close (pipe_fd[1]); +#endif + cleanup (); + log_info (_("%s %s stopped\n"), strusage(11), strusage(13)); + npth_attr_destroy (&tattr); +} + +/* Return the number of active connections. */ +int +get_active_connection_count (void) +{ + return active_connections; +} diff --git a/scd/scdaemon.h b/scd/scdaemon.h new file mode 100644 index 0000000..bb7c1b0 --- /dev/null +++ b/scd/scdaemon.h @@ -0,0 +1,145 @@ +/* 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef 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 <time.h> +#include <gcrypt.h> +#include "../common/util.h" +#include "../common/sysutils.h" +#include "app-common.h" + + +/* To convey some special hash algorithms we use algorithm numbers + reserved for application use. */ +#ifndef GCRY_MODULE_ID_USER +#define GCRY_MODULE_ID_USER 1024 +#endif +#define MD_USER_TLS_MD5SHA1 (GCRY_MODULE_ID_USER+1) + +/* Maximum length of a digest. */ +#define MAX_DIGEST_LEN 64 + + + +/* A large struct name "opt" to keep global flags. */ +EXTERN_UNLESS_MAIN_MODULE +struct +{ + unsigned int debug; /* Debug flags (DBG_foo_VALUE). */ + int verbose; /* Verbosity level. */ + int quiet; /* Be as quiet as possible. */ + int dry_run; /* Don't change any persistent data. */ + int batch; /* Batch mode. */ + const char *ctapi_driver; /* Library to access the ctAPI. */ + const char *pcsc_driver; /* Library to access the PC/SC system. */ + const char *reader_port; /* NULL or reder port to use. */ + int disable_ccid; /* Disable the use of the internal CCID driver. */ + int disable_pinpad; /* Do not use a pinpad. */ + int enable_pinpad_varlen; /* Use variable length input for pinpad. */ + int allow_admin; /* Allow the use of admin commands for certain + cards. */ + int pcsc_shared; /* Use shared PC/SC access. */ + strlist_t disabled_applications; /* Card applications we do not + want to use. */ + unsigned long card_timeout; /* Disconnect after N seconds of inactivity. */ +} opt; + + +#define DBG_MPI_VALUE 2 /* debug mpi details */ +#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ +#define DBG_CARD_VALUE 16 /* debug card info */ +#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ +#define DBG_CACHE_VALUE 64 /* debug the caching */ +#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ +#define DBG_HASHING_VALUE 512 /* debug hashing operations */ +#define DBG_IPC_VALUE 1024 +#define DBG_CARD_IO_VALUE 2048 /* debug card I/O (e.g. APDUs). */ +#define DBG_READER_VALUE 4096 /* Trace reader related functions. */ + +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) +#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) +#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) +#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) +#define DBG_IPC (opt.debug & DBG_IPC_VALUE) +#define DBG_CARD (opt.debug & DBG_CARD_VALUE) +#define DBG_CARD_IO (opt.debug & DBG_CARD_IO_VALUE) +#define DBG_READER (opt.debug & DBG_READER_VALUE) + +struct server_local_s; +struct app_ctx_s; + +struct server_control_s +{ + /* Private data used to fire up the connection thread. We use this + structure do avoid an extra allocation for just a few bytes. */ + struct { + gnupg_fd_t fd; + } thread_startup; + + /* Local data of the server; used only in command.c. */ + struct server_local_s *server_local; + + /* The application context used with this connection or NULL if none + associated. Note that this is shared with the other connections: + All connections accessing the same reader are using the same + application context. */ + struct app_ctx_s *app_ctx; + + /* Helper to store the value we are going to sign */ + struct + { + unsigned char *value; + int valuelen; + } in_data; +}; + + +/*-- scdaemon.c --*/ +void scd_exit (int rc); +const char *scd_get_socket_name (void); + +/*-- command.c --*/ +gpg_error_t initialize_module_command (void); +int scd_command_handler (ctrl_t, int); +void send_keyinfo (ctrl_t ctrl, int data, const char *keygrip_str, + const char *serialno, const char *idstr); +void send_status_info (ctrl_t ctrl, const char *keyword, ...) + GPGRT_ATTR_SENTINEL(1); +gpg_error_t send_status_direct (ctrl_t ctrl, const char *keyword, + const char *args); +gpg_error_t send_status_printf (ctrl_t ctrl, const char *keyword, + const char *format, ...) GPGRT_ATTR_PRINTF(3,4); + +void popup_prompt (void *opaque, int on); +void send_client_notifications (app_t app, int removal); +void scd_kick_the_loop (void); +int get_active_connection_count (void); + +/*-- app.c --*/ +int scd_update_reader_status_file (void); + +#endif /*SCDAEMON_H*/ diff --git a/scd/scdaemon.w32-manifest.in b/scd/scdaemon.w32-manifest.in new file mode 100644 index 0000000..22dabb6 --- /dev/null +++ b/scd/scdaemon.w32-manifest.in @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<description>GNU Privacy Guard (Scmartcard daemon)</description> +<assemblyIdentity + type="win32" + name="GnuPG.scdaemon" + version="@BUILD_VERSION@" + /> +<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- 10 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><!-- 8.1 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><!-- 8 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><!-- 7 --> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><!-- Vista --> + </application> +</compatibility> +</assembly> |